Écran E05 – “Enregistrement en cours (connecté au dictaphone)”
1-Version Agen 3
1.1. - Version App ( native REACT )
Voici un prompt AGENT3 complet pour l’App mobile React Native + Expo, écran E05 – Enregistrement en cours (connecté au dictaphone).
Tu peux le coller tel quel dans Replit Agent3.
------------------------------------------------------------- 🟦 AGENT3 PROMPT — E05_Recording (React Native + Expo) ------------------------------------------------------------- You are Replit Agent3, expert in production-grade **React Native + Expo + TypeScript** apps. The Synergia / Plode Care app skeleton already exists from the MASTER PROMPT with: - React Native + Expo + TypeScript - React Navigation (AuthNavigator + MainTabs + stacks) - Zustand stores in `/app/store` - Services in `/app/services` (odoo, n8n, supabase, etc.) - BLE integration via `react-native-ble-plx` (for the dictaphone) - Offline mode (React Query offline queue + SQLite `pending_sync` table) Your task is to implement **Screen E05 – “Enregistrement en cours (connecté au dictaphone)”** for the mobile app and hook it to the recording logic. This is the **live recording screen during a visit**. Follow ALL instructions carefully. ------------------------------------------------------------- 🟦 1. FILE & NAVIGATION ------------------------------------------------------------- Create/complete the screen: - `/app/screens/E05_Recording.tsx` Navigation assumptions: - E05 is reached from: - E02 Dashboard (“Enregistrer maintenant” or FAB without patient → free mode), OR - E03/E04 when starting a recording for a specific patient. - On STOP, we must: - Stop the recording, - Enqueue upload / IA processing, - Navigate to **E06_UploadProcessing**. Requirements: 1. Register route `E05_Recording` in the appropriate stack (likely the “Enregistrements / Recordings” stack or Patients stack). 2. Ensure that E05 **does NOT change** the bottom tab bar (still the 3 fixed tabs: Patients / Enregistrements / Paramètres). You can assume a route signature like: ```ts type RootStackParamList = { E05_Recording: { patientId?: number; patientName?: string; patientBirthDate?: string; avatarUrl?: string; recordingId?: string; // optional, for resumed recordings }; E06_UploadProcessing: { recordingId: string }; // ... };
If the type file already exists, extend it accordingly.
🟦 2. STATE & SERVICES (RECORDING + BLE + OFFLINE)
Use or create a Zustand store for the current recording:
- /app/store/recording.ts
Shape (example):
export interface Marker { id: string; timestampMs: number; label?: string; } export interface RecordingState { isRecording: boolean; startedAt: number | null; // timestamp (Date.now) elapsedMs: number; // updated by an interval markers: Marker[]; recordingId: string | null; // local ID patientId?: number; patientName?: string; // actions startRecording: (payload: { patientId?: number; patientName?: string }) => Promise<void>; stopRecording: () => Promise<string>; // returns recordingId addMarker: () => void; resetRecording: () => void; }
Internally, startRecording / stopRecording should call a simple recording service:
-
/app/services/recording/audio.ts (if not present, create)
- Can wrap Expo AV recording (or be a stub for now)
- For this prompt, you can implement a basic Expo AV recording or leave TODO comments, but the UI must be wired to these functions.
BLE dictaphone status:
-
Reuse an existing BLE service if present, for example:
- /app/services/ble/dictaphone.ts
- If not present, create a minimal hook:
export interface DictaphoneStatus { connected: boolean; batteryLevel?: number; // 0-100 storageLeftMinutes?: number; } export const useDictaphoneStatus = (): DictaphoneStatus => { // For now, return dummy values or subscribe to BLE state if implemented };
Offline flag:
- Use existing offline store if defined, e.g.:
const isOffline = useOfflineStore((s) => s.isOffline);
- If no store exists, derive from @react-native-community/netinfo or React Query onlineManager in a small hook:
const { isConnected } = useNetInfo(); const isOffline = isConnected === false;
🟦 3. UI SPEC — E05 RECORDING SCREEN
The screen must match the HD mockup and this description.
3.1 Overall layout
-
Root container: <SafeAreaView> + <View> or <ScrollView> with:
- White background (#FFFFFF)
- Centered card (rounded, subtle shadow)
-
Vertical layout:
- Patient header
- Dictaphone status pill
- Waveform (fake / animated)
- Timer
- Buttons: “Marquer” and red “Arrêter”
- Clinical reminders panel
- Offline banner (if offline)
3.2 Patient header
Top card section:
-
Left: patient avatar (rounded image) – use:
- avatarUrl from route params OR a placeholder image.
-
Right: text stack:
- Line 1: patientName, e.g. Bernard Durand (bold, large).
-
Line 2: smaller metadata:
- age + birth date (you can derive age from birthdate if provided, or just show param).
- Example: 81 ans · Né le 8 mars 1942.
Use route params patientName / patientBirthDate. If not provided (free recording), show “Enregistrement libre” and hide age.
3.3 Dictaphone status pill
On the right/top of the header card, show a pill with status:
-
If connected === true:
-
Green/teal pill with:
- Text: Enregistré (or Connecté)
- Subtext: XX% de batterie (use batteryLevel)
-
Green/teal pill with:
-
If not connected:
- Grey pill with text: Dictaphone non connecté.
Use useDictaphoneStatus() for values.
3.4 Waveform / VU-meter
Below the header:
- A simple animated waveform across the width of the card.
Implementation can be lightweight:
- Use Animated.View components with varying heights in a row and an infinite loop.
- OR use a single SVG-like shape; but keep it simple and self-contained.
This is visual only, it does not need real audio levels for now, but structure the code so we can later plug real amplitude values.
3.5 Timer
Centered big timer:
- Format MM:SS or HH:MM:SS.
- Use elapsedMs from recordingStore, updated every second with setInterval in a useEffect.
- Display e.g. 00:02:16.
Create a helper function to format milliseconds into HH:MM:SS.
3.6 Buttons — “Marquer” & “Arrêter”
Buttons placed vertically:
-
“Marquer” (white pill)
-
Style:
- White background,
- Rounded,
- Border,
- Centered text.
-
On press:
-
Call recordingStore.addMarker():
- Add marker with current elapsedMs.
- Optionally show a short Toast or Alert (“Marqueur ajouté”).
-
Call recordingStore.addMarker():
-
Style:
-
“Arrêter” (big red circular button)
- Circle with red background, white square icon in the middle.
- Label below: Arrêter.
-
On press:
- Call await recordingStore.stopRecording() which returns a recordingId.
- Trigger upload / IA processing via n8n.
- Navigate to E06.
Stop behavior:
const handleStop = async () => { try { const id = await stopRecording(); // from store await enqueueUpload(id); // see 3.7 navigation.navigate('E06_UploadProcessing', { recordingId: id }); } catch (e) { // show error Alert } };
3.7 Upload + IA processing (background)
Use a service:
- /app/services/n8n/audio_upload.ts
Implement (if not existing):
export const enqueueUpload = async (recordingId: string) => { // If online: call n8n webhook /webhook/audio_upload // If offline: push an entry in SQLite pending_sync table // For this prompt, you can just create a TODO or minimal stub. };
We only need wiring from E05:
- On STOP, call enqueueUpload(recordingId) and then navigate to E06.
The actual network logic can be minimal stub with comments if full offline queue is already handled elsewhere.
3.8 Offline banner
If isOffline === true, show a banner at the top or bottom of the screen (above the card):
- Background: yellow/orange,
- Icon: warning,
-
Text:
Pas de réseau – la synchronisation sera faite plus tard.
This banner must not block recording; purely informational.
3.9 Clinical reminders panel
At the bottom of the card, add a light reminder box:
- Title: Pensez à mentionner :
-
Bullet list or two-column layout:
- • Le type de soin
- • Niveau de douleur (0–10), tension, incidents
Use simple Text components.
🟦 4. LIFECYCLE BEHAVIOR
When E05 mounts:
-
If recordingStore.isRecording === false:
- Option 1: automatically call startRecording() with patient info from route params.
- Option 2: assume recording was started earlier and only show UI.
- For simplicity, implement:
useEffect(() => { if (!isRecording) { startRecording({ patientId: route.params?.patientId, patientName: route.params?.patientName, }); } }, []);
When E05 unmounts:
-
If still recording (user navigated away unexpectedly):
- Optionally stop recording and enqueue upload.
- Or leave as TODO with a comment.
Make sure to clear any setInterval used for the timer in useEffect cleanup.
🟦 5. TYPESCRIPT & STYLING
- Use TypeScript in E05_Recording.tsx with proper props typing from React Navigation.
- Define styles with StyleSheet.create.
- Ensure the screen works on small phones (use ScrollView if necessary).
Example component skeleton:
const E05_Recording: React.FC = () => { const navigation = useNavigation<E05RecordingNav>(); const route = useRoute<E05RecordingRoute>(); const { isRecording, elapsedMs, startRecording, stopRecording, addMarker } = useRecordingStore(); const dictaphoneStatus = useDictaphoneStatus(); const isOffline = useOfflineFlag(); // store or hook // effects + handlers... return ( <SafeAreaView style={styles.safeArea}> {/* offline banner */} {/* card with patient header, waveform, timer, buttons, reminders */} </SafeAreaView> ); }; export default E05_Recording;
🟦 6. FILES TO TOUCH
You MUST update / create:
-
/app/screens/E05_Recording.tsx
- Full UI + timer + buttons + offline banner + navigation to E06.
-
/app/store/recording.ts
- Recording state (isRecording, elapsedMs, markers, startRecording, stopRecording, resetRecording).
-
/app/services/recording/audio.ts
- Minimal wrapper / stub for audio recording (Expo AV or TODO).
-
/app/services/n8n/audio_upload.ts
- enqueueUpload(recordingId: string) function stubbed or minimal.
-
BLE hook if not present:
- /app/services/ble/dictaphone.ts
- useDictaphoneStatus() returning { connected, batteryLevel } (dummy values ok).
-
Navigation config file(s):
- Ensure E05_Recording and E06_UploadProcessing are declared in the appropriate stack navigator.
Do NOT modify the 3 bottom tabs (Patients / Enregistrements / Paramètres) or the global auth flow.
🟦 7. AFTER IMPLEMENTATION
Expected behavior:
-
When navigating to E05_Recording:
- Recording starts (if not already).
- UI shows current patient, waveform, timer.
-
Nurse can press:
- “Marquer” → marker added (no navigation).
- Red “Arrêter” → recording stops, upload is enqueued, navigation to E06_UploadProcessing.
-
If the device is offline:
- Offline banner is visible.
- Recording still works.
- Upload is queued for later.
After implementing all the above, STOP.
END OF PROMPT — E05 RECORDING (APP)
1.2 WebAPP
Voici la version WebApp pour AGENT3 (React + TypeScript) de l’écran E05 – Enregistrement en cours (connecté au dictaphone).
Tu peux la coller telle quelle dans Replit Agent3 👇
------------------------------------------------------------- 🟦 AGENT3 PROMPT — E05_Recording (WebApp React + TS) ------------------------------------------------------------- You are Replit Agent3, expert in production-ready **React + TypeScript** web apps. The Synergia / Plode Care WebApp already has: - React + TypeScript + React Router v6 - An authenticated zone with a shared MainLayout and bottom nav: - Patients → /patients - Enregistrements → /recordings - Paramètres → /settings - A dashboard screen E02 at `/dashboard` - Auth flow handled separately (E01 → login → /dashboard) Your task is to implement **Screen E05 – “Enregistrement en cours (connecté au dictaphone)”** as a WebApp page and wire it into routing + services. This is the **live recording screen during a visit**. Follow ALL instructions carefully. ------------------------------------------------------------- 🟦 1. FILES & ROUTING ------------------------------------------------------------- Create / update: - `/src/pages/E05_Recording.tsx` // main page component - `/src/services/recording/webAudio.ts` // recording abstraction (browser) - `/src/services/n8n/audio_upload.ts` // upload / queue stub - `/src/hooks/useDictaphoneStatus.ts` // dictaphone status (dummy for now) - `/src/hooks/useOfflineFlag.ts` // online/offline detection hook - Update `/src/App.tsx` routing so that: - Route: `/recordings/live` - Element: `<MainLayout><E05_Recording /></MainLayout>` Bottom nav in MainLayout must stay **unchanged** (3 tabs only). E05 must appear within the “Enregistrements” area but does not alter the tabs. Typical navigation: - From E02 or a recordings list: - `navigate("/recordings/live")` with optional patient info in state or query params. After STOP: - Navigate to E06 (upload/processing), e.g. `/recordings/processing/:recordingId`. You may add a placeholder component `E06_UploadProcessing.tsx` in `/src/pages`. ------------------------------------------------------------- 🟦 2. DATA & STATE MODEL (RECORDING + MARKERS) ------------------------------------------------------------- Implement a simple recording state using React hooks or a small store file. Inside `/src/pages/E05_Recording.tsx` (local state is OK): Types: ```ts interface Marker { id: string; timestampMs: number; label?: string; } interface RecordingState { isRecording: boolean; startedAt: number | null; elapsedMs: number; markers: Marker[]; recordingId: string | null; }
Component state:
const [isRecording, setIsRecording] = useState(false); const [startedAt, setStartedAt] = useState<number | null>(null); const [elapsedMs, setElapsedMs] = useState(0); const [markers, setMarkers] = useState<Marker[]>([]); const [recordingId, setRecordingId] = useState<string | null>(null);
Timer:
- Use useEffect with setInterval (1000 ms) when isRecording === true.
- Update elapsedMs based on Date.now() - startedAt.
- Clear interval on unmount.
Markers:
-
On “Marquer” click:
- Append a marker with timestampMs: elapsedMs and unique id (e.g. crypto.randomUUID()).
🟦 3. WEB AUDIO RECORDING SERVICE
Create /src/services/recording/webAudio.ts that wraps browser MediaRecorder.
Minimal API:
export interface StartRecordingOptions { onDataAvailable?: (blob: Blob) => void; } export interface WebRecorderHandle { stop: () => Promise<Blob>; // resolves final audio Blob } export const startWebRecording = async ( options?: StartRecordingOptions ): Promise<WebRecorderHandle> => { // Use navigator.mediaDevices.getUserMedia({ audio: true }) // Create MediaRecorder, start recording // Store chunks, resolve Blob on stop };
In E05_Recording.tsx:
- Hold recorderHandle in useRef<WebRecorderHandle | null>.
- On mount or when starting recording:
const handleStart = async () => { const handle = await startWebRecording(/* optional callbacks */); recorderRef.current = handle; setStartedAt(Date.now()); setIsRecording(true); };
- On STOP:
const handleStop = async () => { if (!recorderRef.current) return; const blob = await recorderRef.current.stop(); const id = await enqueueUpload(blob, { markers, patientInfo }); setRecordingId(id); navigate(`/recordings/processing/${id}`); };
Note: For this prompt you can simplify implementation if needed (e.g. basic MediaRecorder usage with TODO comments).
🟦 4. N8N UPLOAD SERVICE & OFFLINE QUEUE
Create /src/services/n8n/audio_upload.ts:
interface UploadMetadata { markers: Marker[]; patientId?: number; patientName?: string; } export const enqueueUpload = async ( audioBlob: Blob, metadata: UploadMetadata ): Promise<string> => { // Generate a local recordingId (uuid) // If online: POST to /webhook/audio_upload (n8n) // If offline: store Blob reference / metadata in IndexedDB or localStorage // For now: just return the generated recordingId and leave TODO comments };
We only need the wiring from E05; full offline persistence can be stubbed.
🟦 5. DICTAPHONE STATUS & OFFLINE FLAG
🟦 5.1 useDictaphoneStatus
Create /src/hooks/useDictaphoneStatus.ts:
export interface DictaphoneStatus { connected: boolean; batteryLevel?: number; // 0-100 storageLeftMinutes?: number; } export const useDictaphoneStatus = (): DictaphoneStatus => { // For now, return dummy status: // connected: true, batteryLevel: 80, storageLeftMinutes: 120 };
Later this can be hooked to a BLE bridge or Web Bluetooth.
🟦 5.2 useOfflineFlag
Create /src/hooks/useOfflineFlag.ts:
export const useOfflineFlag = (): boolean => { const [offline, setOffline] = useState(!navigator.onLine); useEffect(() => { const handleOnline = () => setOffline(false); const handleOffline = () => setOffline(true); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []); return offline; };
This will drive the offline banner.
🟦 6. UI SPEC — E05 RECORDING PAGE
File: /src/pages/E05_Recording.tsx
This page must visually match the HD mockup:
- Patient section at top
- Dictaphone pill
- Blue waveform
- Large timer
- “Marquer” button
- Big red Stop button
- Clinical reminders card
- Offline banner
All labels in French.
6.1 Patient header
At top of main white card:
- Left: circular avatar (image or placeholder).
-
Right:
- Bernard Durand (or patientName from route state/query)
- Smaller line: 81 ans · Né le 8 mars 1942 (or generic text if not provided).
You can get patient info from location.state (React Router) or search params:
const location = useLocation(); const state = location.state as { patientName?: string; birthDate?: string } | undefined;
Fall back to “Enregistrement libre” if no patient.
6.2 Dictaphone status pill
Top-right of the header card:
-
If connected === true:
-
Green/teal rounded pill:
- Title: Enregistré (or Connecté)
- Subtext: XX% de batterie from batteryLevel.
-
Green/teal rounded pill:
-
If not connected:
- Grey pill: Dictaphone non connecté.
Use const dictaphone = useDictaphoneStatus();.
6.3 Waveform area
Below header:
- A wide waveform (blue waves).
Implementation idea:
- Create a <div> with display: flex, containing several child <div> bars whose heights animate with CSS keyframes.
- Or a simple SVG path.
It is fake/visual for now, not tied to real audio amplitude.
6.4 Timer
Center a large timer below the waveform:
- Format: MM:SS or HH:MM:SS.
- Compute from elapsedMs.
- Helper:
const formatElapsed = (ms: number) => { /* returns "00:02:16" */ };
6.5 Buttons
Arrange vertically:
-
“Marquer” button:
- White pill-style button.
- On click: addMarker() (append to markers state).
- Optionally show a toast or alert: “Marqueur ajouté”.
-
Stop button
- Circular red button with white square icon.
- Label text below: Arrêter.
-
On click: handleStop():
const handleStop = async () => { if (!recorderRef.current) return; const blob = await recorderRef.current.stop(); setIsRecording(false); const id = await enqueueUpload(blob, { markers, patientId, patientName, }); navigate(`/recordings/processing/${id}`); };
Handle exceptions with a simple console.error and/or alert("Erreur lors de l'arrêt de l'enregistrement").
6.6 Offline banner
If useOfflineFlag() returns true:
-
Display a banner at top of page or top of card:
- Background: yellow/orange.
- Text: Pas de réseau – la synchronisation sera faite plus tard.
This must NOT prevent recording.
6.7 Clinical reminders card
At bottom of card:
-
Small card with:
- Title: Pensez à mentionner :
-
List:
- • Le type de soin
- • Niveau de douleur (0–10), tension, incidents
Style:
- White background
- Rounded corners
- Light border or shadow
- Padding
🟦 7. LIFECYCLE BEHAVIOR
On component mount:
- Immediately start recording if not already:
useEffect(() => { const start = async () => { const handle = await startWebRecording(); recorderRef.current = handle; setIsRecording(true); setStartedAt(Date.now()); }; start(); }, []);
On unmount:
-
If isRecording is still true and user navigates away unexpectedly:
- Optionally stop the recording and discard or queue it.
- At minimum, ensure recorderRef.current.stop() is called in a cleanup with try/catch.
Always clear timer interval in useEffect cleanup.
🟦 8. STYLING & RESPONSIVENESS
- Use mobile-first CSS: page must work nicely on phones and desktop.
- Main card centered with max-width: 480–600px.
- Use CSS modules (e.g. E05_Recording.module.css) or styled-components.
Key styling goals:
- Clean, minimal, clinical.
- Large hit areas for buttons.
- Timer highly readable.
- Waveform visually appealing but simple.
🟦 9. FILES SUMMARY
You MUST create / update:
-
/src/pages/E05_Recording.tsx
-
Screen component with:
- Timer
- Waveform
- Patient header
- Dictaphone pill
- Marquer + Stop buttons
- Clinical reminders
- Offline banner
- Hooks into webAudio + enqueueUpload.
-
Screen component with:
-
/src/services/recording/webAudio.ts
- Minimal MediaRecorder wrapper with startWebRecording() returning WebRecorderHandle.
-
/src/services/n8n/audio_upload.ts
- enqueueUpload(blob, metadata) stub that returns a recordingId.
-
/src/hooks/useDictaphoneStatus.ts
- Returns dummy { connected: true, batteryLevel: 80 } for now.
-
/src/hooks/useOfflineFlag.ts
- Listens to window online / offline events.
-
/src/App.tsx
- Add route /recordings/live → <MainLayout><E05_Recording /></MainLayout>.
- Add route /recordings/processing/:recordingId → simple placeholder E06 page.
Do NOT modify:
- Auth flow,
- MainLayout bottom tabs (Patients / Enregistrements / Paramètres),
- Existing E01/E02 behavior.
🟦 10. AFTER IMPLEMENTATION
Expected behavior:
-
Navigating to /recordings/live:
- Starts recording using the browser microphone.
- Shows patient info (if provided).
- Displays waveform + running timer.
- “Marquer” adds markers in memory.
- “Arrêter” stops recording, calls enqueueUpload, navigates to /recordings/processing/:recordingId.
-
If network is offline:
- Offline banner visible.
- Recording still functional.
- Upload service can queue or be stubbed (but API is in place).
After everything is implemented, STOP.
END OF PROMPT — E05 RECORDING (WEBAPP)
Vous avez dit :
2- Version CLAUDE
2.1 version APP
Voici un prompt long & complet pour Claude 4.5, version App mobile (React Native + Expo), pour l’écran E05 – “Enregistrement en cours (connecté au dictaphone)”.
Tu peux le copier-coller tel quel dans Claude 👇
You are Claude, an expert mobile engineer specialized in **React Native, Expo, TypeScript and React Navigation**. Your task is to implement **Screen E05 – “Enregistrement en cours (connecté au dictaphone)”** of the **Synergia / Plode Care mobile app**, using **React Native + Expo + TypeScript**. This screen is the **live recording screen during a visit**, connected to a dictaphone (BLE in the future). For this prompt, you will focus on **UI, navigation, and local state** (timer, markers, offline banner). You will NOT implement real microphone or BLE logic; they will be stubbed. The prompt is intentionally detailed. Please follow all constraints carefully. ====================================================================== 0. CONTEXT: APP ARCHITECTURE & FLOW ====================================================================== The app is built with: - React Native + Expo - TypeScript - React Navigation: - Bottom Tab Navigator with 3 fixed tabs: - **Patients** - **Enregistrements** - **Paramètres** - Stacks nested under each tab. - Auth flow: - E01 (Onboarding & Auth) → after login, user enters MainTabs. - Dashboard: - E02 Dashboard is accessible after login. Screen E05 – “Enregistrement en cours”: - Belongs to the **Recordings / Enregistrements** area. - Is typically reached when the user starts a recording: - from E02 Dashboard (free recording, not tied to a patient), or - from a patient screen (recording tied to a specific patient). After the user presses **Stop**, E05 must: - Stop the “logical” recording, - Trigger a placeholder “upload / IA processing”, - Navigate to **E06 – Upload / Processing** screen. E05 must **not** modify the 3-tab bottom navigation; it should appear inside the existing navigation structure. ====================================================================== 1. TECHNOLOGY & PROJECT STRUCTURE ====================================================================== Use: - React Native + Expo - TypeScript - React Navigation: - `@react-navigation/native` - `@react-navigation/native-stack` - `@react-navigation/bottom-tabs` Basic structure (for reference): - `App.tsx` - `src/navigation/` - `AppNavigator.tsx` → NavigationContainer + decide auth vs main tabs - `MainTabs.tsx` → Bottom tab navigator (Patients / Enregistrements / Paramètres) - `RecordingsStack.tsx` → Stack for recordings under Enregistrements tab - `src/screens/` - `E02_Dashboard.tsx` - `E05_Recording.tsx` ← this screen - `E06_UploadProcessing.tsx` (placeholder) - `src/hooks/` - `useOfflineFlag.ts` (optional, can be implemented in this prompt) - `useDictaphoneStatus.ts` (optional, dummy BLE status) - `src/store/` - (optional) recording store, but local state is acceptable for this prompt. You can adapt slightly, but keep structure clear and idiomatic. ====================================================================== 2. NAVIGATION: WHERE E05 LIVES ====================================================================== Use React Navigation with: 1. **Bottom Tab Navigator** (`MainTabs`): - Tab 1: Patients - Tab 2: Enregistrements - Tab 3: Paramètres 2. **Recordings Stack** under the “Enregistrements” tab: - Initial route: `RecordingsListScreen` (placeholder). - Route `E05_Recording` → this screen. - Route `E06_UploadProcessing` → placeholder. Type definitions (example): ```ts export type RecordingsStackParamList = { RecordingsList: undefined; E05_Recording: { patientId?: number; patientName?: string; patientBirthDate?: string; // ISO string avatarUrl?: string; }; E06_UploadProcessing: { recordingId: string; }; };
E05 should use useRoute() / useNavigation() typed with this RecordingsStackParamList.
Flows:
-
From E02 or a patient screen:
navigation.navigate('EnregistrementsTab', { screen: 'E05_Recording', params: { patientId, patientName, patientBirthDate, avatarUrl }, });
-
From E05 Stop button:
navigation.navigate('E06_UploadProcessing', { recordingId });
You may stub RecordingsListScreen and E06_UploadProcessing with minimal placeholder components.
======================================================================
3. FUNCTIONAL SPECIFICATIONS – SCREEN E05
E05 is a live recording UI with:
- Patient info (header).
- Dictaphone status (connected, battery).
- Visual waveform.
- Timer counting up.
- “Marquer” button (add marker).
- Red “Arrêter” button.
- Clinical reminders panel.
- Offline mode banner.
Labels remain in French.
You will NOT integrate real microphone APIs or BLE; just simulate recording state.
3.1 PATIENT HEADER
At the top of the card:
-
Left: patient avatar:
- If avatarUrl provided → show image.
- Else → show placeholder circle with initials or generic silhouette.
-
Right: patient identity:
-
Line 1 (bold, large): patientName
- Example: Bernard Durand.
- If no patientName (free recording) → show “Enregistrement libre”.
-
Line 2 (smaller, muted):
- Example: 81 ans · Né le 8 mars 1942.
-
You may:
- either compute age from patientBirthDate, OR
- simply display Né le <date> if calculation is too verbose.
- If no birth date and free recording, hide this line or show “Sans patient associé”.
-
Line 1 (bold, large): patientName
Layout:
- Use a horizontal flex row inside a white card with rounded corners and mild shadow.
- Avatar on the left, text on the right.
- Align vertically centered.
3.2 DICTAPHONE STATUS PILL
On the same header card, aligned to the right/top (or right under the name):
- A small rounded status pill.
Create a hook (dummy) to represent dictaphone status:
interface DictaphoneStatus { connected: boolean; batteryLevel?: number; // 0–100 } const useDictaphoneStatus = (): DictaphoneStatus => { // For now, hardcode: // { connected: true, batteryLevel: 80 } };
Use it in E05:
-
If connected === true:
- Green / teal pill (background light green).
- Text: Enregistré or Connecté.
- Subtext (small): - 80% de batterie (use actual batteryLevel).
-
If connected === false:
- Grey pill.
- Text: Dictaphone non connecté.
This is purely UI for now; BLE integration will be done later.
3.3 WAVEFORM / VU METER
Below the header:
- Show a blue waveform similar to the mockup.
Implementation (simple):
- Use a View with horizontal flex containing multiple small vertical bars (rectangles) whose heights animate over time using Animated.
- Alternatively, use 1–2 Animated.View with sinusoidal height changes.
Key points:
- Visual only (not linked to real audio).
- Should loop continuously when isRecording === true.
- If isRecording === false, you can freeze the animation or reduce amplitude.
You may create a small reusable component, e.g. Waveform inside the same file for clarity.
3.4 TIMER (CHRONO)
Centered below the waveform:
-
Large text showing elapsed recording time, e.g.:
00:02:16
Behavior:
-
When E05 is mounted:
- Start “logical” recording if not already started.
- Store startedAt = Date.now() in local state.
- Use setInterval (1000 ms) in a useEffect to update elapsedMs.
State:
const [isRecording, setIsRecording] = useState(true); const [startedAt, setStartedAt] = useState<number | null>(Date.now()); const [elapsedMs, setElapsedMs] = useState(0);
Effect:
useEffect(() => { if (!isRecording || startedAt == null) return; const interval = setInterval(() => { setElapsedMs(Date.now() - startedAt); }, 1000); return () => clearInterval(interval); }, [isRecording, startedAt]);
Formatting helper:
const formatElapsed = (ms: number) => { const totalSeconds = Math.floor(ms / 1000); const seconds = totalSeconds % 60; const minutes = Math.floor(totalSeconds / 60) % 60; const hours = Math.floor(totalSeconds / 3600); if (hours > 0) return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; return `${pad(minutes)}:${pad(seconds)}`; };
Use formatElapsed(elapsedMs) as timer display.
3.5 MARKERS (“MARQUER” BUTTON)
Add a “Marquer” button below the timer.
-
Style:
- White pill button,
- Rounded corners,
- Light border,
- Centered label: Marquer.
State:
interface Marker { id: string; timestampMs: number; } const [markers, setMarkers] = useState<Marker[]>([]);
On press:
- Append a new marker:
const handleAddMarker = () => { setMarkers(prev => [ ...prev, { id: String(Date.now()), timestampMs: elapsedMs }, ]); };
Optionally:
- Show a short feedback (e.g. Alert.alert('Marqueur ajouté') or small text label fade-in).
- Do not navigate away.
You do NOT need to render the markers list in this screen, but you may log them to console or keep for upload.
3.6 STOP BUTTON (“ARRÊTER”)
Below the “Marquer” button:
-
Big red circular button:
- Red background,
- White square icon or stop symbol inside.
- Below the circle: label text “Arrêter”.
On press:
- Set isRecording to false.
- Generate a recordingId (e.g. UUID or timestamp string).
- Call a stub upload function.
- Navigate to E06 Upload / Processing, passing recordingId.
Pseudo-code:
const handleStop = async () => { try { setIsRecording(false); const id = `rec_${Date.now()}`; await enqueueUpload(id, { markers, patientId: route.params?.patientId, patientName: route.params?.patientName, durationMs: elapsedMs, }); navigation.navigate('E06_UploadProcessing', { recordingId: id }); } catch (e) { console.error(e); Alert.alert('Erreur', "Impossible d'arrêter l'enregistrement."); } };
For this prompt:
- Implement enqueueUpload as a simple async function stub with setTimeout and comments (no real network).
3.7 CLINICAL REMINDERS PANEL
At the bottom of the card:
-
A white panel (smaller card) with:
- Title: “Pensez à mentionner :”
-
Two bullet items:
- • Le type de soin
- • Niveau de douleur (0–10), tension, incidents
Layout:
- White background,
- Rounded corners,
- Light border or shadow,
- Padding,
- Margin top from buttons.
3.8 OFFLINE BANNER (MODE SANS RÉSEAU)
If the device is offline, show a banner at the top of the screen (above the card) or top of the card.
Create a small hook useOfflineFlag (or simple NetInfo usage):
import NetInfo from '@react-native-community/netinfo'; const useOfflineFlag = (): boolean => { const [offline, setOffline] = useState(false); useEffect(() => { const unsubscribe = NetInfo.addEventListener(state => { setOffline(!state.isConnected); }); return () => unsubscribe(); }, []); return offline; };
In E05:
- const isOffline = useOfflineFlag();
If isOffline === true:
-
Render a banner:
- Background: yellow/orange,
- Text: Pas de réseau – la synchronisation sera faite plus tard.,
- Optional warning icon.
Behavior:
- Does not block recording or navigation.
- Just informs the user that upload will be deferred.
======================================================================
4. STATE MANAGEMENT SUMMARY
For this prompt, you can keep all state local to E05 (no global store required):
- isRecording, startedAt, elapsedMs
- markers
- recordingId (temporary before navigation)
- dictaphone status via useDictaphoneStatus() (dummy values)
- offline flag via useOfflineFlag()
Later, this can be refactored to Zustand or context if necessary.
======================================================================
5. ACCESSIBILITY & UX
Ensure:
-
Buttons have accessible labels:
- e.g. accessibilityLabel="Arrêter l'enregistrement".
-
Large tap areas for:
- “Marquer”
- Red “Arrêter” circle.
- Timer font is large and clear.
-
General design:
- Clean, calm, medical style (white, blue, red for danger).
- Spacing between components for good readability.
Testing:
-
Screen should look good on:
- Small devices (e.g. iPhone SE),
- Medium and large phones.
- If needed, wrap content in ScrollView with contentContainerStyle so it remains usable even with small screens.
======================================================================
6. FILES TO OUTPUT
Please output the following files (with TypeScript):
-
src/screens/E05_Recording.tsx
-
Full implementation of the recording screen:
- Uses useRoute / useNavigation.
- Reads optional patient info from route params.
- Manages local recording state, timer, markers.
-
Renders:
- Offline banner,
- Main card with patient header + dictaphone pill,
- Waveform,
- Timer,
- “Marquer” & “Arrêter” buttons,
- Clinical reminders panel.
- Calls stub enqueueUpload on stop and navigates to E06_UploadProcessing.
-
Full implementation of the recording screen:
-
src/hooks/useDictaphoneStatus.ts (or inline inside E05)
- Returns a dummy { connected: true, batteryLevel: 80 }.
-
src/hooks/useOfflineFlag.ts (if not already existing)
- Based on @react-native-community/netinfo or simple mock.
-
src/screens/E06_UploadProcessing.tsx (placeholder)
-
Simple screen showing recordingId from route params and text:
- “Traitement IA en cours…” etc.
-
Simple screen showing recordingId from route params and text:
-
Update src/navigation/RecordingsStack.tsx:
-
Declare routes:
- RecordingsList
- E05_Recording
- E06_UploadProcessing
-
Declare routes:
You may show each file in separate code blocks with headers, e.g.:
// src/screens/E05_Recording.tsx ... // src/hooks/useDictaphoneStatus.ts ... // src/hooks/useOfflineFlag.ts ... // src/screens/E06_UploadProcessing.tsx ... // src/navigation/RecordingsStack.tsx ...
======================================================================
7. NON-GOALS FOR THIS PROMPT
Do NOT implement:
- Real audio capture (no Expo AV / MediaRecorder integration required).
- Real BLE connection to dictaphone.
- Real upload or background sync logic.
- Integration with Odoo / n8n / Supabase.
These should be represented as stubs with clear TODO comments.
Focus on:
- UI & UX matching the mockup.
- Navigation wiring.
- Local, simulated recording state.
- Offline banner behavior.
- Clean, idiomatic React Native + TypeScript code.
======================================================================
8. AFTER COMPLETING THE IMPLEMENTATION
Once you have:
- Implemented E05_Recording with all UI and interactions,
- Added dummy dictaphone status and offline banner,
- Navigated correctly to E06_UploadProcessing on Stop,
- Wired the route into the RecordingsStack,
→ Stop. Do NOT implement additional features or screens until explicitly requested.
---