Refreshed the documentation of the README file in the project one last time.
This commit is contained in:
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.
@@ -2,16 +2,34 @@
|
|||||||
|
|
||||||
A sample app that implements an audio recording flow in SwiftUI: start, pause, resume, or discard a recording while a
|
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
|
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
|
sheet. On iOS, the flow also surfaces as a Live Activity, so the recording can be followed and controlled from the
|
||||||
state and timer, with buttons that pause, resume, send, or discard it without opening the app.
|
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 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
|  |  |  |  |
|
||||||
|
|
||||||
|
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
|
## Context
|
||||||
|
|
||||||
This sample app was created for a technical interview for a senior Mobile (iOS + Android) engineer role at
|
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
|
[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
|
## Requirements
|
||||||
|
|
||||||
@@ -24,8 +42,8 @@ healthcare professionals, based on [a given set of requirements](README.pdf).
|
|||||||
## Running the app
|
## 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
|
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
|
Apple Vision Pro — the same code base covers all three. On an iPhone, lock the device or leave the app to follow and
|
||||||
Activity: lock the device or leave the app to follow and control the flow from the Lock Screen or the Dynamic Island.
|
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
|
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
|
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
|
- **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.
|
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
|
- **Serialized service access** — separate task chains guarantee the capture and the reporting calls each reach their
|
||||||
matter how quickly the user taps.
|
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
|
- **Drift-free timing** — the recording time derives from a monotonic clock anchor instead of counted ticks, so
|
||||||
scheduling latency never accumulates.
|
scheduling latency never accumulates.
|
||||||
- **File-based audio hand-off** — the capture and the transcription exchange a file URL, never an in-memory copy of
|
- **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
|
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
|
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
|
returning to idle. While processing, the send button shows a progress indicator and is disabled, and the discard
|
||||||
and throws the recording away without publishing anything. The model owns every side effect of a transition (timer,
|
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
|
capture, processing), so the state machine works the same whether it is driven by the view's buttons or by the unit
|
||||||
tests.
|
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
|
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.
|
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
|
- **`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
|
Liquid Glass background, tinted by the color given at initialization — the feature colors the main button yellow,
|
||||||
presses, and dims while disabled. The label and its padding scale with Dynamic Type via `@ScaledMetric`.
|
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
|
### 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
|
locale ahead of its first transcription, emitting the started, cancelled, and failed downloads through an
|
||||||
`events` stream instead of throwing.
|
`events` stream instead of throwing.
|
||||||
- **`Reporting`** — reports the lifecycle of the recording flow — started, paused, resumed, processing, and ended —
|
- **`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:
|
The real backends used by the app are:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user