Refreshed the documentation of the README file in the project one last time.

This commit is contained in:
2026-07-09 18:22:00 +02:00
parent 5c05eeb318
commit 14f91a08a9
8 changed files with 34 additions and 13 deletions
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 MiB

Binary file not shown.
+34 -13
View File
@@ -2,16 +2,34 @@
A sample app that implements an audio recording flow in SwiftUI: start, pause, resume, or discard a recording while a
timer tracks the elapsed time, then send the recording to be transcribed on device — and read the resulting text in a
sheet. On iOS, the flow also surfaces as a Live Activity: the Lock Screen and the Dynamic Island show the recording's
state and timer, with buttons that pause, resume, send, or discard it without opening the app.
sheet. On iOS, the flow also surfaces as a Live Activity, so the recording can be followed and controlled from the
Lock Screen and the Dynamic Island without opening the app.
A screen recording of the full flow is included in the repo: [`Attendi_Sample_App.MP4`](Attendi_Sample_App.MP4).
A screen recording of the end-to-end flow on iOS ([direct link](.README/Attendi_Demo.MP4)):
| Full recording flow on iOS |
| --- |
| <video src=".README/Attendi_Demo.MP4" controls width="320"></video> |
Live Activity interactions can't be captured in a screen recording, so these screenshots stand in — the permission prompt on first record, the active and paused Lock Screen banners, and the collapsed Dynamic Island:
| User permission | Microphone recording | Microphone paused | Dynamic Island |
| --- | --- | --- | --- |
| ![User permission](.README/Attendi_Demo_Live_activity_Allow.PNG) | ![Microphone recording](.README/Attendo_Demo_Live_Activity_Recording.PNG) | ![Microphone paused](.README/Attendo_Demo_Live_Activity_Paused.PNG) | ![Dynamic Island](.README/Attendi_Demo_Dynamic_Island.PNG) |
On iPhone, starting a recording also starts a Live Activity, so the flow can be followed and controlled without the app
in the foreground. On the Lock Screen it appears as a banner, and while the app is open it collapses into the Dynamic
Island. Both surfaces show the recording's current state and a timer that the system counts up on its own — no app
updates needed to keep it ticking — alongside buttons that pause, resume, send, or discard the recording. Tapping pause
or resume acts silently in place; send and discard bring the app forward to finish the flow. Each button routes back into
the same recording state machine as the in-app controls, so the Live Activity and the app never disagree, and the
activity ends the moment the recording does.
## Context
This sample app was created for a technical interview for a senior Mobile (iOS + Android) engineer role at
[Attendi](https://attendi.nl), a Dutch health-tech company that makes voice-driven reporting software for
healthcare professionals, based on [a given set of requirements](README.pdf).
healthcare professionals, based on [a given set of requirements](.README/Assignment.pdf).
## Requirements
@@ -24,8 +42,8 @@ healthcare professionals, based on [a given set of requirements](README.pdf).
## Running the app
Open `Attendi.xcodeproj` in Xcode, pick the `Attendi` scheme, and run it on an iOS device or simulator, a Mac, or an
Apple Vision Pro — the same code base covers all three. On an iPhone, starting a recording also starts a Live
Activity: lock the device or leave the app to follow and control the flow from the Lock Screen or the Dynamic Island.
Apple Vision Pro — the same code base covers all three. On an iPhone, lock the device or leave the app to follow and
control the recording from its Live Activity, as described above.
Two things to expect on a first run: the app asks for the microphone permission when the first recording starts, and
the first transcription in a language needs a network connection — picking a locale from the toolbar menu downloads
@@ -140,8 +158,8 @@ no-op on the other platforms. Everything else, including the Liquid Glass stylin
- **Model-owned side effects** — a button press and a unit test drive the exact same transition code, keeping the
views pure renderers and the state machine fully testable.
- **Serialized service access** — a task chain guarantees the capture calls reach the service in transition order, no
matter how quickly the user taps.
- **Serialized service access** — separate task chains guarantee the capture and the reporting calls each reach their
service in transition order, no matter how quickly the user taps.
- **Drift-free timing** — the recording time derives from a monotonic clock anchor instead of counted ticks, so
scheduling latency never accumulates.
- **File-based audio hand-off** — the capture and the transcription exchange a file URL, never an in-memory copy of
@@ -169,8 +187,9 @@ The flow is modeled as a state machine in `RecordingView.Model`, an `@Observable
The main button starts, pauses, and resumes a recording. While paused, two more buttons appear: discard throws the
recording away, and send moves the flow into processing — stopping the capture and transcribing the audio — before
returning to idle. The discard button stays available while processing, where it cancels the in-flight transcription
and throws the recording away without publishing anything. The model owns every side effect of a transition (timer,
returning to idle. While processing, the send button shows a progress indicator and is disabled, and the discard
button stays available — there it cancels the in-flight transcription and throws the recording away without
publishing anything. The model owns every side effect of a transition (timer,
capture, processing), so the state machine works the same whether it is driven by the view's buttons or by the unit
tests.
@@ -207,8 +226,9 @@ distinguished from an ordinary transcription failure. The underlying errors are
error alert. All controls carry localized accessibility labels matching their current action, and the timer
exposes its value to assistive technologies spelled out in full units.
- **`RecordingButtonStyle`** — a custom `ButtonStyle` used by the controls: the label sits on a padded, circular
Liquid Glass background tinted by the color given at initialization, red by default — that reacts fluidly to
presses, and dims while disabled. The label and its padding scale with Dynamic Type via `@ScaledMetric`.
Liquid Glass background, tinted by the color given at initialization — the feature colors the main button yellow,
discard red, and send green — that reacts fluidly to presses, and dims while disabled. The label and its padding
scale with Dynamic Type via `@ScaledMetric`.
### Services
@@ -224,7 +244,8 @@ attached to its view model:
locale ahead of its first transcription, emitting the started, cancelled, and failed downloads through an
`events` stream instead of throwing.
- **`Reporting`** — reports the lifecycle of the recording flow — started, paused, resumed, processing, and ended —
outside the feature's UI; the app implements it over `ActivityKit` to drive the Live Activity.
outside the feature's UI, plus a `reset` that clears any surface a previous run left behind; the app implements it
over `ActivityKit` to drive the Live Activity.
The real backends used by the app are: