Dentolize · Upload Progress Walkthrough
On this pageBusiness viewTechnical view

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).