# 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. ## 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). - **`README.pdf`** — the original assignment requirements.