know that you are breathing — implementation plan
status: historical. this is the original approved roadmap. nearly all of it has shipped (see
CHANGELOG.md), but it is not maintained as a status document — phase numbers, checklists, and "not yet implemented" notes inside may be stale. for what the code does today, readARCHITECTURE.mdand the other dev-guide docs; for user-facing behavior, readdocs/user-guide/. the phase 9 welcome tour has since shipped as a customPageView(see "welcome tour and in-app help" inARCHITECTURE.md); ADR 0007 ("watch as remote") remains an accepted future design that is not implemented.
A phased, dependency-first roadmap to re-brand the Flutter app, modernize its
packages, fix the listed bugs, and deliver all requested features in a sequence
that keeps flutter analyze, flutter test, and flutter build linux green at
each milestone. Once approved, this becomes the source of truth to update
docs/dev-guide/PROJECT_PLAN.md.
terminology note: this plan was written against the original vocabulary (
TimerSegment,NotificationProfile,EditorScreen,DndService, prompt lists, quote lists,keepScreenOn, background sounds, timer/counter modes). The current names are segment, reminder,MeditationEditorScreen,SilencePhoneService, lists, screen behavior, soundscapes, and the meditation / recitation / breathing modes — seedocs/dev-guide/terminology.md.
Summary
The plan splits the work into nine shippable phases. Phase 0 does the foundational re-brand, package modernization, and app icon generator so later phases build on a clean base. Phases 1 and 2 fix editor/UX bugs, add auto-save, and restructure settings around global defaults. Phases 3–8 add the major features in dependency order (lists, player experience, health/sessions, advanced segments, audio creation, guided breathing). Phase 9 is the welcome tour and release polish.
Decisions captured from clarifying questions
- Re-brand scope: full package restructure — update Android Kotlin package
directories, iOS/macOS bundle IDs, Linux/Windows metadata, and all
packaging/CI identifiers to
com.wesorick.john.knowthatyouarebreathing. - Package upgrades: major-version upgrade path with manual compatibility
fixes, but avoid freshly-released versions (e.g.
file_picker12.0.0, which was published hours ago). - Lists merge: unify
prompt_listsandquote_listsinto a single genericlistsconcept, so the same list can be used for meditation prompts or notification messages. - Defaults toggle: a single "use defaults" switch in the meditation editor. When on, the editor uses the global defaults and hides the corresponding options; when off, all per-meditation options are shown.
- Do Not Disturb: implement where the OS allows it (Android via notification-policy access). Hide the option on platforms where it is unsupported; prompt for the required permission when the user enables it.
- Haptics: use the
vibrationpackage for custom patterns and intensities on Android/iOS, withHapticFeedbackfallback. No haptics on desktop. - Guided breathing: begin with a research/design spike to decide how it relates to per-segment meditation types and visualizers before writing production code.
Dependencies and overall ordering
- Re-brand and package upgrades first. They touch build files, imports, generated files, and CI; doing them first avoids reworking later feature branches.
- Auto-save and defaults next. Auto-save changes how every editor works; the global-defaults system is needed by the meditation editor "use defaults" toggle and by player defaults.
- Settings restructure and lists unification. Settings groups determine where new global preferences live. Unifying lists is a schema refactor that should finish before multi-select and prompt/notification list usage.
- Player experience. Countdown, DND, screen off, haptics, and visualizer toggles all read the new global/per-meditation defaults.
- Health and session lifecycle. Incomplete-session prompting and deleting sessions both depend on the player/session stop flow.
- Advanced segments. Per-segment meditation types and segment repeat are deep model/player changes that should build on the already-refactored player.
- Audio creation. Binaural beats and recording are new sound sources; they depend on the sound-library and audio-service abstractions.
- Guided breathing. Depends on the segment/type model and the visualizer architecture, so it follows the research spike.
- Welcome tour and release polish. By definition last, and should re-use the settings screens it offers to enable.
Package modernization notes
Run flutter pub upgrade --major-versions in a branch, then apply the following
guardrails:
| package | current constraint | target | reason |
|---|---|---|---|
riverpod |
^3.3.2 |
^3.3.2 |
3.4.2 requires riverpod_annotation 4.0.6, which requires riverpod_generator 4.0.8 and freezed 4.0.0-dev.3 |
flutter_riverpod |
^3.0.0 |
^3.3.2 |
align with riverpod |
riverpod_annotation |
^4.0.0 |
^4.0.3 |
4.0.6 requires riverpod 3.4.2, which would force freezed 4.0.0-dev.3 |
riverpod_generator |
^4.0.4 |
^4.0.4 |
4.0.8 requires analyzer 13.x, which requires freezed 4.0.0-dev.3 |
freezed |
^3.2.6-dev.1 |
^3.2.6-dev.1 |
3.2.5 cannot be used with build_runner 2.16.0 / drift_dev 2.34.5; 4.0.0-dev.3 is a major pre-release, so the dev release is held |
freezed_annotation |
^3.1.0 |
^3.1.0 |
already current stable |
build_runner |
^2.15.1 |
^2.15.1 |
2.16.0 requires analyzer 13.x, which requires freezed 4.0.0-dev.3 |
drift_dev |
^2.34.0 |
^2.34.0 |
2.34.5 requires analyzer 13.x, which requires freezed 4.0.0-dev.3 |
drift |
^2.34.3 |
^2.34.3 |
latest stable |
go_router |
^17.3.0 |
^17.4.0 |
17.5.0 is only ~2 days old |
permission_handler |
^11.3.1 |
^11.4.0 |
13.0.0 requires permission_handler_android 14.0.0, which targets Android SDK 37 |
share_plus |
^11.0.0 |
^12.0.2 |
13.x requires win32 6.x, which conflicts with the file_picker/wakelock_plus versions we can use |
package_info_plus |
^9.0.1 |
^9.0.1 |
10.x requires win32 6.x, which conflicts with wakelock_plus 1.5.2 |
wakelock_plus |
^1.5.2 |
^1.5.2 |
1.6.0+ requires win32 6.x, which conflicts with the file_picker/share_plus versions we can use |
flutter_local_notifications |
^22.2.0 |
^22.2.0 |
22.3.0 is too new |
flutter_secure_storage |
^10.3.1 |
^10.3.1 |
11.0.0 is too new and has breaking cipher changes |
file_picker |
^8.0.7 |
^8.3.7 |
11.0.3 breaks the Wear build on AGP 9; 12.0.0 is <1 day old and reworks the API |
health |
^13.2.0 |
^13.3.1 |
latest stable |
emoji_picker_flutter |
'4.4.0' pinned |
^4.5.3 |
stop pinning, current is fine |
flutter_soloud |
^4.1.6 |
^4.1.7 |
patch |
sqlite3 |
^3.5.0 |
^3.5.1 |
patch |
uuid |
^4.4.2 |
^4.6.0 |
patch |
intl |
^0.20.2 |
^0.20.3 |
patch |
New packages to add for feature work:
vibration: ^3.2.0— haptic patterns.do_not_disturb: ^1.0.3— Android DND toggle.record: ^7.1.1— sound recorder.introduction_screen: ^4.0.0(or a custom equivalent) — welcome tour.image: ^4.9.1+args: ^2.7.0— dev dependencies for the icon/logo generator.
After editing pubspec.yaml:
flutter pub upgrade
dart run build_runner build
flutter gen-l10n
flutter analyze
flutter test
flutter build linux
flutter build apk --flavor wear --release -t lib/main_wear.dart
Fix any API breakages before moving to the next phase.
Phase 0 — Re-brand, app icon generator, and package upgrade
Phase 0 — Goal
Finish the name, package ID, and asset refresh so the rest of the plan is not built on top of stale identifiers.
Phase 0 — Implementation steps
- Rename the app to "know that you are breathing"
lib/l10n/app_en.arb: changeappTitletoknow that you are breathing(lowercase, per the existing English convention).- Regenerate
lib/l10n/generated/app_localizations*.dartwithflutter gen-l10n. lib/app.dart,lib/main.dart,lib/features/wear/wear_app.dart,lib/core/widgets/about_tile.dart: update references toappTitleonly; no hard-coded strings.README.md,CONTRIBUTING.md,CHANGELOG.md,LICENSEcopyright line,docs/dev-guide/PROJECT_PLAN.md,docs/dev-guide/ARCHITECTURE.md,docs/dev-guide/design-system.md,docs/user-guide/*.md.linux/runner/my_application.cc,linux/packaging/*.desktop,linux/packaging/*.metainfo.xml: updateName=and title strings.windows/runner/Runner.rc: updateFileDescription,ProductName, etc.ios/Runner/Info.plist:CFBundleDisplayNametoKnow That You Are BreathingandCFBundleNametoknow_that_you_are_breathing.macos/Runner/Info.plistandmacos/Runner/Configs/AppInfo.xcconfig:PRODUCT_NAMEtoknow_that_you_are_breathing.lib/data/database/app_database.dart: update the database directory fromknow_that_you_are_breathingtoknow_that_you_are_breathing.lib/data/services/backup_sync/backup_mapper.dart: update backup bundle directory name.pubspec.yaml: change the Dart packagenamefromknow_that_you_are_breathingtoknow_that_you_are_breathing.- Run a project-wide find/replace on
package:know_that_you_are_breathing/imports andpackage:know_that_you_are_breathing/. Update all Dart files and tests. -
.gitlab-ci.yml: update artifact/package names. -
Application ID and directory-name convention
- Use the app ID
com.wesorick.john.knowthatyouarebreathingonly where the OS convention is reverse-DNS package identification:android/app/build.gradle.kts:namespaceandapplicationId.- Android Kotlin package path under
android/app/src/main/kotlin/. Movecom/wesorick/john/knowthatyouarebreathing/tocom/wesorick/john/knowthatyouarebreathing/. Updatepackagedeclarations inMainActivity.kt,DataLayerPlugin.kt,DataLayerCache.kt,DataLayerListenerService.kt,HealthConnectPlugin.kt, andHealthConnectService.kt. - iOS/macOS
PRODUCT_BUNDLE_IDENTIFIER.
- On desktops and in source/packaging directory names, use the app name
instead of the app ID:
- Rename the project/checkout directory to
know that you are breathing. linux/CMakeLists.txt:BINARY_NAME=know_that_you_are_breathing. SetAPPLICATION_IDtoknow-that-you-are-breathingso that path_provider and GTK use a human-readable app name for data directories/window class (verify the window-class impact on first desktop build; if needed keep a reverse-DNS WM class and just set the user-visible names).linux/packaging/*.desktop:Name=Know That You Are Breathing,Icon=know-that-you-are-breathing,Exec=know_that_you_are_breathing, and rename the file toknow-that-you-are-breathing.desktop.linux/packaging/*/*.metainfo.xml: AppStream<id>can stay reverse-DNS, but visible name tags useknow that you are breathing.windows/runner/CMakeLists.txt:BINARY_NAME=know_that_you_are_breathing.windows/runner/Runner.rc:FileDescriptionandProductName=Know That You Are Breathing;CompanyName= the author/publisher name (e.g.John Wesorick), not the app ID.macos/Runner/Configs/AppInfo.xcconfigandInfo.plist:PRODUCT_BUNDLE_IDENTIFIER=com.wesorick.john.knowthatyouarebreathing;PRODUCT_NAME/CFBundleDisplayName=Know That You Are Breathing.lib/data/database/app_database.dart: database directory name =know_that_you_are_breathing(app name, not app ID).lib/data/services/backup_sync/backup_mapper.dart: backup bundle directory name =know_that_you_are_breathing.
- Rename the project/checkout directory to
- Packaging metadata:
snap/snapcraft.yaml:name=know-that-you-are-breathing(already is); updatetitle, description, andcommandto the new app name.flatpak/com.wesorick.john.knowthatyouarebreathing.yml: rename file and updateapp-idtocom.wesorick.john.knowthatyouarebreathing(flatpak still uses a reverse-DNS package identifier). Update desktop references.linux/packaging/rpm/build-rpm.sh,.gitlab-ci.yml: artifact/package file names useknow-that-you-are-breathing.
-
tool/generate_android_keystore.sh: update keystore alias to the new app ID. -
App icon/logo and generator
- Add
imageandargstodev_dependencies. - Create
tool/generate.dartadapted from/home/deck/Nextcloud/documents/apps/pyggo/tool/generate.dart, supporting at least:dart tool/generate.dart app <icon.png>to generate launcher icons.dart tool/generate.dart logo <logo.png>to generate brand assets.
- Inputs:
/home/deck/Downloads/know that you are breathing icon.pngand/home/deck/Downloads/know that you are breathing logo.png. - Outputs:
assets/images/app_icon.png(replaces current placeholder).assets/brand/app_logo.pngandassets/brand/android12_splash.png.android/app/src/main/res/mipmap-*/ic_launcher.png.ios/Runner/Assets.xcassets/AppIcon.appiconset/*.macos/Runner/Assets.xcassets/AppIcon.appiconset/*.windows/runner/resources/app_icon.ico.linux/runner/resources/know_that_you_are_breathing.png.docs/assets/know_that_you_are_breathing_logo_200.png.
- Update
pubspec.yamlassets section and any platform manifest references to the new icon/logos. -
Add a short
docs/dev-guide/icon-logo-generator.mddocumenting usage. -
Package modernization
- Edit
pubspec.yamlwith the target versions in the table above. - Run
flutter pub upgradeand resolve any breaking changes. - Run
dart run build_runner build,flutter gen-l10n,flutter analyze,flutter test,flutter build linux, and the Wear release build.
Phase 0 — Verification
- [x]
flutter analyzeis clean. - [x]
flutter testpasses. - [x]
flutter build linuxsucceeds. - [x]
flutter build apk --flavor wear --release -t lib/main_wear.dartsucceeds. - [x] The app displays the new name and icon on desktop and Android.
- [x] A fresh install reports the new bundle ID / package name.
Phase 1 — Editor UX bugs, auto-save, and navigation cleanup
Phase 1 — Goal
Fix the immediate editor bugs, introduce auto-save everywhere, and clean up the meditation library navigation before adding defaults.
Phase 1 — Implementation steps
- Auto-save pattern
- Create a reusable
AutoSaveDraft<T>helper orDebouncedSaverthat persists after a short idle period (e.g. 1.5 s). - Apply to:
EditorScreen(meditation)PromptLibraryScreen/ list editors (once unified)QuoteListEditorScreen/NotificationProfileEditorScreen
- Keep a "cancel" action. When the user cancels, restore the original draft from the repository.
-
Add
WillPopScope/PopScopebehavior: if there are unsaved local changes, prompt to save or discard. -
Fix single prompt space bug
- In
lib/features/editor/editor_screen.dart, the prompt and segment labelTextEditingControllers are recreated each build. Move them toState(cache keyed by segment index) and dispose indispose(). - Remove
.trim()from the promptonChangedcall so spaces are preserved. -
Update widget/golden tests and add a test that a space can be entered.
-
Segment "name" field
- Rename the ARB key and UI label from "label" to "name".
- Replace the read-only
segment xheader with a tappable/editableTextFieldinline at the top of each segment card. -
Keep the default placeholder as "segment {number}" but allow the user to type any name.
-
No total wheel for a single segment
-
In
EditorScreen._buildSegmentsSectionand any player summary that usesDurationPicker, hide the total-duration wheel whenmeditation.segments.length == 1. -
Reorder segments
- Add a reorder icon button on the editor segments section.
- Open a dialog with a
ReorderableListViewshowing segment names. - On drag completion, rewrite
orderIndexvalues and rebuild_draft.segments. -
Add unit and widget tests for the reorder flow.
-
Rename library to "meditations"
- Update ARB strings:
meditationLibraryTitle,navigationLibraryLabel,navigationLibraryTooltiptomeditations. - Update
AppNavDestination.libraryand the route path. ChangeAppRoutes.libraryto/meditationsand updateapp_router.dart. -
Change the navigation icon from
Icons.library_bookstoIcons.self_improvement(a meditator) inlib/core/routing/app_navigation_shell.dart. -
Display a snackbar when sync is running on Backup & Sync
- In
lib/features/backup_sync/backup_sync_screen.dart, listen to the backup/sync provider (backupSyncServiceProvider) withref.listen. - Detect any sync component (
backup,localSync,webDAVSync,dataLayerSync) enteringBackupSyncStatus.inProgress. - On first build, if a sync is already in progress, show a
SnackBarimmediately explaining that a sync is in progress and the controls are disabled. - When a new sync starts while the screen is visible, pop up the same snackbar.
- Guard against duplicate snackbars (e.g. a single visible snackbar that
updates, or a
_hasShownSyncSnackbarguard). - Add a widget test in
test/features/backup_sync/backup_sync_screen_test.dartcovering "already in progress" and "starts while visible".
Phase 1 — Verification
- [x] Space can be typed in a segment prompt and segment name.
- [x] Saving a meditation with a single segment does not show a redundant total wheel.
- [x] Segments can be reordered and the new order persists.
- [x] Auto-save persists edits after a short idle pause; cancel reverts them.
- [x] The bottom tab is labeled "meditations" and uses the meditator icon.
Phase 2 — Settings restructure and global defaults
Phase 2 — Goal
Move global preferences into grouped sub-screens and build the "use defaults" system so the meditation editor can hide options that fall back to those defaults.
Phase 2 — Implementation steps
- New routes and sub-screens
- Add
AppRoutesandGoRouteentries under/settings:/settings/look_and_feel/settings/meditation_defaults/settings/notifications/settings/library/settings/insights/settings/health_sync/settings/backup_sync(already exists)
-
Create one screen per group.
SettingsScreenbecomes a landing list with tiles; each tile pushes the sub-screen. -
Group the existing settings per the requested order
- Look & feel: theme, accent color, appearance (font scale, reduce motion, compact density, amoled dark), recitation input, TTS voice.
- Meditation defaults: session prompts, mood presets, count direction (and later haptics/screen off/countdown/DND defaults as those features land).
- Notifications: move the notification profiles list here from the bottom
navigation. Remove the
notificationstab fromAppNavigationShell. - Library: sounds, binaural beats, lists (links/screens that will be built in later phases).
- Insights: completed-only streaks toggle.
- Health sync: existing
HealthSyncSection. -
Backup & sync: existing
BackupSyncScreen. -
Move TTS voice to settings and support per-meditation override
- Move the voice picker from
EditorScreento the look-and-feel sub-screen. - Save the global default under
SettingsRepository(_ttsVoiceKey). - Add
ttsVoiceanduseDefaultTtsVoicefields toMeditationandMeditationsTable. - In the editor, when "use defaults" is on, hide the voice picker and use the global default.
-
Update
MeditationPlayerScreen,RecitationPlayerScreen, andWearPlayerScreento resolvemeditation.ttsVoicefirst, then global default. -
Rename "health sync override" to "health sync" and add a "none" option
- Rename the ARB string
healthSyncOverrideTitletohealthSyncTitleand updateEditorScreen. - Add a
HealthSyncTarget.none(or a sentinel value). When selected, the player skips writing a health record for this meditation (useful for sleep meditations). -
Update
HealthServiceimplementations and the resolve logic inmeditation_player_screen.dart,recitation_player_screen.dart,wear_player_screen.dart, andhealth_relay_service.dart. -
"Use defaults" toggle in the meditation editor
- Add
useDefaultstoMeditation/MeditationsTable(defaulttrue). - Add a switch at the top of
EditorScreen. - When on, hide the following sections that have global defaults: session options, mood presets, count direction, TTS voice, health sync.
- When off, show all options and save them on the meditation.
-
Ensure the player resolves the right value: per-meditation if
useDefaultsis false, otherwise the matching global default. -
Global defaults repository/provider
- Introduce a
MeditationDefaultsmodel grouping:SessionOptions,MoodPresets,CountDirection,TtsVoice,HealthSyncTarget, and placeholders forHapticSettings,ScreenOff,Countdown, andSilencePhone(these fields are populated as their respective features land in Phase 4). - Add
readMeditationDefaults()/saveMeditationDefaults()inSettingsRepositoryand a provider. -
Use this model in the "meditation defaults" settings screen.
-
Parse and clean up the TTS voices list
- Add a
TtsVoiceDisplaymodel and attsVoiceDisplaysProviderthat wraps the rawttsVoicesProviderfromlib/data/services/tts_service_providers.dart. - Create a
TtsVoiceLabelServicethat turns raw platform voice maps into human-readable labels:- Parse the BCP-47
localestring into language and region codes. - Map language codes (e.g.
en,es,de) and region codes (e.g.US,GB,MX) to English display names. - Strip noise from the system voice
name(_network,_high_quality, vendor prefixes, etc.). - Format the final label as
{Language} ({Region}) – {Voice Name}or{Language} – {Voice Name}when the region is missing. - Sort voices by language, then region, then name.
- Parse the BCP-47
- Use the cleaned list in the look-and-feel voice picker and the per-meditation voice override.
- Add unit tests for
TtsVoiceLabelServiceusing sample iOS and Android voice maps.
Phase 2 — Verification
- [x] Settings is organized into the requested sub-screens.
- [x] Notifications is removed from the bottom navigation and reachable through settings.
- [x] TTS voice default is selected in settings and can be overridden per meditation.
- [x] The voice list is cleaned into recognizable language/region/voice labels.
- [x] Health sync "none" prevents any health write for that meditation.
- [x] The "use defaults" switch hides and shows the correct editor sections.
Phase 3 — Lists unification and multi-select
Phase 3 — Goal
Merge prompt lists and quote lists into one generic list concept and allow multi-selection for prompts and notifications.
Phase 3 — Implementation steps
- New unified schema
- Create
ListsTable:id,name,isBuiltin,syncId,updatedAt. All lists are usable for prompts, notification messages, or both. Selection mode lives on the segment/profile link, not the list.
- Create
ListItemsTable:id,listId,orderIndex,body,updatedAt.
- Create
TimerSegmentListLinksandNotificationProfileListLinks:- linking tables supporting multiple lists per segment/profile.
- Keep
prompt_lists/promptsandquote_lists/quotestables only for the v22 → v23 migration. Write an idempotent migration that copies existing data into the new tables, then drops the old tables. -
Remove the
usagecolumn and bump the schema version to 24 so all existing v23 databases drop it. -
Domain models and repository
- Replace
PromptList/QuoteListandPrompt/QuotewithAppListandListItem. - Update
lists_repository.dartto unify CRUD, selection, and sync. -
Update the
BackupMapperso unified lists still sync correctly. -
UI refresh
- Replace
PromptLibraryScreenandQuoteListsScreenwith a singleListsScreenfor all lists. -
Replace the segment prompt list picker and the notification message-source picker with an inline selected-list editor: ordered rows, add list, reorder, edit, and remove actions.
-
Multi-select lists
- Add
TimerSegmentListLinksandNotificationProfileListLinkslinking tables to store multiple list IDs. - Update
TimerSegmentandNotificationProfilemodels. - Update the segment prompt picker and notification editor to use an inline selected-list editor (ordered rows, add list, reorder, edit, remove).
- Update the player to merge multiple lists (respecting the segment/profile
listSelectionMode, which israndomorordered) when picking prompts or notification messages.
Phase 3 — Verification
- [x] Existing prompt lists and quote lists are migrated without data loss.
- [x] Any list can be selected as a prompt or notification source.
- [x] Multiple lists can be attached to a segment or notification profile.
- [x]
flutter testmigration tests pass.
Phase 4 — Player experience
Phase 4 — Goal
Improve the session start and playback experience with countdown, DND, screen off, haptics, visualizer toggles, and prompt timing estimation.
Phase 4 — Implementation steps
- Start countdown
- Add
countdownSecondstoMeditationDefaults(global default) andMeditation(per-meditation override, withuseDefaultCountdown). - In the player, if
countdownSeconds > 0, show a silent full-screen countdown before the session begins. -
Add a countdown indicator; no sounds during this phase.
-
Screen behavior
- Replace the legacy
KeepScreenOnenum withScreenBehavior(keepOn,allowTimeout,turnOff). - Global default belongs in
MeditationDefaults; per-meditation override belongs inMeditationand supportsuseDefaultresolution. keepOnusesWakelockPlus;allowTimeoutleaves the OS timeout in control;turnOffattempts to turn the screen off after the countdown where supported and falls back toallowTimeouton unsupported platforms.-
Spike first: verify whether Flutter/native APIs can truly turn the screen off. Document the fallback.
-
Silence phone (DND)
- Add a
silencePhoneglobal default inMeditationDefaultsand a per-meditation override inMeditation. - Add a
DndServiceinterface withisSupported(),isEnabled(),requestPermission(),enable(), anddisable(). - Android implementation uses notification-policy access
(
ACCESS_NOTIFICATION_POLICY). - On iOS and desktop,
isSupported()returns false and the UI hides the option. - Before enabling, save the current interruption filter and restore it on session end, unless the user already had DND on.
-
Integrate with
MeditationPlayerScreen,RecitationPlayerScreen,WearPlayerScreen, andWearRecitationScreenstart/stop. -
Haptics
- Add
vibrationdependency. - Add
HapticSettingsmodel withenabled,pattern,intensity(high/medium/low), anddurationMilliseconds. Store global default and per-meditation override. - Map haptic events to:
- bell events (start bell, end bell, segment bell);
- timer transitions (moving into and out of a segment);
- prompt transitions (when a new prompt is spoken);
- recitation increments.
- Implement a
HapticServicewith pattern presets (short pulse, double pulse, long pulse, etc.) and intensity viaamplitudeorintensities. Duration is configurable; if the device does not support variable duration, fall back to a single standardHapticFeedback. - Add continuous haptic patterns that repeat for the duration of a segment
or breathing phase (dot, dash, ramp up, ramp down, slow dot, slow dash,
continuous double/triple pulse, and raindrops). Continuous patterns are
shown in the same dropdown with a
(continuous)suffix and stop automatically when the segment/phase/session ends. - Add an option for haptics in recitation mode on phones.
-
Desktop: haptics disabled/hidden.
-
Visualizer enable/disable
- Add
visualizerEnabledtoMeditationDefaultsandMeditation(withuseDefaultVisualizer). - When
visualizerEnabledresolves to true, renderSessionVisualizer. -
When it resolves to false, do not render
SessionVisualizerand do not replace it with another circular or static circle widget. -
Estimate prompt timings
- Investigate
flutter_ttscompletion callbacks and speech rates. - Add a
TtsTimingServicethat estimates prompt duration based on text length and configured speech rate, with a per-voice calibration fallback. - In the player, if
waitForStartBell/waitForEndBell/ speak-prompts is on, wait for the estimated TTS duration before advancing.
Phase 4 — Milestones
- Milestone 1 (countdown + visualizer) is implemented in schema v25 and covers the global/per-meditation model, UI, player integration, and backup/sync.
- Milestone 2 (screen behavior, DND, haptics) is planned for schema v26.
- Milestone 3 (TTS timing) adds
TtsTimingServiceand controller integration.
Verification is intended for the completed Phase 4 rather than after each milestone.
Phase 4 — Verification
- [x] Countdown displays silently and the session starts when it reaches zero.
- [x] DND enables/disables on Android and respects the pre-session state.
- [x] Haptics fire on start/end bells, segment transitions, prompt transitions, and recitation counts; duration is configurable.
- [x] The visualizer can be disabled globally and per meditation.
- [x] Player waits appropriately for longer TTS prompts.
Phase 5 — Health and session lifecycle
Phase 5 — Goal
Fix the incomplete-session health-connect bug, add session deletion, and make insights locale-aware.
Phase 5 — Implementation steps
- Incomplete sessions prompt
- Modify the player stop flow so the controller returns a draft
Sessionwithout logging it. The screen asks the user whether to save the incomplete session. - If the user chooses save: call
SessionsRepository.logSessionwithcompleted = falseandactualDurationSeconds = elapsed, then write the health record with the actual time if health sync is enabled. - If the user chooses discard: do not save locally and do not write to health.
-
For completed sessions, keep the current auto-save/auto-write behavior.
-
Delete sessions
- Add a delete action to
HistoryScreenandSessionDetailScreenwith a confirmation dialog. - If the session has
healthSynced == true, prompt whether to also delete the Health Connect / Apple Health record. - Extend
HealthServicewithdeleteSession(clientRecordId, start, end, target). Implement on Android usingHealthConnectClient.deleteRecords. - On iOS/HealthKit, attempt deletion through
healthpackage APIs if available; if not, show a message that the external record must be removed manually. -
Record a tombstone before deleting the local row so the deletion syncs.
-
First day of week by locale
- Use
intlDateFormat/DateSymbolsto readFIRSTDAYOFWEEKfor the current locale. - Update
SessionStatsweek-boundary logic and the insights/stats graphs. -
Add a test verifying week start for
en_US(Sunday) vsen_GB(Monday). -
Meditation history/stats icon in the meditation list
- Add a small icon button on each
MeditationListScreentile. - Tapping it opens
HistoryScreenandStatsScreenpre-filtered to that meditation (passmeditationIdthrough route extra or query parameter). - Move existing icons for duplicate, archive in meditation list to meditation editor, at the top, next to export.
Phase 5 — Verification
- [x] Stopping an incomplete session prompts save/discard.
- [x] Saving writes the actual elapsed time to Health Connect / Apple Health.
- [x] Discarding does not create a local or health record.
- [x] Deleting a synced session prompts and deletes the health record where the platform supports it.
- [x] Insights honor the locale's first day of the week.
Phase 6 — Advanced segments (per-segment type and repeat)
Phase 6 — Goal
Make meditation type a per-segment property and add segment repetition, including ordered/random list-based repetition.
Phase 6 — Implementation steps
- Per-segment meditation type
- Add
TimerSegment.modeenum (timer,counter,breathing). - Keep
Meditation.modeas the default for new segments but allow override. - Refactor
MeditationPlayerControllerandRecitationPlayerControllerinto a singleMultiSegmentPlayerControllerthat can run a mixed sequence of timer, counter, and guided-breathing segments. -
Update
MeditationPlayerScreen/RecitationPlayerScreento use the unified controller; easy mode and Wear keep working as one-segment cases. -
Segment repeat
- Add
repeatCount,repeatMode,repeatListId, andrepeatListOrdertoTimerSegment:repeatMode:none,single,group.- For single-segment repeat: choose
samePromptornewFromList. - For group repeat: choose
perSegment(a,a,a,b,b,b) orinOrder(a,b,c,a,b,c). - For list-driven repeat: repeat the segment until the selected list is exhausted, either in order or at random.
- Add a
SegmentExpanderthat turns aMeditationwith repeat rules into a flat runtime sequence of concrete segments. -
Add an editor UI for repeat settings per segment and per group.
-
Player integration
- The unified controller expands repeats before playback and tracks the runtime segment index.
- Prompt selection follows the repeat rules and the chosen list's
selectionMode.
Phase 6 — Verification
- [x] A meditation can mix timer, counter, and breathing segments in one session.
- [x] Single and group repeats produce the expected sequence order.
- [x] List-driven repeat stops when the list is exhausted.
- [x] Unit tests cover
SegmentExpanderfor all repeat modes.
Phase 7 — Audio creation and binaural beats
Phase 7 — Goal
Add user-created audio (recording and synthesized binaural beats) as sound sources that the existing sound library can use.
Phase 7 — Implementation steps
- Binaural beats
- Add a new sound category
binaural. - Add
BinauralBeatConfigmodel with base frequency, beat frequency, and duration. - Use
flutter_soloud's waveform generation (WaveForm.sin) to create two sine voices at slightly different frequencies, panned left/right, for the binaural effect. - Add default presets (alpha, theta, delta) and a custom editor in the settings/library group.
-
Store binaural definitions in
BackgroundSoundsTablewith a JSON config field, or a newBinauralBeatsTable. -
Sound recorder
- Add
record: ^7.1.1. - Add
RecordedSoundsTableor reuseBackgroundSoundsTablewithsource = 'recorded'andfilePath. - Add a recording UI in the sound library. Allow recording, trimming (optional), naming, and choosing a category (bell, background, prompt).
- Handle microphone and storage permissions through
permission_handler. - Make recorded sounds selectable in the meditation editor like any other sound.
Phase 7 — Verification
- [x] Binaural beat presets play and can be used as background sounds.
- [z] A user can record a prompt/bell/background and use it in a meditation.
- [z] Recordings are persisted in the app documents and included in backup/sync.
Phase 8 — Guided breathing and visualizers
Phase 8 — Goal
Implement guided breathing exercises as a meditation/segment type with dedicated visualizers.
Phase 8 — Implementation steps
- Research/design spike
- Decide whether breathing exercises are:
- a
Meditationtype, - a
TimerSegment.modevalue, or - both.
- a
- Define a
BreathingExercisemodel with inhale/hold/exhale/hold timings and optional start/end prompts. -
Choose whether the exercise dropdown lives on the meditation editor or per segment.
-
Implementation
- Add the chosen model fields and DB migration.
- Add a dropdown to select the breathing exercise (box, bee, etc.).
- Add the breath-phase controller that drives:
- an expanding/contracting dot visualizer,
- a tree/flower growth/reversal visualizer, and
- a wave grow/crash/recede visualizer.
- Implement each visualizer as a
CustomPainterorAnimatedBuilderwidget. - Allow the user to pick which visualizer to use.
Phase 8 — Verification
- [x] A breathing exercise can be selected and played.
- [x] The chosen visualizer animates smoothly through inhale/hold/exhale phases.
- [x] The player advances breath phases on time.
Phase 9 — Welcome tour and release polish
Phase 9 — Goal
Guide first-time users through standard options and do final documentation/QA.
Phase 9 — Implementation steps
- Welcome screen/tour
- Use
introduction_screen: ^4.0.0or a custom page view. - Show on first launch only; store
hasSeenWelcomeinSettingsRepository. - Steps (each skippable, whole thing skippable):
- theme/accent,
- health connect opt-in,
- accessibility (font scale / reduce motion),
- DND/haptics/voice overview,
- final "get started".
-
Add a "re-show welcome" button in the look-and-feel or about settings.
-
Documentation and QA
- Update
docs/user-guide/*.mdanddocs/dev-guide/ARCHITECTURE.mdfor all new features. - Update
docs/dev-guide/PROJECT_PLAN.mdwith the final phase list and implementation notes. - Run the full verification suite and a manual QA pass on Linux, Android, and Wear.
Phase 9 — Verification
- [ ] Welcome tour appears once on first launch and can be skipped entirely.
- [ ] Each step can be skipped individually.
- [ ] The "re-show welcome" button in settings opens the tour again.
- [ ] All
flutter analyze,flutter test, andflutter build linux/apk --flavor wearpass.
Files and components most likely to change
Build, re-brand, and packaging
pubspec.yamlandroid/app/build.gradle.ktsandroid/app/src/main/kotlin/com/wesorick/john/knowthatyouarebreathing/*.ktios/Runner.xcodeproj/project.pbxproj,ios/Runner/Info.plistmacos/Runner.xcodeproj/project.pbxproj,macos/Runner/Info.plist,macos/Runner/Configs/AppInfo.xcconfiglinux/CMakeLists.txt,linux/runner/my_application.cc,linux/packaging/*windows/runner/Runner.rcflatpak/*,snap/snapcraft.yamltool/generate.dart,tool/generate_android_keystore.sh.gitlab-ci.ymlREADME.md,CONTRIBUTING.md,CHANGELOG.md,LICENSEdocs/dev-guide/PROJECT_PLAN.md,docs/dev-guide/ARCHITECTURE.md
Localization and assets
lib/l10n/app_en.arblib/l10n/generated/app_localizations*.dartassets/images/app_icon.png,assets/brand/*android/app/src/main/res/mipmap-*/ic_launcher.pngios/Runner/Assets.xcassets/AppIcon.appiconset/*macos/Runner/Assets.xcassets/AppIcon.appiconset/*windows/runner/resources/app_icon.icolinux/runner/resources/know_that_you_are_breathing.png
Core routing and shell
lib/core/routing/app_router.dartlib/core/routing/app_navigation_shell.dartlib/app.dartlib/main.dart
Data layer
lib/data/database/app_database.dartlib/data/database/tables/meditations_table.dartlib/data/database/tables/timer_segments_table.dartlib/data/database/tables/sessions_table.dartlib/data/database/tables/prompt_lists_table.dart(unified)lib/data/database/tables/quotes_table.dart(unified)lib/data/database/tables/quotes_table.dartlib/data/database/tables/notification_profiles_table.dartlib/data/database/tables/background_sounds_table.dartlib/data/models/meditation.dartlib/data/models/session.dartlib/data/models/ui_preferences.dartlib/data/models/health_sync_target.dartlib/data/models/keep_screen_on.dartlib/data/repositories/settings_repository.dartlib/data/repositories/sessions_repository.dartlib/data/repositories/meditations_repository.dartlib/data/services/health_service.dartlib/data/services/health_service_impl.dartlib/data/services/health_service_android.dartlib/data/services/health_relay_service.dartlib/data/services/tts_service.dartlib/data/services/flutter_tts_service.dartlib/data/services/tts_service_providers.dartlib/data/services/tts_voice_label_service.dart(new)lib/data/services/dnd_service.dart(new)lib/data/services/haptic_service.dart(new)
Features
lib/features/editor/editor_screen.dartlib/features/editor/meditation_list_screen.dartlib/features/editor/meditation_player_screen.dartlib/features/editor/meditation_player_controller.dartlib/features/editor/recitation_player_screen.dartlib/features/editor/recitation_player_controller.dartlib/features/wear/wear_player_screen.dartlib/features/wear/wear_recitation_screen.dartlib/features/settings/settings_screen.dart+ new sub-screenslib/features/history/history_screen.dartlib/features/history/session_detail_screen.dartlib/features/history/session_prompts.dartlib/features/notifications/notifications_screen.dartlib/features/notifications/notification_profile_editor_screen.dartlib/features/prompts/prompt_library_screen.dartlib/features/quotes/quote_lists_screen.dartlib/features/quotes/quote_list_editor_screen.dartlib/features/sounds/sound_library_screen.dart
Widgets
lib/core/widgets/session_visualizer.dartlib/widgets/duration_picker.dartlib/widgets/number_wheel.dart
Tests
- Mirror every
lib/change intest/with unit, widget, and golden tests. - Update
test/data/database/app_database_migration_test.dartfor each schema bump.
Verification (re-run after every non-trivial change)
- [ ]
flutter pub getanddart run build_runner buildsucceed. - [ ]
flutter gen-l10nregenerates localizations. - [ ]
flutter analyzeis clean. - [ ]
flutter testpasses. - [ ]
flutter build linuxsucceeds. - [ ]
flutter build apk --flavor wear --release -t lib/main_wear.dartsucceeds. - [ ] Code coverage remains at or above the 70% CI target.
Risks and considerations
- Major package upgrades can cause analyzer/codegen failures. Phase 0 must be green before moving on, and pinning/fallback versions may be needed.
- Full package restructure moves Android Kotlin source; an incorrect path or import will break the build. Coordinate with any in-flight native branches.
- Unified lists is a large schema migration. Test migration from a v21 database to the new schema thoroughly and keep a rollback plan.
- Per-segment meditation types and repeat touch the player core. A refactor
of the two existing controllers into a unified
MultiSegmentPlayerControlleris recommended but high-risk; time-box the design and keep the old controllers as fallbacks if needed. - DND and screen off are OS-limited. Android DND requires a special permission and may not be granted; iOS cannot toggle DND from apps. The UI must hide or disable these options where unsupported.
- Health record deletion is straightforward on Health Connect but may not be
supported by the
healthplugin for Apple Health. Prepare a graceful "manually delete" fallback message. - Guided breathing and visualizers are the largest UI scope. The research spike in Phase 8 may lead to cutting some visualizers for an initial release.
- App ID change is safe because the app has not been released, but existing local developer installs and signing configs need to be recreated.
- Welcome tour and new settings add many UI strings. All English text must stay lowercase per the existing design convention and pass through ARB.
Deferred work from previous plan
The following items were planned in earlier versions of this project plan but are not part of the current roadmap. They are preserved here so they are not lost.
watchOS companion app (separate future plan)
A real watchOS app requires a native SwiftUI/WatchKit target bridged to the
Flutter iOS app through WatchConnectivity. It is not part of this plan; when
reached, a separate plan should cover scaffolding the watch target, the bridge,
and a feature subset matching the Wear OS subset from Phase 10. Apple Watch
double-tap / crown-based recitation counting should also be considered as an
input option once that plan begins.
Wear OS remote-audio / audio-source setting (Stage 10.9)
The "play session audio from: this device / phone / watch" setting and its
remote-audio control channel over the Wearable Data Layer were deferred. The
intended design uses a small command protocol (playBell(soundId),
startBackground(soundId, volume), stopAll, ping) handled by a
RemoteAudioService on the sending side, dispatching to the local AudioService
on the receiving side. Before each session the selected route is checked with a
ping and, if unavailable, falls back to local audio on the originating device.
Watch-local notification scheduling
Scheduling mindfulness reminders directly on the watch was intentionally deferred. The current implementation relies on phone-mirrored Wear OS notifications and does not expose a watch-local toggle.
Standalone WebDAV and git sync from the watch
Data Layer sync between the watch and phone is the primary sync path. A standalone WebDAV sync backend and a standalone git sync backend that can run directly from the watch are deferred to a future release.
Windows and macOS zip bundles for CI packaging
Windows and macOS zip bundle packaging jobs in CI were deferred until runners
for those platforms are available.
Suggested enhancements not in the current plan
The current roadmap already covers the breathing pacer visualizer, haptic-only
bell mode, guided breathing, binaural beats, and .med import/export. The
following ideas from the previous ## Suggested enhancements section remain
optional backlog items:
- Home-screen widgets (Android) / Lock Screen widgets & complications (once watchOS work begins).
- Streaks & gentle achievement badges (opt-in, not gamified in a pushy way).
- Shortcuts / Tasker / Siri-Shortcuts style automation hooks to start a specific meditation.
- Community sharing index for
.medfiles (simple, optional, opt-in directory). - Ambient/adaptive background themes that shift subtly over a session's duration.