This is Void, a simple meditation timer built with SwiftUI and the Composable Architecture (TCA). This repository demonstrates how to structure a small-to-medium app using the composable architecture.
This is by no means a perfect codebase, but I hope open-sourcing it might help others learn to use these wonderful tools. Feel free to open up an issue with any questions or comments you may have. Have fun and enjoy! 🙇♂️
Oh, and download the app! — And review it if you like it!
- SwiftUI + TCA: We organize the app into features, each with its own
@Reducer
,State
,Action
, and SwiftUI views. - Dependency Injection: External services (e.g. HealthKit, Audio) are encapsulated as “clients” using TCA’s
DependencyKey
. - Feature Folders: Each major app section (like Onboarding, Settings, Timer, etc.) lives in its own folder under
Features
. - Reusable Components: Shared logic and helpers (e.g.
SoundManager
,HealthKitClient
) live inClients
andUtilities
.
Below is a quick tour of the major directories and files.
Each feature folder contains:
- A Reducer (
FeatureNameFeature.swift
) definingState
,Action
, and any side effects. - A View file (
FeatureNameView.swift
) binding to the reducer’s state.
Highlights:
OnboardingFeature.swift
Handles the initial onboarding flow and requests HealthKit permissions.SettingsFeature.swift
Manages user preferences (timer length, intervals, ambient sounds).MeditationTimerFeature.swift
The main meditation timer logic (start/pause/play, timer completion).StatsFeature.swift
Fetches daily and weekly meditation stats via HealthKit.
The app’s root reducer is HomeFeature.swift
. It ties all child features together (onboarding, settings, stats, meditation timer) and manages global app state.
Dependencies for external or system services are in Clients
. Each client is implemented as a TCA DependencyKey
:
HealthKitClient.swift
Reads and writes mindful sessions to HealthKit.SoundManager
andAmbientManager
Handle bell sounds, ambient loops, and audio session setup.
We store plain data models in the Models
folder:
VoidSettings.swift
: Holds user preferences (timer duration, ambient sound, etc.).MeditationState.swift
: Tracks the current meditation session.Quote.swift
: Simple struct for random quotes displayed on the home screen.
KeyboardView.swift
: A custom numeric keyboard for user input.MovingLogo.swift
&LogoView.swift
: Animated shape-based logos.PulseView.swift
: Overlay effect that shows pulsing visuals for transitions.Constants.swift
: Global animations and style constants.PreventDeviceSleep.swift
: Disables idle timer to keep the screen awake.
- App Launch
- The root
HomeFeature
checks if HealthKit is authorized. If not, it presents the onboarding flow (OnboardingFeature
).
- The root
- Meditation Flow
- The user sets their timer, intervals, and ambient sound in the Settings feature.
- Tapping Begin starts
MeditationTimerFeature
, which counts elapsed time and plays interval/finish bells. - When the user stops, the session is saved to HealthKit (if over 10 seconds).
- Stats & Quotes
- StatsFeature queries HealthKit daily to display total minutes meditated and keeps track of your daily streak.
- QuotesView shows random mindfulness quotes on the home screen.
- Composable Architecture: Each feature has:
- A
@Reducer
struct withState
,Action
, andbody
.
- A
- Dependency Clients:
- Wrapped in
@DependencyClient
, withliveValue
(real implementation) andtestValue
(fake or mock). - Each client is registered in
DependencyValues
for easy access in reducers.
- Wrapped in
- State Persistence:
- We use TCA’s
@Shared
storage for persisting state across app launches (e.g.VoidSettings
,MeditationState
).
- We use TCA’s
I'm more than happy to accept contributions, but I also want to keep this app simple and functional. So perhaps open up an issue before coding up too much 😜