Files
attendi/README.md
T

100 lines
4.0 KiB
Markdown
Raw Normal View History

# Attendi Sample App
A sample app that implements an audio recording flow in SwiftUI: start, pause, and resume a recording while a timer
tracks the elapsed time, then send the recording for (simulated) processing.
## 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).
## Demo
[▶️ Watch the sample (iOS) app in action!](Attendi_Sample_App.MP4)
## Requirements
| Requirement | Version |
| --- | --- |
| Xcode | 26 or later |
| Swift | 6.3 |
| Platforms | iOS/macOS/visionOS 26 |
## Project structure
The project separates the app shell from the feature code, which lives in a local Swift package:
```
Attendi/
├── Attendi.xcodeproj
├── Apps/
│ └── Attendi/ # The app target (thin shell)
│ ├── AttendiApp.swift # The @main entry point
│ ├── ContentView.swift # The root view, hosting the Recording feature
│ └── Assets.xcassets
└── Packages/
└── Features/ # Local Swift package with the feature code
├── Package.swift
├── Sources/
│ └── Recording/ # The Recording feature target
│ ├── RecordingView.swift
│ ├── RecordingViewModel.swift
│ ├── RecordingButtonStyle.swift
│ └── Images.xcassets # Record, pause, and send icons
└── Tests/
├── Recording/
│ └── RecordingViewModelTests.swift
└── Features.xctestplan
```
The app target only depends on the `Features` package and renders its public `RecordingView` view; all recording logic
and UI live in the package's `Recording` target.
## The Recording feature
### State machine
The flow is modeled as a state machine in `RecordingView.Model`, an `@Observable`, `@MainActor` view model:
| State | Meaning |
| --- | --- |
| `notRecording` | Idle; no recording in progress. |
| `recording` | A recording is in progress and the timer is ticking. |
| `paused` | The recording is paused; it can be resumed or sent. |
| `processing` | The sent recording is being processed. |
The main button starts, pauses, and resumes a recording. While paused, a send button appears; pressing it moves the
flow into processing (currently simulated with a delay) before returning to idle.
### Timer
While recording, an async task increments the elapsed seconds once per second — pausing stops it, resuming continues
it, and a new recording resets it. The view formats the count as `mm:ss` and animates digit changes with a numeric
text content transition.
### Views
- **`RecordingView`** — the public entry point of the feature. It renders the timer label and the control buttons for
the model's current state, and forwards state changes back to the model.
- **`RecordingButtonStyle`** — a custom `ButtonStyle` used by the controls: the label sits on a padded, circular red
background, shrinks while pressed, and dims while disabled. The label and its padding scale with Dynamic Type
via `@ScaledMetric`, and an `invertStyle` flag controls whether the label's color scheme is inverted for contrast.
## Testing
The view model is covered by Swift Testing suites in `RecordingViewModelTests.swift`, grouped with nested `@Suite`
types (initial state, button presses, computed properties, timer, and processing). The state-dependent behaviors are
exercised with parameterized tests across all four states.
Run the tests with the `Features` scheme in Xcode (`⌘U`), or from the command line:
```sh
xcodebuild test -scheme Features -destination 'platform=iOS'
```
## Tooling
- **`.swift-format`** — configuration for Apple's `swift-format`, used by Xcode's built-in formatter (4-space
indentation, 200-column lines).