Where it shows up
Business view
Four places in the app upload a file, and all four now show real progress. Each one wires up the same three-step pattern: mark an upload as starting, feed it byte reports as they arrive, mark it finished (or ended, if it failed). Nothing about what staff do on these screens changed — the file picker, the camera button, the record button all work exactly as before. Only what's shown while the phone talks to the server changed.
- Clocking in or out — the clock-in/out photo now shows progress while it
uploads, instead of a bare spinner that could sit still for a while on a slow connection.
- Adding a file to a patient's record — photos, documents, and screenshots
attached to a patient now show progress, including when a filename could collide with another upload in the same batch.
- Recording a note — a dictated voice note attached to a patient's chart now
shows progress once its (initially unknown) size becomes known.
- Sending a voice message in staff chat — the small send button shows a
percentage while the recording uploads, so staff can tell it's still working rather than frozen.
Technical view
All four screens follow the same shape: call useUploadProgress() once per screen, then call upload.start() before handleUploadFile, pass upload.report as its onProgress, call upload.complete() on success, and always call upload.end() in a finally block. The percent/detail are then handed to AppSpinner.
ClockScreen.js — clock-in/out photo
upload.start(name, fileSize) // ClockScreen.js:171
handleUploadFile({ onProgress: sizes => upload.report(name, sizes), ... })
upload.complete(name, fileSize) // ClockScreen.js:186
// finally: upload.end() // ClockScreen.js:208
<AppSpinner progress={upload.percent} detail={upload.detail} /> // ClockScreen.js:243
fileSize here is estimated from the captured photo's base64 payload (fileSize = imageSrc.base64.length * 4, unchanged by this PR) before the real byte count is known. Because report() lets a subsequent total from the platform override the initial estimate (useUploadProgress.js:40-43), the percentage self-corrects to the accurate figure once handleUploadFile's upload task starts reporting real totalBytesExpectedToSend — the estimate only matters for the very first render, before any bytes have moved.
NewFileScreen.js — patient files
const uploadId = `${name}-${uri}` // NewFileScreen.js:110
upload.start(uploadId, size) // NewFileScreen.js:112
handleUploadFile({ onProgress: sizes => upload.report(uploadId, sizes), ... })
upload.complete(uploadId, size) // NewFileScreen.js:143
// finally: upload.end() // NewFileScreen.js:200
<AppSpinner progress={upload.percent} detail={upload.detail} /> // NewFileScreen.js:265
The upload ID combines the file name and its device URI, not just the name, so two files that happen to share a name in the same batch (e.g. two screenshots both named by the OS) still track separately.
NewNoteScreen.js — patient voice notes
upload.start(name) // NewNoteScreen.js:153
handleUploadFile({ onProgress: sizes => upload.report(name, sizes), ... })
upload.complete(name) // NewNoteScreen.js:168
// finally: upload.end() // NewNoteScreen.js:217
{loadingNote ? <AppSpinner progress={upload.percent} detail={upload.detail} /> : null} // NewNoteScreen.js:326
No size is passed to start() — a recording's final size isn't known until it's made, so percent/detail stay empty until the first real progress report arrives. (This screen also renders a plain <AppSpinner /> for an unrelated loading state at NewNoteScreen.js:137, which was not changed by this PR.)
AudioRecordingButton.js — system chat voice messages
upload.start(name) // AudioRecordingButton.js:127
handleUploadFile({ onProgress: sizes => upload.report(name, sizes), fileSize: 1024 * 1024 * 20, ... })
upload.complete(name) // AudioRecordingButton.js:139
// finally: upload.end() // AudioRecordingButton.js:149
<AppSpinner size="small" progress={upload.percent} /> // AudioRecordingButton.js:188
This is the only one of the four that passes just progress, not detail — the spinner here renders inline inside a small send button (accessoryRight of the submit control), where there's no room for a second line. The fileSize: 1024 * 1024 * 20 (20 MB) passed to handleUploadFile is a pre-existing fallback estimate used to build the S3 signing request; it is only used as the progress total if the platform reports none of its own (handleUploadFile.js:145, 196).