diff --git a/.ai/skills/decompose-component-tests/SKILL.md b/.ai/skills/decompose-component-tests/SKILL.md index a1d2ac9..0369403 100644 --- a/.ai/skills/decompose-component-tests/SKILL.md +++ b/.ai/skills/decompose-component-tests/SKILL.md @@ -8,8 +8,8 @@ description: Use in the Blinkly Kotlin Multiplatform repository when writing, up Read `AGENTS.md` first for architecture and test conventions. Blinkly component tests cover onboarding, home navigation, Store-backed tabs, -nested feature components, coroutine side effects, manager-derived state, fake -time/settings, output mapping, and Mokkery collaborator verification. +root-pushed feature components, coroutine side effects, manager-derived state, +fake time/settings, output mapping, and Mokkery collaborator verification. `MainTabComponentTest` remains the reference for a Store-backed tab with manager-derived state, watcher flows, error labels, and CTA output mapping. @@ -31,7 +31,7 @@ Store-backed tab component tests: - `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/reminders/RemindersTabComponentTest.kt` - `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/trainings/TrainingsTabComponentTest.kt` -Store-backed nested feature tests: +Store-backed root-pushed feature tests: - `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/preferences/PreferencesComponentTest.kt` - `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/achievements/AchievementsComponentTest.kt` - `shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/garden/GardenComponentTest.kt` @@ -69,6 +69,15 @@ For parent navigation components, verify: - back navigation by child callback and by parent `onBack()` - output translation from child to parent navigation +Current Blinkly navigation ownership: +- `RootComponentDefault` owns root-pushed feature screens: `Preferences`, + `Workout`, `Achievements`, `Garden`, and `AddNewReminder`. +- `HomeScreenComponentDefault` owns only the tab stack and handles + `Main.OpenProgressTab` locally. +- `ProgressTabComponentDefault`, `RemindersTabComponentDefault`, and + `TrainingsTabComponentDefault` should be tested as Store-backed tabs that + emit outputs, not as parents with nested child stacks. + For Store-backed child components, verify: - model state before actions - model state after component callbacks diff --git a/.ai/skills/decompose/SKILL.md b/.ai/skills/decompose/SKILL.md index 71a7f6d..403f07d 100644 --- a/.ai/skills/decompose/SKILL.md +++ b/.ai/skills/decompose/SKILL.md @@ -7,11 +7,11 @@ description: Use in the Blinkly Kotlin Multiplatform repository when adding, cha Read `AGENTS.md` first for the project-level architecture. -Blinkly has baseline Decompose components across onboarding, home tabs, nested -feature routes, Compose bindings, previews, and common component tests. +Blinkly has baseline Decompose components across onboarding, home tabs, +root-pushed feature routes, Compose bindings, previews, and common component tests. `MainTabComponent` remains the primary Store-backed tab reference, while -`progress`, `reminders`, and `trainings` show Store-backed tabs with nested -child navigation. +`progress`, `reminders`, and `trainings` show Store-backed tabs that emit +root-handled navigation outputs. ## Use these local references first @@ -112,6 +112,16 @@ Config rules: Keep navigation calls on the main thread. +Current Blinkly hierarchy: +- `RootComponentDefault` owns the app-level stack and pushes `Preferences`, + `Workout`, `Achievements`, `Garden`, and `AddNewReminder`. +- `HomeScreenComponentDefault` owns only the four-tab stack and forwards most + tab outputs upward to root. +- `OnboardingComponentDefault` owns the onboarding step stack. +- `ProgressTabComponentDefault`, `RemindersTabComponentDefault`, and + `TrainingsTabComponentDefault` do not own nested child stacks; they emit + `ComponentOutput` values that root interprets. + ## Output rules Use `ComponentOutput` to communicate upward. @@ -166,6 +176,7 @@ Use them to: - create platform dispatchers - build module factories from `shared/*/di` - assemble `DomainModule` +- create `BeeperModule` and pass `BlinklyBeeper` to workout execution - instantiate `RootComponentDefault` Do not push DI container logic into feature components. @@ -180,6 +191,9 @@ Keep feature components constructor-injected. 5. add navigation config and child mapping in the parent 6. add Compose content in `shared/compose` 7. add common tests for navigation or behaviour +8. update `AGENTS.md` in the same change when the new screen or flow changes + module layout, component hierarchy, navigation ownership, dependency + direction, DI modules, external interfaces, or root factory wiring For existing feature modules, preserve their component/store/Compose/test shape and extend the nearest parent navigation only when the route changes. diff --git a/.ai/skills/mvikotlin/SKILL.md b/.ai/skills/mvikotlin/SKILL.md index 4189c57..655594e 100644 --- a/.ai/skills/mvikotlin/SKILL.md +++ b/.ai/skills/mvikotlin/SKILL.md @@ -8,9 +8,9 @@ description: Use in the Blinkly Kotlin Multiplatform repository when adding, cha Read `AGENTS.md` first for the project-level architecture. Blinkly has Store-backed references across onboarding steps, home tabs, and -nested feature screens. Use `main` as the primary reference for a Store-backed -tab with manager-based business logic, real Compose UI, preview-only component -implementation, and common component tests. +root-pushed feature screens. Use `main` as the primary reference for a +Store-backed tab with manager-based business logic, real Compose UI, +preview-only component implementation, and common component tests. ## Use these local references first @@ -138,13 +138,13 @@ Use these patterns: - `withContext(ioContext)` for IO or heavier domain calls - feature-local managers in component `domain` packages return `Result` from suspend/business operations using `runCatching` - handle manager results with `unwrap(result = ..., onSuccess = ..., onError = ...)` in the executor -- keep manager flow subscriptions as `Flow` and protect them with `.catch { publish(Label.ErrorCaught(it)) }` -- `publish(Label.ErrorCaught(...))` for one-off failures that should not live in state +- keep manager flow subscriptions as `Flow` and protect them with `.catch { publish(Label.ErrorCaught(it.asBlinklyError(...))) }`, using the matching `BlinklyError` subclass for that feature or operation +- `publish(Label.ErrorCaught(...))` for one-off failures that should not live in state; do not publish raw platform/domain exceptions when the error will be visible app-wide `MainTabStoreProvider` is the reference for bootstrapper actions that subscribe to multiple flows, run manager calculations on `ioContext`, and translate stream updates into `Msg` values. `step5`, `progress`, `reminders`, -`trainings`, and nested feature components are valid references for narrower +`trainings`, and root-pushed feature screens are valid references for narrower Store-backed flows. ## Component integration rules @@ -167,6 +167,15 @@ Store `Intent` sealed interface empty as in `MainTabStore`. Component callbacks can use current `model.value` to choose a `ComponentOutput` when the decision is navigation-only, as `MainTabComponentDefault.onPrimaryCtaClick()` does. +Navigation ownership note: +- `ProgressTabComponentDefault`, `RemindersTabComponentDefault`, and + `TrainingsTabComponentDefault` expose Store-backed tab state and emit outputs. + They do not own nested child stacks. +- `Preferences`, `Achievements`, `Garden`, `AddNewReminder`, and `Workout` are + Store-backed feature screens opened by `RootComponentDefault`. +- `WorkoutStoreProvider` receives `BlinklyBeeper` for exercise audio feedback; + `WorkoutComponentDefault` releases it in lifecycle cleanup. + ## Error handling Blinkly does not route every Store label to parent output. @@ -181,6 +190,9 @@ it maps `MainTabStore.Label.ErrorCaught` to `ComponentOutput.Common.ErrorCaught`. `OnboardingStep5ComponentDefault` is the nested-flow reference for the same pattern. +App-wide user-visible errors use `BlinklyError` from `shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/BlinklyError.kt`. +Wrap low-level failures at the point where the user-facing operation is known, keep the original throwable as `cause`, and route the wrapped error through `ComponentOutput.Common.ErrorCaught`. +`RootComponentDefault` exposes the resulting errors through `RootComponent.errors`; `RootContent` maps them to localized Compose resources and shows the top error snackbar. Do not leak platform-specific exceptions into Compose. ## Testing expectations diff --git a/.github/workflows/AnalysisAndTest.yml b/.github/workflows/AnalysisAndTest.yml index 9b3c500..b3c42ec 100644 --- a/.github/workflows/AnalysisAndTest.yml +++ b/.github/workflows/AnalysisAndTest.yml @@ -12,6 +12,8 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + with: + lfs: true - name: Set up JDK uses: actions/setup-java@v4 @@ -28,3 +30,16 @@ jobs: - name: Testing run: ./gradlew testDebugUnitTest + + - name: Screenshot testing + run: ./gradlew :shared:compose:verifyPaparazziDebug + + - name: Upload Paparazzi report + if: failure() + uses: actions/upload-artifact@v4 + with: + name: paparazzi-report + path: | + shared/compose/build/reports/paparazzi + shared/compose/build/paparazzi/failures + if-no-files-found: ignore diff --git a/AGENTS.md b/AGENTS.md index 00cc463..15f7d61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,6 +67,7 @@ repository copy first and remove or refresh the global copy immediately after. - `androidApp` - Android entry point and Android resources - `iosApp` - iOS host app and Xcode project - `shared/alarm` - alarm scheduling through Alarmee +- `shared/beeper` - platform beep/audio feedback abstraction used by workout execution - `shared/component` - Decompose component modules, usually one module per screen or nested flow - `shared/compose` - shared Compose UI only - `shared/database` - SQLDelight database implementation and mappers @@ -82,27 +83,34 @@ modules have baseline contracts, default implementations, Compose bindings, preview implementations where useful, and common component tests. Top-level component modules: -- `root` - app root, dependency composition, top-level navigation +- `root` - app root, dependency composition, top-level stack navigation, and root-level feature screen routing - `onboarding` - one-time onboarding flow with five child steps -- `home` - shell with four tabs; routes tab outputs and owns tab stack navigation +- `home` - shell with four tabs; owns tab stack navigation and forwards root-level tab outputs upward - `main` - implemented main dashboard tab with MVIKotlin Store, feature-local manager, Compose UI, preview component, and common tests -- `progress` - progress tab with MVIKotlin Store, progress manager, achievements and garden child routes, Compose UI, preview component, and common tests -- `reminders` - reminders tab with MVIKotlin Store, reminders manager, add-new-reminder child route, Compose UI, preview component, and common tests -- `trainings` - trainings tab with MVIKotlin Store, trainings manager, workout child route, Compose UI, preview component, and common tests +- `progress` - progress tab with MVIKotlin Store, progress manager, calendar/garden/achievement summary state, outputs for achievements and garden screens, Compose UI, preview component, and common tests +- `reminders` - reminders tab with MVIKotlin Store, reminders manager, reminder list/delete/undo state, output for add-new-reminder, Compose UI, preview component, and common tests +- `trainings` - trainings tab with MVIKotlin Store, trainings manager, exercise block cards, outputs for workout execution, Compose UI, preview component, and common tests Nested component modules: -- `main/child/preferences` - preferences screen with MVIKotlin Store, manager, Compose UI, preview component, and common tests +- `main/child/preferences` - preferences screen opened on the root stack from the main tab; has MVIKotlin Store, manager, Compose UI, preview component, and common tests - `onboarding/child/step1` through `step5` - onboarding steps; `step4` and `step5` are Store-backed -- `progress/child/achievements` - achievements screen with MVIKotlin Store, Compose UI, preview component, and common tests -- `progress/child/garden` - garden screen with MVIKotlin Store, Compose UI, preview component, and common tests -- `reminders/child/newreminder` - add-new-reminder screen with MVIKotlin Store, manager, Compose UI, preview component, and common tests -- `trainings/child/workout` - workout execution screen with MVIKotlin Store, Compose UI, preview component, and common tests +- `progress/child/achievements` - achievements screen opened on the root stack from the progress tab; has MVIKotlin Store, Compose UI, preview component, and common tests +- `progress/child/garden` - garden screen opened on the root stack from the progress tab; has MVIKotlin Store, Compose UI, preview component, and common tests +- `reminders/child/newreminder` - add-new-reminder screen opened on the root stack from the reminders tab; has MVIKotlin Store, manager, Compose UI, preview component, and common tests +- `trainings/child/workout` - workout execution screen opened on the root stack from the trainings tab or main CTA; has MVIKotlin Store, beeper feedback, Compose UI, preview component, and common tests + +Current screen hierarchy: +- `RootComponentDefault` starts with `Onboarding` when onboarding has not been completed, otherwise `HomeScreen`. +- `RootComponentDefault` owns the app-level stack and pushes `Preferences`, `Workout`, `Achievements`, `Garden`, and `AddNewReminder` from `ComponentOutput` values emitted by child components. +- `HomeScreenComponentDefault` owns only the four-tab stack: main, trainings, progress, and reminders. It handles `Main.OpenProgressTab` locally with `bringToFront`; other tab outputs go up to root. +- `OnboardingComponentDefault` owns the onboarding step stack from step 1 through step 5. Steps 1, 2, and 3 are thin output-forwarding components; step 4 and step 5 are Store-backed. +- Root-level feature screens use `ComponentOutput.Common.BackPressed` to return to the previous root stack entry. Current implementation notes: -- Component work is materially implemented across root navigation, onboarding, home tabs, nested feature screens, Compose bindings, previews, and common component tests. +- Component work is materially implemented across root navigation, onboarding, home tabs, root-pushed feature screens, Compose bindings, previews, and common component tests. - `main` is the current reference for a Store-backed tab component with domain-derived dashboard state, one-off error labels, a Compose screen, preview-only component implementation, and common component tests. -- `progress`, `reminders`, and `trainings` are the current references for Store-backed tabs with nested child routes. -- `preferences`, `achievements`, `garden`, `newreminder`, and `workout` are the current references for Store-backed nested feature components. +- `progress`, `reminders`, and `trainings` are the current references for Store-backed tabs that emit root-handled navigation outputs. +- `preferences`, `achievements`, `garden`, `newreminder`, and `workout` are the current references for Store-backed feature screens that are physically grouped under their owning product area but opened on the root stack. - `step4`, `step5`, `main`, `preferences`, `progress`, `achievements`, `garden`, `reminders`, `newreminder`, `trainings`, and `workout` are the current reference implementations for MVIKotlin stores. ## Architecture Rules @@ -113,7 +121,7 @@ Use this dependency direction: - platform app -> root factory -> shared component modules - components -> domain abstractions and external interfaces - domain -> external interfaces from `shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/external` -- database, notifier, alarm, settings, utils -> implementations of those external interfaces +- database, notifier, alarm, settings, beeper, utils -> implementations of those external interfaces - compose -> components only, no business logic Keep UI-specific code out of component and store layers. Compose should render state and call component methods. Components should translate UI actions into navigation, Store intents, or domain calls. @@ -130,11 +138,12 @@ Apply these rules by default when changing Blinkly code: - Keep Store reducers pure. Put side effects, subscriptions, manager calls, and IO switching in the executor. - Route cross-component events upward through `ComponentOutput`; do not let child components manipulate parent navigation directly. - Keep dependencies in constructors and root or parent factories; never place dependencies into Decompose navigation configs. +- When adding or materially changing a shared module, component module, feature screen, or dependency wiring, update this guide in the same change. Keep `Repository Layout`, `Module Map`, `Shared layering`, and `Manual dependency injection` accurate, including new DI modules, external interfaces, root factory wiring, and navigation ownership. - Keep Compose as a rendering layer only. Do not move business logic, navigation decisions, or mutable feature state into composables. - Cover Decompose behaviour in `shared/component/root/src/commonTest`, extending `ComponentTest` with `DefaultComponentContext(lifecycle)`, `testDispatchers`, navigation assertions on `childStack`, and `model.value` assertions for Store-backed components. - Keep `*Default` component implementations as the real runtime wiring for Stores, dependencies, labels, lifecycle, and output routing. Use separate `*Preview` implementations only for Compose previews; they should implement the same public component interface with static `MutableValue` model data and no production dependencies. -When unsure whether to follow a generic library pattern or the project pattern, follow the project pattern demonstrated in `main`, `step4`, `step5`, `onboarding`, `home`, `progress`, `reminders`, `trainings`, and their nested feature components. +When unsure whether to follow a generic library pattern or the project pattern, follow the project pattern demonstrated in `main`, `step4`, `step5`, `onboarding`, `home`, `progress`, `reminders`, `trainings`, and their feature components. ### Manual dependency injection @@ -152,9 +161,10 @@ Reference modules: - `shared/settings/src/commonMain/kotlin/com/sedsoftware/blinkly/settings/di/SettingsModule.kt` - `shared/notifier/src/commonMain/kotlin/com/sedsoftware/blinkly/notifier/di/NotifierModule.kt` - `shared/alarm/src/commonMain/kotlin/com/sedsoftware/blinkly/alarm/di/AlarmModule.kt` +- `shared/beeper/src/commonMain/kotlin/com/sedsoftware/blinkly/beeper/di/BeeperModule.kt` - `shared/utils/src/commonMain/kotlin/com/sedsoftware/blinkly/utils/di/UtilsModule.kt` -`RootComponentFactory` is the composition root on both platforms. It builds dispatchers, utils, platform modules, then `DomainModule`, then `RootComponentDefault`. +`RootComponentFactory` is the composition root on both platforms. It builds dispatchers, utils, platform modules (`alarm`, `database`, `notifier`, `settings`, `beeper`), then `DomainModule`, then `RootComponentDefault`. Important local rule: configuration objects in Decompose navigation carry only persistent arguments, never dependencies. Dependencies are supplied in child factories. @@ -211,9 +221,9 @@ Parent components use: Local reference patterns: - `OnboardingComponentDefault` for nested flow navigation -- `RootComponentDefault` for app-level stack-navigation shape and root-level interpretation of child outputs +- `RootComponentDefault` for app-level stack-navigation shape, root-level feature screens, and root-level interpretation of child outputs - `HomeScreenComponentDefault` for tab-switching shape with `bringToFront`, creation of real tab components, and local handling of tab-switch outputs -- `ProgressTabComponentDefault`, `RemindersTabComponentDefault`, and `TrainingsTabComponentDefault` for nested tab child navigation +- `ProgressTabComponentDefault`, `RemindersTabComponentDefault`, and `TrainingsTabComponentDefault` for Store-backed tab state and root-handled navigation outputs Keep navigation on the main thread. This matches Decompose guidance. @@ -269,7 +279,7 @@ Current store references: - `shared/component/onboarding/child/step5/.../store/InitialRemindersStoreProvider.kt` `main` is the reference for a tab Store with bootstrapper subscriptions, manager-based business logic, domain-derived model data, and labels mapped to parent output. -`progress`, `reminders`, and `trainings` are references for Store-backed tabs with child navigation. `preferences`, `achievements`, `garden`, `newreminder`, and `workout` are references for Store-backed nested screens. +`progress`, `reminders`, and `trainings` are references for Store-backed tabs that emit navigation outputs. `preferences`, `achievements`, `garden`, `newreminder`, and `workout` are references for Store-backed feature screens opened by the root stack. ### Store structure @@ -291,8 +301,8 @@ These match both local code and official MVIKotlin guidance: - switch to `dispatchers.io` inside executor coroutines for IO or long-running operations - publish one-off failures through `Label` when they should not live in state - feature-local managers in component `domain` packages should wrap suspend/business operations in `Result` via `runCatching`; Store executors handle those results with `unwrap(...)` -- managers may expose watcher flows directly when the Store only needs to subscribe and map stream values; protect those subscriptions with `.catch { publish(Label.ErrorCaught(it)) }` -- flow subscriptions may remain as `Flow` from managers and should be protected with `.catch { publish(Label.ErrorCaught(it)) }` in the executor +- managers may expose watcher flows directly when the Store only needs to subscribe and map stream values; protect those subscriptions with `.catch { publish(Label.ErrorCaught(it.asBlinklyError(...))) }`, using the matching `BlinklyError` subclass for that feature or operation +- flow subscriptions may remain as `Flow` from managers and should be protected with `.catch { publish(Label.ErrorCaught(it.asBlinklyError(...))) }` in the executor, using the matching `BlinklyError` subclass rather than publishing raw exceptions `main` is the reference for bootstrapper plus subscriptions that also derive aggregate dashboard state through a manager. `step5` is the reference for bootstrapper plus subscription-driven setup logic. @@ -316,7 +326,13 @@ The project models one-off errors as `ComponentOutput.Common.ErrorCaught`. When a Store label must be visible outside the feature, collect `store.labels` in the component layer, map the label to a typed `ComponentOutput`, and let the parent decide whether to handle it locally or forward it upward. `OnboardingStep5ComponentDefault` is the current reference: it maps `InitialRemindersStore.Label.ErrorCaught` to `ComponentOutput.Common.ErrorCaught`; `OnboardingComponentDefault` handles onboarding navigation outputs locally and forwards common outputs to the root. -Only route labels upward when a parent actually needs the event. Otherwise keep the event as a Store `Label` for local handling. Do not leak domain or platform exceptions into Compose. +Only route labels upward when a parent actually needs the event. Otherwise keep the event as a Store `Label` for local handling. + +App-wide user-visible errors use `BlinklyError` from `shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/BlinklyError.kt`. +Wrap low-level failures at the point where the user-facing operation is known, such as loading progress data, saving preferences, creating a reminder, or saving a workout. Keep the original throwable as `cause` for logging. +`RootComponentDefault` maps incoming `ComponentOutput.Common.ErrorCaught` values to `BlinklyError`, logs the original cause, and emits them through `RootComponent.errors`. +`RootContent` collects `RootComponent.errors`, maps `BlinklyError` to localized strings, and shows the app-wide top error snackbar. +When adding a new user-visible error context, add the `BlinklyError` type and matching English/Russian strings in `shared/compose/src/commonMain/composeResources`; unknown or too-narrow failures should fall back to `BlinklyError.Unknown`. ## Compose Conventions @@ -425,6 +441,7 @@ When adding a new Blinkly feature: 3. keep domain logic in `shared/domain` or a feature-local domain helper, not in Compose 4. inject dependencies from the root or parent factory, never through navigation configs 5. add common tests for component behavior and state transitions +6. update `AGENTS.md` in the same change when the feature adds or changes a module, component hierarchy, dependency direction, external interface, DI module, or root factory wiring When modifying existing code: - preserve current package naming even when file path and package names differ in nesting depth diff --git a/androidApp/src/main/kotlin/com/sedsoftware/blinkly/AppActivity.kt b/androidApp/src/main/kotlin/com/sedsoftware/blinkly/AppActivity.kt index 6861a25..fe3cf98 100644 --- a/androidApp/src/main/kotlin/com/sedsoftware/blinkly/AppActivity.kt +++ b/androidApp/src/main/kotlin/com/sedsoftware/blinkly/AppActivity.kt @@ -49,7 +49,7 @@ class AppActivity : ComponentActivity() { setContent { RootContent( component = rootComponent, - onThemeChanged = { ThemeChanged(it) }, + onSystemBarsAppearanceChanged = { SystemBarsAppearanceChanged(it) }, ) } } @@ -87,13 +87,13 @@ class AppActivity : ComponentActivity() { } @Composable -private fun ThemeChanged(isDark: Boolean) { +private fun SystemBarsAppearanceChanged(useDarkIcons: Boolean) { val view = LocalView.current - LaunchedEffect(isDark) { + LaunchedEffect(useDarkIcons) { val window = (view.context as Activity).window WindowInsetsControllerCompat(window, window.decorView).apply { - isAppearanceLightStatusBars = isDark - isAppearanceLightNavigationBars = isDark + isAppearanceLightStatusBars = useDarkIcons + isAppearanceLightNavigationBars = useDarkIcons } } } diff --git a/shared/component/home/src/commonMain/kotlin/com/sedsoftware/blinkly/component/home/integration/HomeScreenComponentDefault.kt b/shared/component/home/src/commonMain/kotlin/com/sedsoftware/blinkly/component/home/integration/HomeScreenComponentDefault.kt index 5c3a8fc..2918018 100644 --- a/shared/component/home/src/commonMain/kotlin/com/sedsoftware/blinkly/component/home/integration/HomeScreenComponentDefault.kt +++ b/shared/component/home/src/commonMain/kotlin/com/sedsoftware/blinkly/component/home/integration/HomeScreenComponentDefault.kt @@ -6,7 +6,6 @@ import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.bringToFront import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.value.Value -import com.arkivanov.essenty.lifecycle.doOnDestroy import com.arkivanov.mvikotlin.core.store.StoreFactory import com.sedsoftware.blinkly.component.home.HomeScreenComponent import com.sedsoftware.blinkly.component.home.model.HomeScreenTab @@ -27,16 +26,11 @@ import com.sedsoftware.blinkly.domain.external.BlinklyDispatchers import com.sedsoftware.blinkly.domain.external.BlinklySettings import com.sedsoftware.blinkly.domain.external.BlinklyTimeUtils import com.sedsoftware.blinkly.domain.model.ComponentOutput -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @Suppress("LongParameterList") class HomeScreenComponentDefault private constructor( private val componentContext: ComponentContext, - private val dispatchers: BlinklyDispatchers, - private val settings: BlinklySettings, private val homeScreenOutput: (ComponentOutput) -> Unit, private val mainTabComponent: (ComponentContext, (ComponentOutput) -> Unit) -> MainTabComponent, private val trainingsTabComponent: (ComponentContext, (ComponentOutput) -> Unit) -> TrainingsTabComponent, @@ -58,8 +52,6 @@ class HomeScreenComponentDefault private constructor( homeScreenOutput: (ComponentOutput) -> Unit, ) : this( componentContext = componentContext, - dispatchers = dispatchers, - settings = settings, homeScreenOutput = homeScreenOutput, mainTabComponent = { childContext, componentOutput -> MainTabComponentDefault( @@ -107,16 +99,6 @@ class HomeScreenComponentDefault private constructor( } ) - private val scope: CoroutineScope = CoroutineScope(dispatchers.io) - - init { - lifecycle.doOnDestroy { - scope.cancel() - } - - markOnboardingCompleted() - } - private val navigation: StackNavigation = StackNavigation() private val stack: Value> = @@ -161,14 +143,6 @@ class HomeScreenComponentDefault private constructor( HomeScreenComponent.Child.RemindersTab(remindersTabComponent(componentContext, ::onChildOutput)) } - private fun markOnboardingCompleted() { - scope.launch { - if (!settings.onboardingDisplayed) { - settings.onboardingDisplayed = true - } - } - } - @Serializable private sealed interface Config { diff --git a/shared/component/main/child/preferences/src/commonMain/kotlin/com/sedsoftware/blinkly/component/preferences/store/PreferencesStoreProvider.kt b/shared/component/main/child/preferences/src/commonMain/kotlin/com/sedsoftware/blinkly/component/preferences/store/PreferencesStoreProvider.kt index fe7ed2c..b00582a 100644 --- a/shared/component/main/child/preferences/src/commonMain/kotlin/com/sedsoftware/blinkly/component/preferences/store/PreferencesStoreProvider.kt +++ b/shared/component/main/child/preferences/src/commonMain/kotlin/com/sedsoftware/blinkly/component/preferences/store/PreferencesStoreProvider.kt @@ -9,9 +9,11 @@ import com.sedsoftware.blinkly.component.preferences.domain.model.PreferencesDat import com.sedsoftware.blinkly.component.preferences.store.PreferencesStore.Intent import com.sedsoftware.blinkly.component.preferences.store.PreferencesStore.Label import com.sedsoftware.blinkly.component.preferences.store.PreferencesStore.State +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ThemeState import com.sedsoftware.blinkly.utils.StoreProvider import com.sedsoftware.blinkly.utils.unwrap +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext @@ -32,114 +34,154 @@ internal class PreferencesStoreProvider( dispatch(Action.LoadPreferences) }, executorFactory = coroutineExecutorFactory(mainContext) { + var dirtyFlags = DirtyFlags() + var saveJob: Job? = null + onAction { launch { unwrap( result = withContext(ioContext) { manager.load() }, - onSuccess = { data -> dispatch(Msg.PreferencesChanged(data)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = { data -> dispatch(Msg.PreferencesChanged(data, dirtyFlags)) }, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesLoading(throwable))) }, ) } } onIntent { val value = it.value.atLeastOne() - launch { + dirtyFlags = dirtyFlags.copy(blinkBreakCount = true) + dispatch(Msg.BlinkBreakCountChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.saveBlinkBreakCount(value) }, - onSuccess = { dispatch(Msg.BlinkBreakCountChanged(value)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = {}, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } onIntent { val value = it.value.atLeastOne() - launch { + dirtyFlags = dirtyFlags.copy(nearFarFocusCount = true) + dispatch(Msg.NearFarFocusCountChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.saveNearFarFocusCount(value) }, - onSuccess = { dispatch(Msg.NearFarFocusCountChanged(value)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = {}, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } onIntent { val value = it.value.atLeastHalf() - launch { + dirtyFlags = dirtyFlags.copy(nearFarFocusDuration = true) + dispatch(Msg.NearFarFocusDurationChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.saveNearFarFocusDuration(value) }, - onSuccess = { dispatch(Msg.NearFarFocusDurationChanged(value)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = {}, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } onIntent { val value = it.value.atLeastOne() - launch { + dirtyFlags = dirtyFlags.copy(diagonalGazesCount = true) + dispatch(Msg.DiagonalGazesCountChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.saveDiagonalGazesCount(value) }, - onSuccess = { dispatch(Msg.DiagonalGazesCountChanged(value)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = {}, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } onIntent { val value = it.value.atLeastHalf() - launch { + dirtyFlags = dirtyFlags.copy(diagonalGazesDuration = true) + dispatch(Msg.DiagonalGazesDurationChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.saveDiagonalGazesDuration(value) }, - onSuccess = { dispatch(Msg.DiagonalGazesDurationChanged(value)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = {}, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } onIntent { val value = it.value.atLeastOne() - launch { + dirtyFlags = dirtyFlags.copy(figureEightCount = true) + dispatch(Msg.FigureEightCountChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.saveFigureEightCount(value) }, - onSuccess = { dispatch(Msg.FigureEightCountChanged(value)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = {}, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } onIntent { val value = it.value.atLeastOne() - launch { + dirtyFlags = dirtyFlags.copy(clockRollsEachSide = true) + dispatch(Msg.ClockRollsEachSideChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.saveClockRollsEachSide(value) }, - onSuccess = { dispatch(Msg.ClockRollsEachSideChanged(value)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = {}, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } onIntent { val value = it.value.atLeastOne() - launch { + dirtyFlags = dirtyFlags.copy(palmingDuration = true) + dispatch(Msg.PalmingDurationChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.savePalmingDuration(value) }, - onSuccess = { dispatch(Msg.PalmingDurationChanged(value)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = {}, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } onIntent { val value = it.value - launch { + dirtyFlags = dirtyFlags.copy(themeState = true) + dispatch(Msg.ThemeStateChanged(value)) + val previousJob = saveJob + saveJob = launch { + previousJob?.join() unwrap( result = withContext(ioContext) { manager.saveThemeState(value) }, onSuccess = { - dispatch(Msg.ThemeStateChanged(value)) - publish(Label.ThemeStateChanged(value)) + if (state().themeState == value) { + publish(Label.ThemeStateChanged(value)) + } }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.PreferencesSaving(throwable))) }, ) } } @@ -147,15 +189,35 @@ internal class PreferencesStoreProvider( reducer = { msg -> when (msg) { is Msg.PreferencesChanged -> copy( - blinkBreakCount = msg.data.blinkBreakCount, - nearFarFocusCount = msg.data.nearFarFocusCount, - nearFarFocusDuration = msg.data.nearFarFocusDuration, - diagonalGazesCount = msg.data.diagonalGazesCount, - diagonalGazesDuration = msg.data.diagonalGazesDuration, - figureEightCount = msg.data.figureEightCount, - clockRollsEachSide = msg.data.clockRollsEachSide, - palmingDuration = msg.data.palmingDuration, - themeState = msg.data.themeState, + blinkBreakCount = if (msg.dirtyFlags.blinkBreakCount) blinkBreakCount else msg.data.blinkBreakCount, + nearFarFocusCount = if (msg.dirtyFlags.nearFarFocusCount) nearFarFocusCount else msg.data.nearFarFocusCount, + nearFarFocusDuration = if (msg.dirtyFlags.nearFarFocusDuration) { + nearFarFocusDuration + } else { + msg.data.nearFarFocusDuration + }, + diagonalGazesCount = if (msg.dirtyFlags.diagonalGazesCount) { + diagonalGazesCount + } else { + msg.data.diagonalGazesCount + }, + diagonalGazesDuration = if (msg.dirtyFlags.diagonalGazesDuration) { + diagonalGazesDuration + } else { + msg.data.diagonalGazesDuration + }, + figureEightCount = if (msg.dirtyFlags.figureEightCount) { + figureEightCount + } else { + msg.data.figureEightCount + }, + clockRollsEachSide = if (msg.dirtyFlags.clockRollsEachSide) { + clockRollsEachSide + } else { + msg.data.clockRollsEachSide + }, + palmingDuration = if (msg.dirtyFlags.palmingDuration) palmingDuration else msg.data.palmingDuration, + themeState = if (msg.dirtyFlags.themeState) themeState else msg.data.themeState, ) is Msg.BlinkBreakCountChanged -> copy(blinkBreakCount = msg.value) @@ -175,8 +237,20 @@ internal class PreferencesStoreProvider( data object LoadPreferences : Action } + data class DirtyFlags( + val blinkBreakCount: Boolean = false, + val nearFarFocusCount: Boolean = false, + val nearFarFocusDuration: Boolean = false, + val diagonalGazesCount: Boolean = false, + val diagonalGazesDuration: Boolean = false, + val figureEightCount: Boolean = false, + val clockRollsEachSide: Boolean = false, + val palmingDuration: Boolean = false, + val themeState: Boolean = false, + ) + sealed interface Msg { - data class PreferencesChanged(val data: PreferencesData) : Msg + data class PreferencesChanged(val data: PreferencesData, val dirtyFlags: DirtyFlags) : Msg data class BlinkBreakCountChanged(val value: Int) : Msg data class NearFarFocusCountChanged(val value: Int) : Msg data class NearFarFocusDurationChanged(val value: Float) : Msg diff --git a/shared/component/main/src/commonMain/kotlin/com/sedsoftware/blinkly/component/main/store/MainTabStoreProvider.kt b/shared/component/main/src/commonMain/kotlin/com/sedsoftware/blinkly/component/main/store/MainTabStoreProvider.kt index c36e829..ce82ebe 100644 --- a/shared/component/main/src/commonMain/kotlin/com/sedsoftware/blinkly/component/main/store/MainTabStoreProvider.kt +++ b/shared/component/main/src/commonMain/kotlin/com/sedsoftware/blinkly/component/main/store/MainTabStoreProvider.kt @@ -9,9 +9,11 @@ import com.sedsoftware.blinkly.component.main.domain.model.MainTabData import com.sedsoftware.blinkly.component.main.store.MainTabStore.Intent import com.sedsoftware.blinkly.component.main.store.MainTabStore.Label import com.sedsoftware.blinkly.component.main.store.MainTabStore.State +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.HighlightOfTheDay import com.sedsoftware.blinkly.domain.model.Tree import com.sedsoftware.blinkly.domain.model.Workout +import com.sedsoftware.blinkly.domain.model.asBlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import com.sedsoftware.blinkly.utils.unwrap import kotlinx.coroutines.flow.catch @@ -40,7 +42,7 @@ internal class MainTabStoreProvider( onAction { launch { manager.calendar - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(::mainDataLoadingError))) } .collect { calendar -> dispatch(Msg.CalendarUpdated(calendar)) unwrap( @@ -49,7 +51,7 @@ internal class MainTabStoreProvider( dispatch(Msg.DataUpdated(data)) }, onError = { throwable -> - publish(Label.ErrorCaught(throwable)) + publish(Label.ErrorCaught(mainDataLoadingError(throwable))) } ) } @@ -59,7 +61,7 @@ internal class MainTabStoreProvider( onAction { launch { manager.tree - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(::mainDataLoadingError))) } .collect { tree -> dispatch(Msg.TreeUpdated(tree)) } @@ -74,7 +76,7 @@ internal class MainTabStoreProvider( dispatch(Msg.HighlightUpdated(highlight)) }, onError = { throwable -> - publish(Label.ErrorCaught(throwable)) + publish(Label.ErrorCaught(mainDataLoadingError(throwable))) } ) } @@ -121,3 +123,6 @@ internal class MainTabStoreProvider( data class TreeUpdated(val tree: Tree) : Msg } } + +private fun mainDataLoadingError(throwable: Throwable): BlinklyError = + BlinklyError.MainDataLoading(throwable) diff --git a/shared/component/onboarding/child/step4/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step4/integration/OnboardingStep4ComponentDefault.kt b/shared/component/onboarding/child/step4/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step4/integration/OnboardingStep4ComponentDefault.kt index 2c24530..15f9d67 100644 --- a/shared/component/onboarding/child/step4/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step4/integration/OnboardingStep4ComponentDefault.kt +++ b/shared/component/onboarding/child/step4/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step4/integration/OnboardingStep4ComponentDefault.kt @@ -35,7 +35,9 @@ class OnboardingStep4ComponentDefault( } override fun onNextClick() { - onboardingOutput(ComponentOutput.Onboarding.GoToStep5) + if (model.value.checkboxSelected) { + onboardingOutput(ComponentOutput.Onboarding.GoToStep5) + } } override fun onBackClick() { diff --git a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/OnboardingStep5Component.kt b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/OnboardingStep5Component.kt index 0091801..4fb4292 100644 --- a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/OnboardingStep5Component.kt +++ b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/OnboardingStep5Component.kt @@ -24,5 +24,14 @@ interface OnboardingStep5Component { val selectedInterval: Int, val selectedDays: List, val createdRemindersCount: Int, + val initialSetupApplied: Boolean = false, + val isSaving: Boolean = false, + val validationError: ValidationError? = null, ) + + enum class ValidationError { + EMPTY_DAYS, + INVALID_PERIOD, + INVALID_INTERVAL, + } } diff --git a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/integration/Mappers.kt b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/integration/Mappers.kt index 1448684..d187a11 100644 --- a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/integration/Mappers.kt +++ b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/integration/Mappers.kt @@ -1,6 +1,8 @@ package com.sedsoftware.blinkly.component.step5.integration import com.sedsoftware.blinkly.component.step5.OnboardingStep5Component.Model +import com.sedsoftware.blinkly.component.step5.OnboardingStep5Component.ValidationError +import com.sedsoftware.blinkly.component.step5.store.InitialRemindersStore.ValidationError as StoreValidationError import com.sedsoftware.blinkly.component.step5.store.InitialRemindersStore.State internal val stateToModel: (State) -> Model = { @@ -11,5 +13,15 @@ internal val stateToModel: (State) -> Model = { selectedInterval = it.remindIntervalMinutes, selectedDays = it.selectedDays, createdRemindersCount = it.createdReminders.size, + initialSetupApplied = it.initialSetupApplied, + isSaving = it.isSaving, + validationError = it.validationError?.toComponentError(), ) } + +private fun StoreValidationError.toComponentError(): ValidationError = + when (this) { + StoreValidationError.EMPTY_DAYS -> ValidationError.EMPTY_DAYS + StoreValidationError.INVALID_PERIOD -> ValidationError.INVALID_PERIOD + StoreValidationError.INVALID_INTERVAL -> ValidationError.INVALID_INTERVAL + } diff --git a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/integration/OnboardingStep5ComponentDefault.kt b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/integration/OnboardingStep5ComponentDefault.kt index 485a88b..5970b02 100644 --- a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/integration/OnboardingStep5ComponentDefault.kt +++ b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/integration/OnboardingStep5ComponentDefault.kt @@ -61,7 +61,11 @@ class OnboardingStep5ComponentDefault( override val model: Value = store.asValue().map(stateToModel) override fun onNextClick() { - onboardingOutput(ComponentOutput.Onboarding.GoToHomeScreen) + val currentModel = model.value + + if (!currentModel.showInitialSetup || currentModel.initialSetupApplied || currentModel.createdRemindersCount > 0) { + onboardingOutput(ComponentOutput.Onboarding.GoToHomeScreen) + } } override fun onBackClick() { diff --git a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/store/InitialRemindersStore.kt b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/store/InitialRemindersStore.kt index 02ffc8d..f14076c 100644 --- a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/store/InitialRemindersStore.kt +++ b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/store/InitialRemindersStore.kt @@ -29,8 +29,17 @@ internal interface InitialRemindersStore : Store { val remindIntervalMinutes: Int = 20, val selectedDays: List = DayOfWeek.entries.toList(), val createdReminders: List = emptyList(), + val initialSetupApplied: Boolean = false, + val isSaving: Boolean = false, + val validationError: ValidationError? = null, ) + enum class ValidationError { + EMPTY_DAYS, + INVALID_PERIOD, + INVALID_INTERVAL, + } + sealed class Label { data class ErrorCaught(val exception: Throwable) : Label() } diff --git a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/store/InitialRemindersStoreProvider.kt b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/store/InitialRemindersStoreProvider.kt index 03e6551..ccd9423 100644 --- a/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/store/InitialRemindersStoreProvider.kt +++ b/shared/component/onboarding/child/step5/src/commonMain/kotlin/com/sedsoftware/blinkly/component/step5/store/InitialRemindersStoreProvider.kt @@ -8,8 +8,11 @@ import com.sedsoftware.blinkly.component.step5.domain.InitialRemindersManager import com.sedsoftware.blinkly.component.step5.store.InitialRemindersStore.Intent import com.sedsoftware.blinkly.component.step5.store.InitialRemindersStore.Label import com.sedsoftware.blinkly.component.step5.store.InitialRemindersStore.State +import com.sedsoftware.blinkly.component.step5.store.InitialRemindersStore.ValidationError +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.PermissionResult import com.sedsoftware.blinkly.domain.model.Reminder +import com.sedsoftware.blinkly.domain.model.asBlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import com.sedsoftware.blinkly.utils.unwrap import kotlinx.coroutines.flow.catch @@ -46,7 +49,7 @@ internal class InitialRemindersStoreProvider( dispatch(Msg.NotificationPermissionChanged(granted)) }, onError = { throwable -> - publish(Label.ErrorCaught(throwable)) + publish(Label.ErrorCaught(BlinklyError.NotificationPermissionChecking(throwable))) } ) } @@ -55,7 +58,7 @@ internal class InitialRemindersStoreProvider( onAction { launch { manager.observePermissionEvents() - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(BlinklyError::NotificationPermissionChecking))) } .collect { result -> val granted = result == PermissionResult.Granted dispatch(Msg.NotificationPermissionChanged(granted)) @@ -67,19 +70,21 @@ internal class InitialRemindersStoreProvider( onAction { launch { manager.observeReminders() - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(BlinklyError::InitialRemindersLoading))) } .collect { dispatch(Msg.RemindersUpdated(it)) } } } onIntent { + if (state().isSaving) return@onIntent + if (it.show && state().permissionChecked && !state().permissionGranted) { launch { unwrap( result = manager.requestNotificationsPermission(), onSuccess = {}, onError = { throwable -> - publish(Label.ErrorCaught(throwable)) + publish(Label.ErrorCaught(BlinklyError.NotificationPermissionRequesting(throwable))) } ) } @@ -89,34 +94,59 @@ internal class InitialRemindersStoreProvider( } onIntent { + if (state().isSaving) return@onIntent + dispatch(Msg.TimeSelectedFrom(it.time)) } onIntent { + if (state().isSaving) return@onIntent + dispatch(Msg.TimeSelectedTo(it.time)) } onIntent { + if (state().isSaving) return@onIntent + dispatch(Msg.IntervalChanged(it.interval)) } onIntent { + if (state().isSaving) return@onIntent + dispatch(Msg.WeekDayToggled(it.weekDay)) } onIntent { + if (state().isSaving || !state().showInitialSetup) return@onIntent + + val validationError = state().validationError() + + if (validationError != null) { + dispatch(Msg.ValidationFailed(validationError)) + return@onIntent + } + + val setupState = state() + dispatch(Msg.SavingChanged(true)) + launch { unwrap( - result = withContext(ioContext) { manager.setupInitial(state()) }, - onSuccess = {}, + result = withContext(ioContext) { manager.setupInitial(setupState) }, + onSuccess = { + dispatch(Msg.InitialSetupApplied) + }, onError = { throwable -> - publish(Label.ErrorCaught(throwable)) + dispatch(Msg.SavingChanged(false)) + publish(Label.ErrorCaught(BlinklyError.InitialRemindersCreating(throwable))) } ) } } onIntent { + if (state().isSaving) return@onIntent + launch { unwrap( result = withContext(ioContext) { manager.clearInitial() }, @@ -124,7 +154,7 @@ internal class InitialRemindersStoreProvider( dispatch(Msg.RemindersDeleted) }, onError = { throwable -> - publish(Label.ErrorCaught(throwable)) + publish(Label.ErrorCaught(BlinklyError.InitialRemindersClearing(throwable))) } ) } @@ -146,18 +176,26 @@ internal class InitialRemindersStoreProvider( is Msg.ShowInitialSetupChanged -> copy( showInitialSetup = msg.checked, + initialSetupApplied = if (msg.checked) initialSetupApplied else false, + validationError = null, ) is Msg.TimeSelectedFrom -> copy( remindFrom = msg.time, + initialSetupApplied = false, + validationError = null, ) is Msg.TimeSelectedTo -> copy( remindUntil = msg.time, + initialSetupApplied = false, + validationError = null, ) is Msg.IntervalChanged -> copy( remindIntervalMinutes = msg.interval, + initialSetupApplied = false, + validationError = null, ) is Msg.WeekDayToggled -> copy( @@ -165,11 +203,28 @@ internal class InitialRemindersStoreProvider( selectedDays - msg.weekDay } else { selectedDays + msg.weekDay - } + }, + initialSetupApplied = false, + validationError = null, ) is Msg.RemindersDeleted -> copy( createdReminders = emptyList(), + initialSetupApplied = false, + ) + + is Msg.ValidationFailed -> copy( + validationError = msg.error, + ) + + is Msg.SavingChanged -> copy( + isSaving = msg.saving, + ) + + is Msg.InitialSetupApplied -> copy( + isSaving = false, + initialSetupApplied = true, + validationError = null, ) } } @@ -191,5 +246,16 @@ internal class InitialRemindersStoreProvider( data class IntervalChanged(val interval: Int) : Msg data class WeekDayToggled(val weekDay: DayOfWeek) : Msg data object RemindersDeleted : Msg + data class ValidationFailed(val error: ValidationError) : Msg + data class SavingChanged(val saving: Boolean) : Msg + data object InitialSetupApplied : Msg } + + private fun State.validationError(): ValidationError? = + when { + selectedDays.isEmpty() -> ValidationError.EMPTY_DAYS + remindFrom >= remindUntil -> ValidationError.INVALID_PERIOD + remindIntervalMinutes <= 0 -> ValidationError.INVALID_INTERVAL + else -> null + } } diff --git a/shared/component/progress/child/achievements/src/commonMain/kotlin/com/sedsoftware/blinkly/component/achievements/store/AchievementsStoreProvider.kt b/shared/component/progress/child/achievements/src/commonMain/kotlin/com/sedsoftware/blinkly/component/achievements/store/AchievementsStoreProvider.kt index 0824b26..bb057c8 100644 --- a/shared/component/progress/child/achievements/src/commonMain/kotlin/com/sedsoftware/blinkly/component/achievements/store/AchievementsStoreProvider.kt +++ b/shared/component/progress/child/achievements/src/commonMain/kotlin/com/sedsoftware/blinkly/component/achievements/store/AchievementsStoreProvider.kt @@ -12,6 +12,8 @@ import com.sedsoftware.blinkly.component.achievements.store.AchievementsStore.St import com.sedsoftware.blinkly.domain.BlinklyAchievementsWatcher import com.sedsoftware.blinkly.domain.model.Achievement import com.sedsoftware.blinkly.domain.model.AchievementType +import com.sedsoftware.blinkly.domain.model.BlinklyError +import com.sedsoftware.blinkly.domain.model.asBlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch @@ -35,7 +37,7 @@ internal class AchievementsStoreProvider( onAction { launch { achievementsWatcher.achievements - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(BlinklyError::AchievementsLoading))) } .collect { achievements -> dispatch(Msg.AchievementsUpdated(achievements)) } diff --git a/shared/component/progress/child/garden/src/commonMain/kotlin/com/sedsoftware/blinkly/component/garden/store/GardenStoreProvider.kt b/shared/component/progress/child/garden/src/commonMain/kotlin/com/sedsoftware/blinkly/component/garden/store/GardenStoreProvider.kt index 9456232..ac1acd7 100644 --- a/shared/component/progress/child/garden/src/commonMain/kotlin/com/sedsoftware/blinkly/component/garden/store/GardenStoreProvider.kt +++ b/shared/component/progress/child/garden/src/commonMain/kotlin/com/sedsoftware/blinkly/component/garden/store/GardenStoreProvider.kt @@ -8,8 +8,10 @@ import com.sedsoftware.blinkly.component.garden.store.GardenStore.Intent import com.sedsoftware.blinkly.component.garden.store.GardenStore.Label import com.sedsoftware.blinkly.component.garden.store.GardenStore.State import com.sedsoftware.blinkly.domain.BlinklyTreeProgressWatcher +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.TreeGarden import com.sedsoftware.blinkly.domain.model.TreeType +import com.sedsoftware.blinkly.domain.model.asBlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch @@ -33,7 +35,7 @@ internal class GardenStoreProvider( onAction { launch { treeProgressWatcher.garden - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(BlinklyError::GardenLoading))) } .collect { garden -> dispatch(Msg.GardenUpdated(garden)) } diff --git a/shared/component/progress/src/commonMain/kotlin/com/sedsoftware/blinkly/component/progress/store/ProgressTabStoreProvider.kt b/shared/component/progress/src/commonMain/kotlin/com/sedsoftware/blinkly/component/progress/store/ProgressTabStoreProvider.kt index 3369955..14acf9d 100644 --- a/shared/component/progress/src/commonMain/kotlin/com/sedsoftware/blinkly/component/progress/store/ProgressTabStoreProvider.kt +++ b/shared/component/progress/src/commonMain/kotlin/com/sedsoftware/blinkly/component/progress/store/ProgressTabStoreProvider.kt @@ -10,7 +10,9 @@ import com.sedsoftware.blinkly.component.progress.store.ProgressTabStore.Intent import com.sedsoftware.blinkly.component.progress.store.ProgressTabStore.Label import com.sedsoftware.blinkly.component.progress.store.ProgressTabStore.State import com.sedsoftware.blinkly.domain.model.Achievement +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.Tree +import com.sedsoftware.blinkly.domain.model.asBlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import com.sedsoftware.blinkly.utils.unwrap import kotlinx.coroutines.flow.catch @@ -39,12 +41,12 @@ internal class ProgressTabStoreProvider( onAction { launch { manager.calendar - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(::progressDataLoadingError))) } .collect { calendar -> unwrap( result = withContext(ioContext) { manager.calculateCalendarWeeks(calendar) }, onSuccess = { weeks -> dispatch(Msg.CalendarUpdated(weeks)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onError = { throwable -> publish(Label.ErrorCaught(progressDataLoadingError(throwable))) }, ) } } @@ -53,7 +55,7 @@ internal class ProgressTabStoreProvider( onAction { launch { manager.tree - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(::progressDataLoadingError))) } .collect { tree -> dispatch(Msg.TreeUpdated(tree)) } @@ -63,12 +65,12 @@ internal class ProgressTabStoreProvider( onAction { launch { manager.achievements - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(::progressDataLoadingError))) } .collect { achievements -> unwrap( result = withContext(ioContext) { manager.calculateRecentAchievements(achievements) }, onSuccess = { recent -> dispatch(Msg.AchievementsUpdated(recent)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onError = { throwable -> publish(Label.ErrorCaught(progressDataLoadingError(throwable))) }, ) } } @@ -95,3 +97,6 @@ internal class ProgressTabStoreProvider( data class AchievementsUpdated(val achievements: List) : Msg } } + +private fun progressDataLoadingError(throwable: Throwable): BlinklyError = + BlinklyError.ProgressDataLoading(throwable) diff --git a/shared/component/reminders/child/newreminder/src/commonMain/kotlin/com/sedsoftware/blinkly/component/newreminder/store/AddNewReminderStoreProvider.kt b/shared/component/reminders/child/newreminder/src/commonMain/kotlin/com/sedsoftware/blinkly/component/newreminder/store/AddNewReminderStoreProvider.kt index 7e05935..2d97317 100644 --- a/shared/component/reminders/child/newreminder/src/commonMain/kotlin/com/sedsoftware/blinkly/component/newreminder/store/AddNewReminderStoreProvider.kt +++ b/shared/component/reminders/child/newreminder/src/commonMain/kotlin/com/sedsoftware/blinkly/component/newreminder/store/AddNewReminderStoreProvider.kt @@ -9,6 +9,7 @@ import com.sedsoftware.blinkly.component.newreminder.store.AddNewReminderStore.I import com.sedsoftware.blinkly.component.newreminder.store.AddNewReminderStore.Label import com.sedsoftware.blinkly.component.newreminder.store.AddNewReminderStore.State import com.sedsoftware.blinkly.component.newreminder.store.AddNewReminderStore.ValidationError +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import com.sedsoftware.blinkly.utils.unwrap import kotlinx.coroutines.launch @@ -97,7 +98,7 @@ internal class AddNewReminderStoreProvider( }, onError = { throwable -> dispatch(Msg.SavingChanged(false)) - publish(Label.ErrorCaught(throwable)) + publish(Label.ErrorCaught(BlinklyError.ReminderCreating(throwable))) }, ) } diff --git a/shared/component/reminders/src/commonMain/kotlin/com/sedsoftware/blinkly/component/reminders/store/RemindersStore.kt b/shared/component/reminders/src/commonMain/kotlin/com/sedsoftware/blinkly/component/reminders/store/RemindersStore.kt index 77ebc48..2f886ba 100644 --- a/shared/component/reminders/src/commonMain/kotlin/com/sedsoftware/blinkly/component/reminders/store/RemindersStore.kt +++ b/shared/component/reminders/src/commonMain/kotlin/com/sedsoftware/blinkly/component/reminders/store/RemindersStore.kt @@ -17,6 +17,8 @@ internal interface RemindersStore : Store { data class State( val reminders: List = emptyList(), val deletedReminder: Reminder? = null, + val pendingDeleteUuid: String? = null, + val isRestoringDeleted: Boolean = false, ) sealed class Label { diff --git a/shared/component/reminders/src/commonMain/kotlin/com/sedsoftware/blinkly/component/reminders/store/RemindersStoreProvider.kt b/shared/component/reminders/src/commonMain/kotlin/com/sedsoftware/blinkly/component/reminders/store/RemindersStoreProvider.kt index b273f5d..e6722de 100644 --- a/shared/component/reminders/src/commonMain/kotlin/com/sedsoftware/blinkly/component/reminders/store/RemindersStoreProvider.kt +++ b/shared/component/reminders/src/commonMain/kotlin/com/sedsoftware/blinkly/component/reminders/store/RemindersStoreProvider.kt @@ -8,7 +8,9 @@ import com.sedsoftware.blinkly.component.reminders.domain.RemindersManager import com.sedsoftware.blinkly.component.reminders.store.RemindersStore.Intent import com.sedsoftware.blinkly.component.reminders.store.RemindersStore.Label import com.sedsoftware.blinkly.component.reminders.store.RemindersStore.State +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.Reminder +import com.sedsoftware.blinkly.domain.model.asBlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import com.sedsoftware.blinkly.utils.unwrap import kotlinx.coroutines.flow.catch @@ -35,31 +37,47 @@ internal class RemindersStoreProvider( onAction { launch { manager.observeReminders() - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(BlinklyError::RemindersLoading))) } .collect { dispatch(Msg.RemindersUpdated(it)) } } } onIntent { intent -> + if (state().pendingDeleteUuid != null) return@onIntent + val reminder = state().reminders.firstOrNull { it.uuid == intent.uuid } ?: return@onIntent + dispatch(Msg.DeleteStarted(intent.uuid)) launch { unwrap( result = withContext(ioContext) { manager.deleteReminder(intent.uuid) }, - onSuccess = { dispatch(Msg.ReminderDeleted(reminder)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onSuccess = { + if (state().pendingDeleteUuid == intent.uuid) { + dispatch(Msg.ReminderDeleted(reminder)) + } + }, + onError = { throwable -> + dispatch(Msg.DeleteFinished) + publish(Label.ErrorCaught(BlinklyError.ReminderDeleting(throwable))) + }, ) } } onIntent { + if (state().isRestoringDeleted) return@onIntent + val reminder = state().deletedReminder ?: return@onIntent + dispatch(Msg.RestoreStarted) launch { unwrap( result = withContext(ioContext) { manager.restoreReminder(reminder) }, onSuccess = { dispatch(Msg.DeletedMessageShown) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onError = { throwable -> + dispatch(Msg.RestoreFinished) + publish(Label.ErrorCaught(BlinklyError.ReminderRestoring(throwable))) + }, ) } } @@ -77,10 +95,28 @@ internal class RemindersStoreProvider( is Msg.ReminderDeleted -> copy( reminders = reminders.filterNot { it.uuid == msg.item.uuid }, deletedReminder = msg.item, + pendingDeleteUuid = null, ) is Msg.DeletedMessageShown -> copy( deletedReminder = null, + isRestoringDeleted = false, + ) + + is Msg.DeleteStarted -> copy( + pendingDeleteUuid = msg.uuid, + ) + + is Msg.DeleteFinished -> copy( + pendingDeleteUuid = null, + ) + + is Msg.RestoreStarted -> copy( + isRestoringDeleted = true, + ) + + is Msg.RestoreFinished -> copy( + isRestoringDeleted = false, ) } }, @@ -94,5 +130,9 @@ internal class RemindersStoreProvider( data class RemindersUpdated(val items: List) : Msg data class ReminderDeleted(val item: Reminder) : Msg data object DeletedMessageShown : Msg + data class DeleteStarted(val uuid: String) : Msg + data object DeleteFinished : Msg + data object RestoreStarted : Msg + data object RestoreFinished : Msg } } diff --git a/shared/component/root/src/commonMain/kotlin/com/sedsoftware/blinkly/component/root/RootComponent.kt b/shared/component/root/src/commonMain/kotlin/com/sedsoftware/blinkly/component/root/RootComponent.kt index 91b932b..7780e65 100644 --- a/shared/component/root/src/commonMain/kotlin/com/sedsoftware/blinkly/component/root/RootComponent.kt +++ b/shared/component/root/src/commonMain/kotlin/com/sedsoftware/blinkly/component/root/RootComponent.kt @@ -10,12 +10,15 @@ import com.sedsoftware.blinkly.component.home.HomeScreenComponent import com.sedsoftware.blinkly.component.newreminder.AddNewReminderComponent import com.sedsoftware.blinkly.component.onboarding.OnboardingComponent import com.sedsoftware.blinkly.component.preferences.PreferencesComponent +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ThemeState +import kotlinx.coroutines.flow.SharedFlow interface RootComponent : BackHandlerOwner { val childStack: Value> val themeState: Value + val errors: SharedFlow fun onBack() diff --git a/shared/component/root/src/commonMain/kotlin/com/sedsoftware/blinkly/component/root/integration/RootComponentDefault.kt b/shared/component/root/src/commonMain/kotlin/com/sedsoftware/blinkly/component/root/integration/RootComponentDefault.kt index aa5b188..8a39b31 100644 --- a/shared/component/root/src/commonMain/kotlin/com/sedsoftware/blinkly/component/root/integration/RootComponentDefault.kt +++ b/shared/component/root/src/commonMain/kotlin/com/sedsoftware/blinkly/component/root/integration/RootComponentDefault.kt @@ -10,7 +10,6 @@ import com.arkivanov.decompose.router.stack.push import com.arkivanov.decompose.router.stack.replaceCurrent import com.arkivanov.decompose.value.MutableValue import com.arkivanov.decompose.value.Value -import com.arkivanov.essenty.backhandler.BackCallback import com.arkivanov.essenty.lifecycle.doOnDestroy import com.arkivanov.mvikotlin.core.store.StoreFactory import com.sedsoftware.blinkly.component.achievements.AchievementsComponent @@ -41,17 +40,18 @@ import com.sedsoftware.blinkly.domain.external.BlinklyDispatchers import com.sedsoftware.blinkly.domain.external.BlinklyNotifier import com.sedsoftware.blinkly.domain.external.BlinklySettings import com.sedsoftware.blinkly.domain.external.BlinklyTimeUtils +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.ExerciseBlock import com.sedsoftware.blinkly.domain.model.ThemeState -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.cancel +import com.sedsoftware.blinkly.domain.model.asBlinklyError +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.serialization.Serializable @Suppress("LongParameterList") class RootComponentDefault private constructor( private val settings: BlinklySettings, - private val dispatchers: BlinklyDispatchers, private val componentContext: ComponentContext, private val onboardingComponent: (ComponentContext, (ComponentOutput) -> Unit) -> OnboardingComponent, private val homeScreenComponent: (ComponentContext, (ComponentOutput) -> Unit) -> HomeScreenComponent, @@ -60,6 +60,7 @@ class RootComponentDefault private constructor( private val achievementsComponent: (ComponentContext, (ComponentOutput) -> Unit) -> AchievementsComponent, private val gardenComponent: (ComponentContext, (ComponentOutput) -> Unit) -> GardenComponent, private val addNewReminderComponent: (ComponentContext, (ComponentOutput) -> Unit) -> AddNewReminderComponent, + private val releaseBeeper: () -> Unit, ) : RootComponent, ComponentContext by componentContext { @Suppress("UnusedPrivateProperty") @@ -81,7 +82,6 @@ class RootComponentDefault private constructor( treeProgressWatcher: BlinklyTreeProgressWatcher, ) : this( componentContext = componentContext, - dispatchers = dispatchers, settings = settings, onboardingComponent = { childContext, output -> OnboardingComponentDefault(childContext, storeFactory, reminderManager, notifier, dispatchers, output) @@ -130,21 +130,17 @@ class RootComponentDefault private constructor( addNewReminderOutput = output, ) }, + releaseBeeper = beeper::release, ) private val navigation: StackNavigation = StackNavigation() - private val scope: CoroutineScope = CoroutineScope(dispatchers.io) private val themeStateValue: MutableValue = MutableValue(settings.themeState) - - private val backCallback: BackCallback = BackCallback { onBack() } + private val errorEvents: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = ERROR_BUFFER_CAPACITY) init { lifecycle.doOnDestroy { - scope.cancel() + releaseBeeper() } - - backCallback.isEnabled = false - backHandler.register(backCallback) } private val stack: Value> = @@ -158,6 +154,7 @@ class RootComponentDefault private constructor( override val childStack: Value> = stack override val themeState: Value = themeStateValue + override val errors = errorEvents.asSharedFlow() override fun onBack() { navigation.pop() @@ -190,8 +187,8 @@ class RootComponentDefault private constructor( private fun onChildOutput(output: ComponentOutput) { when (output) { is ComponentOutput.Onboarding.GoToHomeScreen -> { + settings.onboardingDisplayed = true navigation.replaceCurrent(Config.HomeScreen) - backCallback.isEnabled = true } is ComponentOutput.Main.OpenPreferences -> { @@ -225,7 +222,11 @@ class RootComponentDefault private constructor( } is ComponentOutput.Common.ErrorCaught -> { - Logger.e { "Blinkly error caught: ${output.throwable.message}" } + val error = output.throwable.asBlinklyError(BlinklyError::Unknown) + val cause = error.cause ?: error + + Logger.e(cause) { "Blinkly error caught: ${error.message}" } + errorEvents.tryEmit(error) } else -> Unit @@ -263,4 +264,8 @@ class RootComponentDefault private constructor( @Serializable data object AddNewReminder : Config } + + private companion object { + const val ERROR_BUFFER_CAPACITY = 16 + } } diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/ComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/ComponentTest.kt index 23211de..d8ac24e 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/ComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/ComponentTest.kt @@ -8,6 +8,7 @@ import com.arkivanov.essenty.lifecycle.resume import com.arkivanov.mvikotlin.core.utils.isAssertOnMainThreadEnabled import com.sedsoftware.blinkly.domain.external.BlinklyDispatchers import com.sedsoftware.blinkly.domain.external.BlinklySettings +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher @@ -54,5 +55,10 @@ abstract class ComponentTest { lifecycle.destroy() } + protected inline fun componentOutputContainsErrorCausedBy(cause: Throwable): Boolean = + componentOutput + .filterIsInstance() + .any { output -> output.throwable is T && output.throwable.cause === cause } + abstract fun createComponent(): Component } diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/achievements/AchievementsComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/achievements/AchievementsComponentTest.kt index 349f2f2..efe3d31 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/achievements/AchievementsComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/achievements/AchievementsComponentTest.kt @@ -17,6 +17,7 @@ import com.sedsoftware.blinkly.domain.BlinklyAchievementsWatcher import com.sedsoftware.blinkly.domain.model.Achievement import com.sedsoftware.blinkly.domain.model.AchievementLevel import com.sedsoftware.blinkly.domain.model.AchievementType +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -179,10 +180,7 @@ class AchievementsComponentTest : ComponentTest() { // then assertThat(testComponent).isInstanceOf(AchievementsComponent::class) - assertThat( - componentOutput.filterIsInstance() - .any { it.throwable.message == exception.message } - ).isTrue() + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } override fun createComponent(): AchievementsComponent = diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/garden/GardenComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/garden/GardenComponentTest.kt index 03fca82..d227c8d 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/garden/GardenComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/garden/GardenComponentTest.kt @@ -11,6 +11,7 @@ import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.sedsoftware.blinkly.component.ComponentTest import com.sedsoftware.blinkly.component.garden.integration.GardenComponentDefault import com.sedsoftware.blinkly.domain.BlinklyTreeProgressWatcher +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.Tree import com.sedsoftware.blinkly.domain.model.TreeGarden @@ -137,10 +138,7 @@ class GardenComponentTest : ComponentTest() { // then assertThat(testComponent).isInstanceOf(GardenComponent::class) - assertThat( - componentOutput.filterIsInstance() - .any { it.throwable.message == exception.message } - ).isTrue() + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } override fun createComponent(): GardenComponent = diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/home/HomeScreenComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/home/HomeScreenComponentTest.kt index b944d4f..3593778 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/home/HomeScreenComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/home/HomeScreenComponentTest.kt @@ -1,6 +1,7 @@ package com.sedsoftware.blinkly.component.home import assertk.assertThat +import assertk.assertions.isFalse import assertk.assertions.isTrue import com.arkivanov.decompose.DefaultComponentContext import com.arkivanov.decompose.router.stack.active @@ -45,11 +46,11 @@ class HomeScreenComponentTest : ComponentTest() { } @Test - fun `when component created then onboarding displayed flag is true`() = runTest(testScheduler) { + fun `when component created then onboarding displayed flag is not changed by home`() = runTest(testScheduler) { // given // when // then - assertThat(fakeSettings.onboardingDisplayed).isTrue() + assertThat(fakeSettings.onboardingDisplayed).isFalse() } @Test diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/main/MainTabComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/main/MainTabComponentTest.kt index c16677c..280d969 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/main/MainTabComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/main/MainTabComponentTest.kt @@ -17,6 +17,7 @@ import com.sedsoftware.blinkly.domain.BlinklyHighlightsProvider import com.sedsoftware.blinkly.domain.BlinklyTreeProgressWatcher import com.sedsoftware.blinkly.domain.external.BlinklySettings import com.sedsoftware.blinkly.domain.external.BlinklyTimeUtils +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.Exercise import com.sedsoftware.blinkly.domain.model.ExerciseBlock @@ -291,10 +292,7 @@ class MainTabComponentTest : ComponentTest() { // then assertThat(testComponent).isInstanceOf(MainTabComponent::class) - assertThat( - componentOutput.filterIsInstance() - .any { it.throwable.message == exception.message } - ).isTrue() + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } override fun createComponent(): MainTabComponent = diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/newreminder/AddNewReminderComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/newreminder/AddNewReminderComponentTest.kt index f0faf25..2a065c9 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/newreminder/AddNewReminderComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/newreminder/AddNewReminderComponentTest.kt @@ -10,6 +10,7 @@ import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.sedsoftware.blinkly.component.ComponentTest import com.sedsoftware.blinkly.component.newreminder.integration.AddNewReminderComponentDefault import com.sedsoftware.blinkly.domain.BlinklyReminderManager +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.Reminder import kotlinx.coroutines.flow.Flow @@ -138,10 +139,7 @@ class AddNewReminderComponentTest : ComponentTest() { testScheduler.advanceUntilIdle() // then - assertThat( - componentOutput.filterIsInstance() - .any { it.throwable.message == exception.message } - ).isTrue() + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } override fun createComponent(): AddNewReminderComponent = diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/onboarding/OnboardingComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/onboarding/OnboardingComponentTest.kt index 3c862af..955158c 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/onboarding/OnboardingComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/onboarding/OnboardingComponentTest.kt @@ -18,6 +18,7 @@ import com.sedsoftware.blinkly.component.onboarding.integration.OnboardingCompon import com.sedsoftware.blinkly.component.step5.OnboardingStep5Component import com.sedsoftware.blinkly.domain.BlinklyReminderManager import com.sedsoftware.blinkly.domain.external.BlinklyNotifier +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.PermissionResult import com.sedsoftware.blinkly.domain.model.Reminder @@ -102,6 +103,7 @@ class OnboardingComponentTest : ComponentTest() { assertThat(childStep4).isNotNull() assertThat(component.childStack.items.size).isEqualTo(4) // when + childStep4?.component?.onCheckboxSelect(true) childStep4?.component?.onNextClick() // then val childStep5 = component.childStack.active.instance as? OnboardingComponent.Child.Step5 @@ -123,6 +125,7 @@ class OnboardingComponentTest : ComponentTest() { var childStep3 = component.childStack.active.instance as? OnboardingComponent.Child.Step3 childStep3?.component?.onNextClick() var childStep4 = component.childStack.active.instance as? OnboardingComponent.Child.Step4 + childStep4?.component?.onCheckboxSelect(true) childStep4?.component?.onNextClick() val childStep5 = component.childStack.active.instance as? OnboardingComponent.Child.Step5 assertThat(childStep5).isNotNull() @@ -182,6 +185,25 @@ class OnboardingComponentTest : ComponentTest() { assertThat(childStep4?.component?.model?.value?.checkboxSelected).isEqualTo(false) } + @Test + fun `when disclaimer checkbox not selected then next step is not opened`() = runTest(testScheduler) { + // given + val childStep1 = component.childStack.active.instance as? OnboardingComponent.Child.Step1 + childStep1?.component?.onNextClick() + val childStep2 = component.childStack.active.instance as? OnboardingComponent.Child.Step2 + childStep2?.component?.onNextClick() + val childStep3 = component.childStack.active.instance as? OnboardingComponent.Child.Step3 + childStep3?.component?.onNextClick() + val childStep4 = component.childStack.active.instance as? OnboardingComponent.Child.Step4 + + // when + childStep4?.component?.onNextClick() + + // then + assertThat(component.childStack.active.instance is OnboardingComponent.Child.Step4).isTrue() + assertThat(component.childStack.items.size).isEqualTo(4) + } + @Test fun `when last step activated then subscribe store for events`() = runTest(testScheduler) { // given @@ -205,7 +227,7 @@ class OnboardingComponentTest : ComponentTest() { testScheduler.advanceUntilIdle() // then - assertThat(componentOutput).contains(ComponentOutput.Common.ErrorCaught(exception)) + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } @Test @@ -258,7 +280,7 @@ class OnboardingComponentTest : ComponentTest() { testScheduler.advanceUntilIdle() // then - assertThat(componentOutput).contains(ComponentOutput.Common.ErrorCaught(exception)) + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } @Test @@ -275,7 +297,7 @@ class OnboardingComponentTest : ComponentTest() { testScheduler.advanceUntilIdle() // then - assertThat(componentOutput).contains(ComponentOutput.Common.ErrorCaught(exception)) + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } @Test @@ -292,7 +314,7 @@ class OnboardingComponentTest : ComponentTest() { testScheduler.advanceUntilIdle() // then - assertThat(componentOutput).contains(ComponentOutput.Common.ErrorCaught(exception)) + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } @Test @@ -355,16 +377,19 @@ class OnboardingComponentTest : ComponentTest() { fun `when creating reminders failed then error output emitted`() = runTest(testScheduler) { // given val exception = IllegalStateException("create reminders failed") + everySuspend { notifierMock.isNotificationPermissionGranted() } returns true val step5 = getStep5Component() everySuspend { reminderManagerMock.scheduleWeeklyDayPeriod(any(), any(), any(), any()) } throws exception testScheduler.advanceUntilIdle() + step5.onInitialSetupChoice(true) + testScheduler.advanceUntilIdle() // when step5.onCreateReminders() testScheduler.advanceUntilIdle() // then - assertThat(componentOutput).contains(ComponentOutput.Common.ErrorCaught(exception)) + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } @Test @@ -380,7 +405,7 @@ class OnboardingComponentTest : ComponentTest() { testScheduler.advanceUntilIdle() // then - assertThat(componentOutput).contains(ComponentOutput.Common.ErrorCaught(exception)) + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } @Test @@ -410,11 +435,37 @@ class OnboardingComponentTest : ComponentTest() { } } + @Test + fun `when initial setup is selected then next waits for reminders creation`() = runTest(testScheduler) { + // given + val step5 = getStep5Component() + everySuspend { notifierMock.isNotificationPermissionGranted() } returns true + testScheduler.advanceUntilIdle() + step5.onInitialSetupChoice(true) + testScheduler.advanceUntilIdle() + + // when + step5.onNextClick() + + // then + assertThat(componentOutput).doesNotContain(ComponentOutput.Onboarding.GoToHomeScreen) + + // when + step5.onCreateReminders() + testScheduler.advanceUntilIdle() + step5.onNextClick() + + // then + assertThat(componentOutput).contains(ComponentOutput.Onboarding.GoToHomeScreen) + } + private fun getStep5Component(): OnboardingStep5Component { (component.childStack.active.instance as? OnboardingComponent.Child.Step1)?.component?.onNextClick() (component.childStack.active.instance as? OnboardingComponent.Child.Step2)?.component?.onNextClick() (component.childStack.active.instance as? OnboardingComponent.Child.Step3)?.component?.onNextClick() - (component.childStack.active.instance as? OnboardingComponent.Child.Step4)?.component?.onNextClick() + val step4 = component.childStack.active.instance as? OnboardingComponent.Child.Step4 + step4?.component?.onCheckboxSelect(true) + step4?.component?.onNextClick() return (component.childStack.active.instance as? OnboardingComponent.Child.Step5)?.component!! } diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/progress/ProgressTabComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/progress/ProgressTabComponentTest.kt index 7346013..bc3daf2 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/progress/ProgressTabComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/progress/ProgressTabComponentTest.kt @@ -17,6 +17,7 @@ import com.sedsoftware.blinkly.domain.external.BlinklyTimeUtils import com.sedsoftware.blinkly.domain.model.Achievement import com.sedsoftware.blinkly.domain.model.AchievementLevel import com.sedsoftware.blinkly.domain.model.AchievementType +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.Exercise import com.sedsoftware.blinkly.domain.model.ExerciseBlock @@ -128,10 +129,7 @@ class ProgressTabComponentTest : ComponentTest() { // then assertThat(testComponent).isInstanceOf(ProgressTabComponent::class) - assertThat( - componentOutput.filterIsInstance() - .any { it.throwable.message == exception.message } - ).isTrue() + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } override fun createComponent(): ProgressTabComponent = diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/reminders/RemindersTabComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/reminders/RemindersTabComponentTest.kt index e81d702..3390be4 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/reminders/RemindersTabComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/reminders/RemindersTabComponentTest.kt @@ -10,10 +10,12 @@ import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.sedsoftware.blinkly.component.ComponentTest import com.sedsoftware.blinkly.component.reminders.integration.RemindersTabComponentDefault import com.sedsoftware.blinkly.domain.BlinklyReminderManager +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.Reminder import com.sedsoftware.blinkly.domain.model.ReminderInterval import com.sedsoftware.blinkly.domain.model.ReminderType +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest @@ -136,10 +138,39 @@ class RemindersTabComponentTest : ComponentTest() { // then assertThat(component.model.value.reminders.map { it.uuid }).isEqualTo(listOf("daily")) assertThat(component.model.value.deletedReminder).isNull() - assertThat( - componentOutput.filterIsInstance() - .any { it.throwable.message == exception.message } - ).isTrue() + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() + } + + @Test + fun `when delete already pending then next delete is ignored`() = runTest(testScheduler) { + // given + val firstReminder = reminder( + uuid = "first", + date = LocalDateTime(year = 2026, month = 6, day = 20, hour = 10, minute = 0), + interval = ReminderInterval.DAILY, + ) + val secondReminder = reminder( + uuid = "second", + date = LocalDateTime(year = 2026, month = 6, day = 20, hour = 11, minute = 0), + interval = ReminderInterval.DAILY, + ) + val cancelGate = CompletableDeferred() + reminderManager.cancelGate = cancelGate + reminderManager.reminders.value = listOf(firstReminder, secondReminder) + testScheduler.advanceUntilIdle() + + // when + component.onDeleteReminder("first") + testScheduler.advanceUntilIdle() + component.onDeleteReminder("second") + testScheduler.advanceUntilIdle() + cancelGate.complete(Unit) + testScheduler.advanceUntilIdle() + + // then + assertThat(reminderManager.cancelled).isEqualTo(listOf("first")) + assertThat(component.model.value.deletedReminder?.uuid).isEqualTo("first") + assertThat(component.model.value.reminders.map { it.uuid }).isEqualTo(listOf("second")) } override fun createComponent(): RemindersTabComponent = @@ -171,6 +202,7 @@ class RemindersTabComponentTest : ComponentTest() { val scheduledDaily: MutableList = mutableListOf() val scheduledWeeklySingle: MutableList> = mutableListOf() var cancelException: Throwable? = null + var cancelGate: CompletableDeferred? = null override fun createdReminders(): Flow> = reminders @@ -192,6 +224,7 @@ class RemindersTabComponentTest : ComponentTest() { override suspend fun rescheduleAll() = Unit override suspend fun cancel(uuid: String) { + cancelGate?.await() cancelException?.let { throw it } cancelled.add(uuid) reminders.value = reminders.value.filterNot { it.uuid == uuid } diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/root/RootComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/root/RootComponentTest.kt index 50b88c5..7037699 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/root/RootComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/root/RootComponentTest.kt @@ -23,6 +23,7 @@ import com.sedsoftware.blinkly.domain.external.BlinklyDatabase import com.sedsoftware.blinkly.domain.external.BlinklyNotifier import com.sedsoftware.blinkly.domain.external.BlinklySettings import com.sedsoftware.blinkly.domain.external.BlinklyTimeUtils +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ThemeState import dev.mokkery.answering.returns import dev.mokkery.answering.throws @@ -31,6 +32,7 @@ import dev.mokkery.everySuspend import dev.mokkery.matcher.any import dev.mokkery.mock import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone @@ -261,6 +263,8 @@ class RootComponentTest : ComponentTest() { fun `when child emits error then root keeps current child`() = runTest(testScheduler) { // given val exception = IllegalStateException("permission check failed") + val errors = mutableListOf() + val collectJob = launch { component.errors.collect { errors.add(it) } } prepareStep5Dependencies() everySuspend { notifierMock.isNotificationPermissionGranted() } throws exception @@ -271,6 +275,8 @@ class RootComponentTest : ComponentTest() { // then assertThat(component.childStack.active.instance::class).isEqualTo(before) + assertThat(errors.any { it is BlinklyError.NotificationPermissionChecking && it.cause === exception }).isTrue() + collectJob.cancel() } private fun completeOnboardingFlow(currentComponent: RootComponent) { @@ -282,6 +288,7 @@ class RootComponentTest : ComponentTest() { val step3 = onboardingComponent.component.childStack.active.instance as OnboardingComponent.Child.Step3 step3.component.onNextClick() val step4 = onboardingComponent.component.childStack.active.instance as OnboardingComponent.Child.Step4 + step4.component.onCheckboxSelect(true) step4.component.onNextClick() val step5 = onboardingComponent.component.childStack.active.instance as OnboardingComponent.Child.Step5 step5.component.onNextClick() @@ -296,6 +303,7 @@ class RootComponentTest : ComponentTest() { val step3 = onboardingComponent.component.childStack.active.instance as OnboardingComponent.Child.Step3 step3.component.onNextClick() val step4 = onboardingComponent.component.childStack.active.instance as OnboardingComponent.Child.Step4 + step4.component.onCheckboxSelect(true) step4.component.onNextClick() } diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/trainings/TrainingsTabComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/trainings/TrainingsTabComponentTest.kt index e290333..b23978a 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/trainings/TrainingsTabComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/trainings/TrainingsTabComponentTest.kt @@ -13,6 +13,7 @@ import com.sedsoftware.blinkly.component.ComponentTest import com.sedsoftware.blinkly.component.trainings.integration.TrainingsTabComponentDefault import com.sedsoftware.blinkly.domain.BlinklyCalendarWatcher import com.sedsoftware.blinkly.domain.external.BlinklyTimeUtils +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.Exercise import com.sedsoftware.blinkly.domain.model.ExerciseBlock @@ -166,10 +167,7 @@ class TrainingsTabComponentTest : ComponentTest() { // then assertThat(testComponent.model.value.cards.any { it.completedToday }).isFalse() - assertThat( - componentOutput.filterIsInstance() - .any { it.throwable.message == exception.message } - ).isTrue() + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } override fun createComponent(): TrainingsTabComponent = diff --git a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/workout/WorkoutComponentTest.kt b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/workout/WorkoutComponentTest.kt index 2cd2529..b697718 100644 --- a/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/workout/WorkoutComponentTest.kt +++ b/shared/component/root/src/commonTest/kotlin/com/sedsoftware/blinkly/component/workout/WorkoutComponentTest.kt @@ -16,6 +16,7 @@ import com.sedsoftware.blinkly.component.ComponentTest import com.sedsoftware.blinkly.component.workout.integration.WorkoutComponentDefault import com.sedsoftware.blinkly.domain.BlinklyExerciseManager import com.sedsoftware.blinkly.domain.external.BlinklyBeeper +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ComponentOutput import com.sedsoftware.blinkly.domain.model.ExerciseBlock import com.sedsoftware.blinkly.domain.model.ExerciseEvent @@ -229,10 +230,28 @@ class WorkoutComponentTest : ComponentTest() { // then assertThat(testComponent.model.value.phase).isEqualTo(WorkoutComponent.Phase.INTRO) - assertThat( - componentOutput.filterIsInstance() - .any { it.throwable.message == exception.message } - ).isTrue() + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() + } + + @Test + fun `when saving workout fails then component publishes saving error output`() = runTest(testScheduler) { + // given + val exception = IllegalStateException("save failed") + component.onStartClick() + testScheduler.advanceUntilIdle() + + // when + exerciseManager.emit( + ExerciseEvent.Error( + block = ExerciseBlock.A, + exercise = ExerciseType.BLINK_BREAK, + throwable = BlinklyError.WorkoutSaving(exception), + ) + ) + testScheduler.advanceUntilIdle() + + // then + assertThat(componentOutputContainsErrorCausedBy(exception)).isTrue() } @Test @@ -245,7 +264,7 @@ class WorkoutComponentTest : ComponentTest() { } @Test - fun `when lifecycle destroyed then manager stops and beeper releases`() = runTest(testScheduler) { + fun `when lifecycle destroyed then manager stops and beeper stays owned by root`() = runTest(testScheduler) { // given val localLifecycle = LifecycleRegistry() val localManager = FakeExerciseManager() @@ -265,7 +284,7 @@ class WorkoutComponentTest : ComponentTest() { // then assertThat(localManager.stopCount).isEqualTo(1) - assertThat(localBeeper.releaseCount).isEqualTo(1) + assertThat(localBeeper.releaseCount).isEqualTo(0) } override fun createComponent(): WorkoutComponent = diff --git a/shared/component/trainings/child/workout/src/commonMain/kotlin/com/sedsoftware/blinkly/component/workout/integration/WorkoutComponentDefault.kt b/shared/component/trainings/child/workout/src/commonMain/kotlin/com/sedsoftware/blinkly/component/workout/integration/WorkoutComponentDefault.kt index e831c37..78b53c0 100644 --- a/shared/component/trainings/child/workout/src/commonMain/kotlin/com/sedsoftware/blinkly/component/workout/integration/WorkoutComponentDefault.kt +++ b/shared/component/trainings/child/workout/src/commonMain/kotlin/com/sedsoftware/blinkly/component/workout/integration/WorkoutComponentDefault.kt @@ -61,7 +61,6 @@ class WorkoutComponentDefault( lifecycle.doOnDestroy { exerciseManager.stop() - beeper.release() scope.cancel() } } diff --git a/shared/component/trainings/child/workout/src/commonMain/kotlin/com/sedsoftware/blinkly/component/workout/store/WorkoutStoreProvider.kt b/shared/component/trainings/child/workout/src/commonMain/kotlin/com/sedsoftware/blinkly/component/workout/store/WorkoutStoreProvider.kt index fed27a1..9958b8b 100644 --- a/shared/component/trainings/child/workout/src/commonMain/kotlin/com/sedsoftware/blinkly/component/workout/store/WorkoutStoreProvider.kt +++ b/shared/component/trainings/child/workout/src/commonMain/kotlin/com/sedsoftware/blinkly/component/workout/store/WorkoutStoreProvider.kt @@ -10,11 +10,13 @@ import com.sedsoftware.blinkly.component.workout.store.WorkoutStore.Phase import com.sedsoftware.blinkly.component.workout.store.WorkoutStore.State import com.sedsoftware.blinkly.domain.BlinklyExerciseManager import com.sedsoftware.blinkly.domain.external.BlinklyBeeper +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ExerciseBlock import com.sedsoftware.blinkly.domain.model.ExerciseEvent import com.sedsoftware.blinkly.domain.model.ExerciseProgress import com.sedsoftware.blinkly.domain.model.ExerciseType import com.sedsoftware.blinkly.domain.model.EyeMovement +import com.sedsoftware.blinkly.domain.model.asBlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.filter @@ -48,7 +50,9 @@ internal class WorkoutStoreProvider( launch { exerciseManager.events .filter { event -> event.block == block } - .catch { throwable -> publish(Label.ErrorCaught(throwable)) } + .catch { throwable -> + publish(Label.ErrorCaught(throwable.asBlinklyError(BlinklyError::WorkoutDataLoading))) + } .collect { event -> when (event) { is ExerciseEvent.Movement -> dispatch(Msg.MovementUpdated(event.movement)) @@ -57,6 +61,9 @@ internal class WorkoutStoreProvider( is ExerciseEvent.Beep -> beeper.beep() is ExerciseEvent.ExerciseCompleted -> dispatch(Msg.ExerciseCompleted(event.exercise)) is ExerciseEvent.BlockCompleted -> dispatch(Msg.BlockCompleted) + is ExerciseEvent.Error -> publish( + Label.ErrorCaught(event.throwable.asBlinklyError(BlinklyError::WorkoutDataLoading)) + ) } } } diff --git a/shared/component/trainings/src/commonMain/kotlin/com/sedsoftware/blinkly/component/trainings/store/TrainingsTabStoreProvider.kt b/shared/component/trainings/src/commonMain/kotlin/com/sedsoftware/blinkly/component/trainings/store/TrainingsTabStoreProvider.kt index b85306f..d7ed337 100644 --- a/shared/component/trainings/src/commonMain/kotlin/com/sedsoftware/blinkly/component/trainings/store/TrainingsTabStoreProvider.kt +++ b/shared/component/trainings/src/commonMain/kotlin/com/sedsoftware/blinkly/component/trainings/store/TrainingsTabStoreProvider.kt @@ -8,7 +8,9 @@ import com.sedsoftware.blinkly.component.trainings.domain.TrainingsTabManager import com.sedsoftware.blinkly.component.trainings.store.TrainingsTabStore.Intent import com.sedsoftware.blinkly.component.trainings.store.TrainingsTabStore.Label import com.sedsoftware.blinkly.component.trainings.store.TrainingsTabStore.State +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.ExerciseBlock +import com.sedsoftware.blinkly.domain.model.asBlinklyError import com.sedsoftware.blinkly.utils.StoreProvider import com.sedsoftware.blinkly.utils.unwrap import kotlinx.coroutines.flow.catch @@ -35,12 +37,12 @@ internal class TrainingsTabStoreProvider( onAction { launch { manager.calendar - .catch { publish(Label.ErrorCaught(it)) } + .catch { publish(Label.ErrorCaught(it.asBlinklyError(BlinklyError::TrainingsDataLoading))) } .collect { calendar -> unwrap( result = withContext(ioContext) { manager.completedToday(calendar) }, onSuccess = { completed -> dispatch(Msg.CompletedTodayUpdated(completed)) }, - onError = { throwable -> publish(Label.ErrorCaught(throwable)) }, + onError = { throwable -> publish(Label.ErrorCaught(BlinklyError.TrainingsDataLoading(throwable))) }, ) } } diff --git a/shared/compose/src/commonMain/composeResources/values-ru/strings.xml b/shared/compose/src/commonMain/composeResources/values-ru/strings.xml index 3771dee..027b3db 100644 --- a/shared/compose/src/commonMain/composeResources/values-ru/strings.xml +++ b/shared/compose/src/commonMain/composeResources/values-ru/strings.xml @@ -7,6 +7,25 @@ ОК Отмена + Что-то пошло не так + Не удалось обновить главный экран + Не удалось загрузить прогресс + Не удалось загрузить тренировки + Не удалось загрузить настройки + Не удалось сохранить настройки + Не удалось загрузить достижения + Не удалось загрузить сад + Не удалось загрузить напоминания + Не удалось удалить напоминание + Не удалось восстановить напоминание + Не удалось создать напоминание + Не удалось загрузить первые напоминания + Не удалось создать первые напоминания + Не удалось очистить первые напоминания + Не удалось проверить разрешение на уведомления + Не удалось запросить разрешение на уведомления + Не удалось обновить прогресс тренировки + Не удалось сохранить тренировку Назад diff --git a/shared/compose/src/commonMain/composeResources/values/strings.xml b/shared/compose/src/commonMain/composeResources/values/strings.xml index 8eeb797..77e1e01 100644 --- a/shared/compose/src/commonMain/composeResources/values/strings.xml +++ b/shared/compose/src/commonMain/composeResources/values/strings.xml @@ -10,6 +10,25 @@ OK Cancel + Something went wrong + Could not update the main screen + Could not load your progress + Could not load trainings + Could not load preferences + Could not save preferences + Could not load achievements + Could not load the garden + Could not load reminders + Could not delete reminder + Could not restore reminder + Could not create reminder + Could not load initial reminders + Could not create initial reminders + Could not clear initial reminders + Could not check notification permission + Could not request notification permission + Could not update workout progress + Could not save workout Back diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/theme/BlinklyAppTheme.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/theme/BlinklyAppTheme.kt index bfdabf9..73e2b56 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/theme/BlinklyAppTheme.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/theme/BlinklyAppTheme.kt @@ -90,7 +90,7 @@ internal val LocalThemeIsDark = compositionLocalOf { true } @Composable internal fun BlinklyAppTheme( - onThemeChanged: @Composable (isDark: Boolean) -> Unit, + onSystemBarsAppearanceChanged: @Composable (useDarkIcons: Boolean) -> Unit, themeState: ThemeState = ThemeState.SYSTEM, isSystemDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit, @@ -104,7 +104,7 @@ internal fun BlinklyAppTheme( CompositionLocalProvider( LocalThemeIsDark provides isDark ) { - onThemeChanged(!isDark) + onSystemBarsAppearanceChanged(!isDark) MaterialTheme( colorScheme = if (isDark) DarkColorScheme else LightColorScheme, typography = BlinklyFonts.interTypography(), @@ -116,12 +116,12 @@ internal fun BlinklyAppTheme( @Composable internal fun BlinklyWidgetPreview( - isDakTheme: Boolean = false, + isDarkTheme: Boolean = false, content: @Composable () -> Unit, ) { BlinklyAppTheme( - onThemeChanged = {}, - themeState = if (isDakTheme) ThemeState.DARK else ThemeState.LIGHT, + onSystemBarsAppearanceChanged = {}, + themeState = if (isDarkTheme) ThemeState.DARK else ThemeState.LIGHT, content = content, ) } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/RootContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/RootContent.kt index c8c6015..f71a4de 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/RootContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/RootContent.kt @@ -1,14 +1,45 @@ package com.sedsoftware.blinkly.compose.ui +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Snackbar +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import blinkly.shared.compose.generated.resources.Res +import blinkly.shared.compose.generated.resources.error_achievements_loading +import blinkly.shared.compose.generated.resources.error_garden_loading +import blinkly.shared.compose.generated.resources.error_initial_reminders_clearing +import blinkly.shared.compose.generated.resources.error_initial_reminders_creating +import blinkly.shared.compose.generated.resources.error_initial_reminders_loading +import blinkly.shared.compose.generated.resources.error_main_data_loading +import blinkly.shared.compose.generated.resources.error_notification_permission_checking +import blinkly.shared.compose.generated.resources.error_notification_permission_requesting +import blinkly.shared.compose.generated.resources.error_preferences_loading +import blinkly.shared.compose.generated.resources.error_preferences_saving +import blinkly.shared.compose.generated.resources.error_progress_data_loading +import blinkly.shared.compose.generated.resources.error_reminder_creating +import blinkly.shared.compose.generated.resources.error_reminder_deleting +import blinkly.shared.compose.generated.resources.error_reminder_restoring +import blinkly.shared.compose.generated.resources.error_reminders_loading +import blinkly.shared.compose.generated.resources.error_trainings_data_loading +import blinkly.shared.compose.generated.resources.error_unknown +import blinkly.shared.compose.generated.resources.error_workout_data_loading +import blinkly.shared.compose.generated.resources.error_workout_saving import com.arkivanov.decompose.extensions.compose.experimental.stack.ChildStack import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.fade @@ -26,45 +57,116 @@ import com.sedsoftware.blinkly.compose.ui.home.HomeScreenContent import com.sedsoftware.blinkly.compose.ui.newreminder.AddNewReminderContent import com.sedsoftware.blinkly.compose.ui.onboarding.OnboardingContent import com.sedsoftware.blinkly.compose.ui.preferences.PreferencesContent +import com.sedsoftware.blinkly.domain.model.BlinklyError +import org.jetbrains.compose.resources.stringResource @Composable fun RootContent( component: RootComponent, modifier: Modifier = Modifier, - onThemeChanged: @Composable (isDark: Boolean) -> Unit = {}, + onSystemBarsAppearanceChanged: @Composable (useDarkIcons: Boolean) -> Unit = {}, ) { val themeState by component.themeState.subscribeAsState() + val snackbarHostState = remember { SnackbarHostState() } + var currentError: BlinklyError? by remember { mutableStateOf(null) } BlinklyAppTheme( - onThemeChanged = onThemeChanged, + onSystemBarsAppearanceChanged = onSystemBarsAppearanceChanged, themeState = themeState, ) { - ChildStack( - stack = component.childStack, - animation = stackAnimation( - animator = fade() + scale(), - predictiveBackParams = { - PredictiveBackParams( - backHandler = component.backHandler, - onBack = component::onBack, - animatable = ::materialPredictiveBackAnimatable, - ) + LaunchedEffect(component) { + component.errors.collect { error -> + currentError = error + } + } + + currentError?.let { error -> + val message = error.asMessage() + + LaunchedEffect(error, message) { + snackbarHostState.showSnackbar( + message = message, + withDismissAction = true, + ) + + if (currentError === error) { + currentError = null } - ), + } + } + + Box( modifier = modifier .windowInsetsPadding(WindowInsets.safeDrawing) .padding(16.dp) .fillMaxSize(), ) { - when (val child = it.instance) { - is RootComponent.Child.Onboarding -> OnboardingContent(child.component) - is RootComponent.Child.HomeScreen -> HomeScreenContent(child.component) - is RootComponent.Child.Preferences -> PreferencesContent(child.component) - is RootComponent.Child.Workout -> WorkoutContent(child.component) - is RootComponent.Child.Achievements -> AchievementsContent(child.component) - is RootComponent.Child.Garden -> GardenContent(child.component) - is RootComponent.Child.AddNewReminder -> AddNewReminderContent(child.component) + ChildStack( + stack = component.childStack, + animation = stackAnimation( + animator = fade() + scale(), + predictiveBackParams = { + PredictiveBackParams( + backHandler = component.backHandler, + onBack = component::onBack, + animatable = ::materialPredictiveBackAnimatable, + ) + } + ), + modifier = Modifier.fillMaxSize(), + ) { + when (val child = it.instance) { + is RootComponent.Child.Onboarding -> OnboardingContent(child.component) + is RootComponent.Child.HomeScreen -> HomeScreenContent(child.component) + is RootComponent.Child.Preferences -> PreferencesContent(child.component) + is RootComponent.Child.Workout -> WorkoutContent(child.component) + is RootComponent.Child.Achievements -> AchievementsContent(child.component) + is RootComponent.Child.Garden -> GardenContent(child.component) + is RootComponent.Child.AddNewReminder -> AddNewReminderContent(child.component) + } } + + SnackbarHost( + hostState = snackbarHostState, + snackbar = { snackbarData -> + Snackbar( + snackbarData = snackbarData, + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + actionColor = MaterialTheme.colorScheme.onError, + dismissActionContentColor = MaterialTheme.colorScheme.onError, + ) + }, + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth(), + ) } } } + +@Composable +private fun BlinklyError.asMessage(): String = + stringResource( + resource = when (this) { + is BlinklyError.MainDataLoading -> Res.string.error_main_data_loading + is BlinklyError.ProgressDataLoading -> Res.string.error_progress_data_loading + is BlinklyError.TrainingsDataLoading -> Res.string.error_trainings_data_loading + is BlinklyError.PreferencesLoading -> Res.string.error_preferences_loading + is BlinklyError.PreferencesSaving -> Res.string.error_preferences_saving + is BlinklyError.AchievementsLoading -> Res.string.error_achievements_loading + is BlinklyError.GardenLoading -> Res.string.error_garden_loading + is BlinklyError.RemindersLoading -> Res.string.error_reminders_loading + is BlinklyError.ReminderDeleting -> Res.string.error_reminder_deleting + is BlinklyError.ReminderRestoring -> Res.string.error_reminder_restoring + is BlinklyError.ReminderCreating -> Res.string.error_reminder_creating + is BlinklyError.InitialRemindersLoading -> Res.string.error_initial_reminders_loading + is BlinklyError.InitialRemindersCreating -> Res.string.error_initial_reminders_creating + is BlinklyError.InitialRemindersClearing -> Res.string.error_initial_reminders_clearing + is BlinklyError.NotificationPermissionChecking -> Res.string.error_notification_permission_checking + is BlinklyError.NotificationPermissionRequesting -> Res.string.error_notification_permission_requesting + is BlinklyError.WorkoutDataLoading -> Res.string.error_workout_data_loading + is BlinklyError.WorkoutSaving -> Res.string.error_workout_saving + is BlinklyError.Unknown -> Res.string.error_unknown + } + ) diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/achievements/AchievementsContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/achievements/AchievementsContent.kt index 99ceaa9..5bf4e0e 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/achievements/AchievementsContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/achievements/AchievementsContent.kt @@ -322,6 +322,7 @@ private fun HiddenDescriptionPlaceholder( private fun AchievementDetailsSheet( achievement: AchievementItem, modifier: Modifier = Modifier, + unlockedAtTimeZone: TimeZone = TimeZone.currentSystemDefault(), ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -358,7 +359,7 @@ private fun AchievementDetailsSheet( Text( text = stringResource( resource = Res.string.achievements_unlocked_at, - unlockedAt.asUserTimeZoneDateTime(), + unlockedAt.asUserTimeZoneDateTime(timeZone = unlockedAtTimeZone), ), style = MaterialTheme.typography.labelLarge, modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp), @@ -368,8 +369,8 @@ private fun AchievementDetailsSheet( } } -private fun Instant.asUserTimeZoneDateTime(): String { - val localDateTime = toLocalDateTime(TimeZone.currentSystemDefault()) +private fun Instant.asUserTimeZoneDateTime(timeZone: TimeZone): String { + val localDateTime = toLocalDateTime(timeZone) val date = localDateTime.date val time = localDateTime.time return "${date.year}-${(date.month.ordinal + 1).twoDigits()}-${date.day.twoDigits()} " + @@ -378,7 +379,7 @@ private fun Instant.asUserTimeZoneDateTime(): String { private fun Int.twoDigits(): String = toString().padStart(length = 2, padChar = '0') -@Preview(widthDp = 420, heightDp = 1200) +@Preview(widthDp = 420, heightDp = 1060) @Composable private fun AchievementsContentPreviewLight() { BlinklyWidgetPreview { @@ -386,15 +387,15 @@ private fun AchievementsContentPreviewLight() { } } -@Preview(widthDp = 420, heightDp = 1200) +@Preview(widthDp = 420, heightDp = 1060) @Composable private fun AchievementsContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { AchievementsContent(component = AchievementsComponentPreview()) } } -@Preview(widthDp = 420, heightDp = 520) +@Preview(widthDp = 420, heightDp = 500) @Composable private fun AchievementDetailsSheetPreviewLight() { BlinklyWidgetPreview { @@ -403,19 +404,21 @@ private fun AchievementDetailsSheetPreviewLight() { modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp, vertical = 32.dp), + unlockedAtTimeZone = TimeZone.UTC, ) } } -@Preview(widthDp = 420, heightDp = 520) +@Preview(widthDp = 420, heightDp = 500) @Composable private fun AchievementDetailsSheetPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { AchievementDetailsSheet( achievement = previewDetailsAchievement(), modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp, vertical = 32.dp), + unlockedAtTimeZone = TimeZone.UTC, ) } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/exercises/WorkoutContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/exercises/WorkoutContent.kt index aaca457..a54f9ed 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/exercises/WorkoutContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/exercises/WorkoutContent.kt @@ -655,7 +655,7 @@ private data class WorkoutCopy( val description: String, ) -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 520) @Composable private fun WorkoutIntroPreview() { BlinklyWidgetPreview { @@ -663,7 +663,15 @@ private fun WorkoutIntroPreview() { } } -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 520) +@Composable +private fun WorkoutIntroPreviewDark() { + BlinklyWidgetPreview(isDarkTheme = true) { + WorkoutContent(component = WorkoutComponentPreview()) + } +} + +@Preview(widthDp = 390, heightDp = 780) @Composable private fun WorkoutBlinkBreakPreview() { WorkoutExercisePreview( @@ -673,7 +681,18 @@ private fun WorkoutBlinkBreakPreview() { ) } -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 780) +@Composable +private fun WorkoutBlinkBreakPreviewDark() { + WorkoutExercisePreview( + block = ExerciseBlock.A, + exercise = ExerciseType.BLINK_BREAK, + movement = EyeMovement.Blink(count = 3), + isDarkTheme = true, + ) +} + +@Preview(widthDp = 390, heightDp = 780) @Composable private fun WorkoutNearFarPreview() { WorkoutExercisePreview( @@ -683,7 +702,18 @@ private fun WorkoutNearFarPreview() { ) } -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 780) +@Composable +private fun WorkoutNearFarPreviewDark() { + WorkoutExercisePreview( + block = ExerciseBlock.A, + exercise = ExerciseType.NEAR_FAR_FOCUS, + movement = EyeMovement.AccommodationFar, + isDarkTheme = true, + ) +} + +@Preview(widthDp = 390, heightDp = 780) @Composable private fun WorkoutDiagonalPreview() { WorkoutExercisePreview( @@ -693,7 +723,18 @@ private fun WorkoutDiagonalPreview() { ) } -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 780) +@Composable +private fun WorkoutDiagonalPreviewDark() { + WorkoutExercisePreview( + block = ExerciseBlock.A, + exercise = ExerciseType.DIAGONAL_GAZES, + movement = EyeMovement.DiagonalBottomRight, + isDarkTheme = true, + ) +} + +@Preview(widthDp = 390, heightDp = 780) @Composable private fun WorkoutFigureEightPreview() { WorkoutExercisePreview( @@ -703,7 +744,18 @@ private fun WorkoutFigureEightPreview() { ) } -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 780) +@Composable +private fun WorkoutFigureEightPreviewDark() { + WorkoutExercisePreview( + block = ExerciseBlock.B, + exercise = ExerciseType.FIGURE_EIGHT, + movement = EyeMovement.EightClockwise, + isDarkTheme = true, + ) +} + +@Preview(widthDp = 390, heightDp = 780) @Composable private fun WorkoutClockRollsPreview() { WorkoutExercisePreview( @@ -713,7 +765,18 @@ private fun WorkoutClockRollsPreview() { ) } -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 780) +@Composable +private fun WorkoutClockRollsPreviewDark() { + WorkoutExercisePreview( + block = ExerciseBlock.B, + exercise = ExerciseType.CLOCK_ROLLS, + movement = EyeMovement.CircleClockwise, + isDarkTheme = true, + ) +} + +@Preview(widthDp = 390, heightDp = 860) @Composable private fun WorkoutPalmingPreview() { WorkoutExercisePreview( @@ -723,7 +786,18 @@ private fun WorkoutPalmingPreview() { ) } -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 860) +@Composable +private fun WorkoutPalmingPreviewDark() { + WorkoutExercisePreview( + block = ExerciseBlock.B, + exercise = ExerciseType.PALMING, + timerRemainingSeconds = 42, + isDarkTheme = true, + ) +} + +@Preview(widthDp = 390, heightDp = 800) @Composable private fun WorkoutTwentyPreview() { WorkoutExercisePreview( @@ -733,7 +807,18 @@ private fun WorkoutTwentyPreview() { ) } -@Preview(widthDp = 390, heightDp = 900) +@Preview(widthDp = 390, heightDp = 800) +@Composable +private fun WorkoutTwentyPreviewDark() { + WorkoutExercisePreview( + block = ExerciseBlock.C, + exercise = ExerciseType.TWENTY_X3, + timerRemainingSeconds = 16, + isDarkTheme = true, + ) +} + +@Preview(widthDp = 390, heightDp = 520) @Composable private fun WorkoutCompletedPreview() { BlinklyWidgetPreview { @@ -748,14 +833,30 @@ private fun WorkoutCompletedPreview() { } } +@Preview(widthDp = 390, heightDp = 520) +@Composable +private fun WorkoutCompletedPreviewDark() { + BlinklyWidgetPreview(isDarkTheme = true) { + WorkoutContent( + component = WorkoutComponentPreview( + block = ExerciseBlock.C, + currentExercise = ExerciseType.TWENTY_X3, + phase = WorkoutComponent.Phase.COMPLETED, + progress = WorkoutComponent.Progress(percent = 100, remainingMs = 0L, totalMs = 20_000L), + ) + ) + } +} + @Composable private fun WorkoutExercisePreview( block: ExerciseBlock, exercise: ExerciseType, movement: EyeMovement? = null, timerRemainingSeconds: Int? = null, + isDarkTheme: Boolean = false ) { - BlinklyWidgetPreview { + BlinklyWidgetPreview(isDarkTheme = isDarkTheme) { WorkoutContent( component = WorkoutComponentPreview( block = block, diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/extension/ComposeExt.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/extension/ComposeExt.kt index fed48ee..9f93a78 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/extension/ComposeExt.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/extension/ComposeExt.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.semantics.Role import kotlinx.coroutines.delay private const val DEFAULT_SHIMMER_DURATION_MS: Int = 1_200 @@ -97,6 +98,7 @@ fun Modifier.shimmering( fun Modifier.clickableOnce( onClick: () -> Unit, debounceMs: Long = 500L, + role: Role? = Role.Button, ): Modifier = composed { val interactionSource = remember { MutableInteractionSource() } var isEnabled by remember { mutableStateOf(true) } @@ -112,6 +114,7 @@ fun Modifier.clickableOnce( enabled = isEnabled, interactionSource = interactionSource, indication = ripple(), + role = role, onClick = { if (isEnabled) { isEnabled = false diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/garden/GardenContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/garden/GardenContent.kt index f49f174..d1c5494 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/garden/GardenContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/garden/GardenContent.kt @@ -142,7 +142,7 @@ fun GardenContent( items( items = model.grownTrees.chunked(size = 2), - key = { row -> "garden_row_${row.first().type.name}" }, + key = { row -> row.joinToString(prefix = "garden_row_", separator = "_") { it.type.name } }, contentType = { "garden_row" }, ) { row -> GardenTreeRow( @@ -410,7 +410,7 @@ private fun TreeImage( } } -@Preview(widthDp = 420, heightDp = 1100) +@Preview(widthDp = 420, heightDp = 900) @Composable private fun GardenContentPreviewLight() { BlinklyWidgetPreview { @@ -418,15 +418,15 @@ private fun GardenContentPreviewLight() { } } -@Preview(widthDp = 420, heightDp = 1100) +@Preview(widthDp = 420, heightDp = 900) @Composable private fun GardenContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { GardenContent(component = GardenComponentPreview()) } } -@Preview(widthDp = 420, heightDp = 520) +@Preview(widthDp = 420, heightDp = 500) @Composable private fun GardenTreeDetailsSheetPreviewLight() { BlinklyWidgetPreview { @@ -439,10 +439,10 @@ private fun GardenTreeDetailsSheetPreviewLight() { } } -@Preview(widthDp = 420, heightDp = 520) +@Preview(widthDp = 420, heightDp = 500) @Composable private fun GardenTreeDetailsSheetPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { GardenTreeDetailsSheet( tree = Tree(TreeStage.MAGNIFICENT, TreeType.GINKGO_BILOBA, 100f), modifier = Modifier diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/bottomnavigation/BottomNavigationBar.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/bottomnavigation/BottomNavigationBar.kt index eea158d..fa2a036 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/bottomnavigation/BottomNavigationBar.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/bottomnavigation/BottomNavigationBar.kt @@ -236,14 +236,14 @@ private fun BottomNavigationBarPreviewLight() { @Preview @Composable private fun BottomNavigationBarPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BottomNavigationBarPreviewContent() } } @Composable private fun BottomNavigationBarPreviewContent() { - var activeTab: HomeScreenTab by remember { mutableStateOf(HomeScreenTab.TRAINING) } + var activeTab: HomeScreenTab by remember { mutableStateOf(HomeScreenTab.MAIN) } Column( modifier = Modifier diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/MainTabContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/MainTabContent.kt index d17a28c..3cad792 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/MainTabContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/MainTabContent.kt @@ -413,7 +413,7 @@ private fun FourthCardHighlights( } @Composable -@Preview(widthDp = 1200, heightDp = 1800) +@Preview(widthDp = 1200, heightDp = 1700) private fun MainTabContentPreviewLight() { BlinklyWidgetPreview { MainTabPreviewContent() @@ -421,15 +421,15 @@ private fun MainTabContentPreviewLight() { } @Composable -@Preview(widthDp = 1200, heightDp = 1800) +@Preview(widthDp = 1200, heightDp = 1700) private fun MainTabContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { MainTabPreviewContent() } } @Composable -@Preview(widthDp = 420, heightDp = 920) +@Preview(widthDp = 420, heightDp = 740) private fun MainTabContentPhonePreviewLight() { BlinklyWidgetPreview { MainTabPhonePreviewContent() @@ -437,9 +437,9 @@ private fun MainTabContentPhonePreviewLight() { } @Composable -@Preview(widthDp = 420, heightDp = 920) +@Preview(widthDp = 420, heightDp = 740) private fun MainTabContentPhonePreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { MainTabPhonePreviewContent() } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/ProgressTabContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/ProgressTabContent.kt index b75b2d1..27ffec0 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/ProgressTabContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/ProgressTabContent.kt @@ -364,7 +364,7 @@ private fun AchievementPreviewItem( } } -@Preview(widthDp = 420, heightDp = 1060) +@Preview(widthDp = 420, heightDp = 920) @Composable private fun ProgressTabContentPreviewLight() { BlinklyWidgetPreview { @@ -372,10 +372,10 @@ private fun ProgressTabContentPreviewLight() { } } -@Preview(widthDp = 420, heightDp = 1060) +@Preview(widthDp = 420, heightDp = 920) @Composable private fun ProgressTabContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { ProgressTabContent(component = ProgressTabComponentPreview()) } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/RemindersTabContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/RemindersTabContent.kt index f5f6626..cd08378 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/RemindersTabContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/RemindersTabContent.kt @@ -357,7 +357,7 @@ private fun ReminderCard( ) { IconButton( onClick = onDeleteClick, - modifier = Modifier.size(size = 40.dp), + modifier = Modifier.size(size = 48.dp), ) { Icon( painter = painterResource(resource = Res.drawable.icon_delete), @@ -417,7 +417,7 @@ private fun ReminderItem.daysLabel(): String { private fun LocalDate.asShortDate(): String = "${day.toString().padStart(2, '0')}.${(month.ordinal + 1).toString().padStart(2, '0')}" -@Preview(widthDp = 440, heightDp = 880) +@Preview(widthDp = 440, heightDp = 560) @Composable private fun RemindersTabContentPreviewLight() { BlinklyWidgetPreview { @@ -425,15 +425,15 @@ private fun RemindersTabContentPreviewLight() { } } -@Preview(widthDp = 440, heightDp = 880) +@Preview(widthDp = 440, heightDp = 560) @Composable private fun RemindersTabContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { RemindersPreviewContent() } } -@Preview(widthDp = 440, heightDp = 880) +@Preview(widthDp = 440, heightDp = 560) @Composable private fun RemindersTabContentEmptyPreviewLight() { BlinklyWidgetPreview { diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/TrainingsTabContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/TrainingsTabContent.kt index df69fef..edb3dc3 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/TrainingsTabContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/home/tabs/TrainingsTabContent.kt @@ -336,7 +336,7 @@ private data class TrainingColors( ) @Composable -@Preview(widthDp = 420, heightDp = 920) +@Preview(widthDp = 420, heightDp = 800) private fun TrainingsTabContentPreviewLight() { BlinklyWidgetPreview { TrainingsTabContent( @@ -346,9 +346,9 @@ private fun TrainingsTabContentPreviewLight() { } @Composable -@Preview(widthDp = 420, heightDp = 920) +@Preview(widthDp = 420, heightDp = 800) private fun TrainingsTabContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { TrainingsTabContent( component = TrainingsTabComponentPreview( completedBlocks = setOf( diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/newreminder/AddNewReminderContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/newreminder/AddNewReminderContent.kt index 4d1a1d6..31800ce 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/newreminder/AddNewReminderContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/newreminder/AddNewReminderContent.kt @@ -23,7 +23,6 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TimePickerState import androidx.compose.material3.TopAppBar import androidx.compose.material3.rememberTimePickerState import androidx.compose.runtime.Composable @@ -93,6 +92,7 @@ import org.jetbrains.compose.resources.stringResource private const val INTERVAL_MIN = 20 private const val INTERVAL_STEP_COUNT = 4 +private const val CUSTOM_INTERVAL_DEFAULT = INTERVAL_MIN * 3 @Composable fun AddNewReminderContent( @@ -434,14 +434,15 @@ private fun TimeSelector( prefix: String? = null, onTimeSelect: (LocalTime) -> Unit, ) { - val timePickerState: TimePickerState = rememberTimePickerState( - initialHour = time.hour, - initialMinute = time.minute, - is24Hour = true, - ) var pickerVisible: Boolean by remember { mutableStateOf(false) } if (pickerVisible) { + val timePickerState = rememberTimePickerState( + initialHour = time.hour, + initialMinute = time.minute, + is24Hour = true, + ) + BlinklyTimePickerDialog( timePickerState = timePickerState, onConfirm = { @@ -470,7 +471,7 @@ private fun IntervalSelector( onIntervalSelect: (Int) -> Unit, modifier: Modifier = Modifier, ) { - var customIntervalVisible: Boolean by remember { mutableStateOf(interval != INTERVAL_MIN && interval != INTERVAL_MIN * 2) } + val customIntervalVisible: Boolean = interval != INTERVAL_MIN && interval != INTERVAL_MIN * 2 Column(modifier = modifier) { Row( @@ -480,27 +481,23 @@ private fun IntervalSelector( IntervalOption( text = stringResource(resource = Res.string.add_reminder_interval_minutes, INTERVAL_MIN), selected = interval == INTERVAL_MIN && !customIntervalVisible, - onClick = { - customIntervalVisible = false - onIntervalSelect(INTERVAL_MIN) - }, + onClick = { onIntervalSelect(INTERVAL_MIN) }, modifier = Modifier.weight(weight = 1f), ) IntervalOption( text = stringResource(resource = Res.string.add_reminder_interval_minutes, INTERVAL_MIN * 2), selected = interval == INTERVAL_MIN * 2 && !customIntervalVisible, - onClick = { - customIntervalVisible = false - onIntervalSelect(INTERVAL_MIN * 2) - }, + onClick = { onIntervalSelect(INTERVAL_MIN * 2) }, modifier = Modifier.weight(weight = 1f), ) IntervalOption( text = stringResource(resource = Res.string.add_reminder_interval_custom), selected = customIntervalVisible, - onClick = { customIntervalVisible = true }, + onClick = { + onIntervalSelect(interval.takeIf { customIntervalVisible } ?: CUSTOM_INTERVAL_DEFAULT) + }, modifier = Modifier.weight(weight = 1f), ) } @@ -659,7 +656,7 @@ private fun ValidationError.asMessage(): String = ValidationError.INVALID_INTERVAL -> stringResource(resource = Res.string.add_reminder_error_invalid_interval) } -@Preview(widthDp = 440, heightDp = 880) +@Preview(widthDp = 440, heightDp = 840) @Composable private fun AddNewReminderContentPreviewLight() { BlinklyWidgetPreview { @@ -667,15 +664,15 @@ private fun AddNewReminderContentPreviewLight() { } } -@Preview(widthDp = 440, heightDp = 880) +@Preview(widthDp = 440, heightDp = 840) @Composable private fun AddNewReminderContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { AddNewReminderPreviewContent() } } -@Preview(widthDp = 440, heightDp = 880) +@Preview(widthDp = 440, heightDp = 660) @Composable private fun AddNewReminderContentDailyPreviewLight() { BlinklyWidgetPreview { @@ -685,6 +682,16 @@ private fun AddNewReminderContentDailyPreviewLight() { } } +@Preview(widthDp = 440, heightDp = 660) +@Composable +private fun AddNewReminderContentDailyPreviewDark() { + BlinklyWidgetPreview(isDarkTheme = true) { + AddNewReminderContent( + component = AddNewReminderComponentPreview(scheduleType = ScheduleType.DAILY) + ) + } +} + @Composable @PreviewContent private fun AddNewReminderPreviewContent() { diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/newreminder/AddWeeklyPeriodContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/newreminder/AddWeeklyPeriodContent.kt index 12e9c4b..9cb3820 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/newreminder/AddWeeklyPeriodContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/newreminder/AddWeeklyPeriodContent.kt @@ -14,7 +14,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Slider import androidx.compose.material3.Text -import androidx.compose.material3.TimePickerState import androidx.compose.material3.rememberTimePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -50,6 +49,7 @@ import org.jetbrains.compose.resources.stringResource private const val SLIDER_STEP_VALUE = 20 private const val SLIDER_TOTAL_STEPS = 4 +private const val CUSTOM_INTERVAL_DEFAULT = SLIDER_STEP_VALUE * 3 @Composable fun AddWeeklyPeriodContent( @@ -66,29 +66,15 @@ fun AddWeeklyPeriodContent( onCreateClick: () -> Unit = {}, onClearClick: () -> Unit = {}, ) { - val timePickerStateFrom: TimePickerState = rememberTimePickerState( - initialHour = selectedTimeFrom.hour, - initialMinute = selectedTimeFrom.minute, - is24Hour = true, // TODO 24hr format hardcoded, fix later? - ) - val defaultIntervals: Set = setOf( SLIDER_STEP_VALUE, SLIDER_STEP_VALUE * 2, ) + val showSlider: Boolean = !defaultIntervals.contains(selectedInterval) var timePickerFromVisible: Boolean by remember { mutableStateOf(false) } - - val timePickerStateUntil: TimePickerState = rememberTimePickerState( - initialHour = selectedTimeUntil.hour, - initialMinute = selectedTimeUntil.minute, - is24Hour = true, - ) - var timePickerUntilVisible: Boolean by remember { mutableStateOf(false) } - var showSlider: Boolean by remember { mutableStateOf(!defaultIntervals.contains(selectedInterval)) } - val periodOptions: List = listOf( stringResource(Res.string.onboarding_initial_setup_20), stringResource(Res.string.onboarding_initial_setup_40), @@ -96,6 +82,12 @@ fun AddWeeklyPeriodContent( ) if (timePickerFromVisible) { + val timePickerStateFrom = rememberTimePickerState( + initialHour = selectedTimeFrom.hour, + initialMinute = selectedTimeFrom.minute, + is24Hour = true, // TODO 24hr format hardcoded, fix later? + ) + BlinklyTimePickerDialog( timePickerState = timePickerStateFrom, onConfirm = { @@ -110,6 +102,12 @@ fun AddWeeklyPeriodContent( } if (timePickerUntilVisible) { + val timePickerStateUntil = rememberTimePickerState( + initialHour = selectedTimeUntil.hour, + initialMinute = selectedTimeUntil.minute, + is24Hour = true, + ) + BlinklyTimePickerDialog( timePickerState = timePickerStateUntil, onConfirm = { @@ -176,7 +174,7 @@ fun AddWeeklyPeriodContent( Row( modifier = Modifier .fillMaxWidth() - .height(height = 32.dp) + .height(height = 48.dp) .selectable( selected = when (index) { 0 -> selectedInterval == SLIDER_STEP_VALUE @@ -184,12 +182,13 @@ fun AddWeeklyPeriodContent( else -> !defaultIntervals.contains(selectedInterval) }, onClick = { - when(index) { + when (index) { 0 -> onIntervalSelect.invoke(SLIDER_STEP_VALUE) 1 -> onIntervalSelect.invoke(SLIDER_STEP_VALUE * 2) + else -> onIntervalSelect.invoke( + selectedInterval.takeIf { it !in defaultIntervals } ?: CUSTOM_INTERVAL_DEFAULT + ) } - - showSlider = index != 0 && index != 1 }, role = Role.RadioButton, ) @@ -285,53 +284,89 @@ fun AddWeeklyPeriodContent( } } -@Preview(heightDp = 1600) +@Preview(heightDp = 520) @Composable -private fun AddWeeklyPeriodContentPreviewLight() { +private fun AddWeeklyPeriodContentDefaultPreviewLight() { BlinklyWidgetPreview { - AddWeeklyPeriodContentPreviewContent() + AddWeeklyPeriodContentDefaultPreviewContent() } } -@Preview(heightDp = 1600) +@Preview(heightDp = 520) @Composable -private fun AddWeeklyPeriodContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { - AddWeeklyPeriodContentPreviewContent() +private fun AddWeeklyPeriodContentDefaultPreviewDark() { + BlinklyWidgetPreview(isDarkTheme = true) { + AddWeeklyPeriodContentDefaultPreviewContent() } } +@Preview(heightDp = 520) @Composable -@PreviewContent -private fun AddWeeklyPeriodContentPreviewContent() { - Column { - AddWeeklyPeriodContent( - selectedTimeFrom = LocalTime(10, 0), - selectedTimeUntil = LocalTime(18, 0), - selectedDays = DayOfWeek.entries.toList(), - selectedInterval = SLIDER_STEP_VALUE, - createdRemindersCount = 0, - modifier = Modifier.padding(bottom = 64.dp) - ) +private fun AddWeeklyPeriodContentCreatedPreviewLight() { + BlinklyWidgetPreview { + AddWeeklyPeriodContentCreatedPreviewContent() + } +} - AddWeeklyPeriodContent( - selectedTimeFrom = LocalTime(14, 0), - selectedTimeUntil = LocalTime(16, 0), - selectedDays = listOf( - DayOfWeek.MONDAY, - DayOfWeek.WEDNESDAY, - ), - selectedInterval = SLIDER_STEP_VALUE * 2, - createdRemindersCount = 2, - modifier = Modifier.padding(bottom = 64.dp) - ) +@Preview(heightDp = 520) +@Composable +private fun AddWeeklyPeriodContentCreatedPreviewDark() { + BlinklyWidgetPreview(isDarkTheme = true) { + AddWeeklyPeriodContentCreatedPreviewContent() + } +} - AddWeeklyPeriodContent( - selectedTimeFrom = LocalTime(11, 0), - selectedTimeUntil = LocalTime(13, 0), - selectedDays = listOf(DayOfWeek.SUNDAY), - selectedInterval = SLIDER_STEP_VALUE * 3, - createdRemindersCount = 99, - ) +@Preview(heightDp = 560) +@Composable +private fun AddWeeklyPeriodContentCustomPreviewLight() { + BlinklyWidgetPreview { + AddWeeklyPeriodContentCustomPreviewContent() + } +} + +@Preview(heightDp = 560) +@Composable +private fun AddWeeklyPeriodContentCustomPreviewDark() { + BlinklyWidgetPreview(isDarkTheme = true) { + AddWeeklyPeriodContentCustomPreviewContent() } } + +@Composable +@PreviewContent +private fun AddWeeklyPeriodContentDefaultPreviewContent() { + AddWeeklyPeriodContent( + selectedTimeFrom = LocalTime(10, 0), + selectedTimeUntil = LocalTime(18, 0), + selectedDays = DayOfWeek.entries.toList(), + selectedInterval = SLIDER_STEP_VALUE, + createdRemindersCount = 0, + ) +} + +@Composable +@PreviewContent +private fun AddWeeklyPeriodContentCreatedPreviewContent() { + AddWeeklyPeriodContent( + selectedTimeFrom = LocalTime(14, 0), + selectedTimeUntil = LocalTime(16, 0), + selectedDays = listOf( + DayOfWeek.MONDAY, + DayOfWeek.WEDNESDAY, + ), + selectedInterval = SLIDER_STEP_VALUE * 2, + createdRemindersCount = 2, + ) +} + +@Composable +@PreviewContent +private fun AddWeeklyPeriodContentCustomPreviewContent() { + AddWeeklyPeriodContent( + selectedTimeFrom = LocalTime(11, 0), + selectedTimeUntil = LocalTime(13, 0), + selectedDays = listOf(DayOfWeek.SUNDAY), + selectedInterval = SLIDER_STEP_VALUE * 3, + createdRemindersCount = 99, + ) +} diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/BottomNavigationButtons.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/BottomNavigationButtons.kt index 8bf2c76..784cfdf 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/BottomNavigationButtons.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/BottomNavigationButtons.kt @@ -71,7 +71,7 @@ private fun BottomNavigationButtonsPreviewLight() { @Preview @Composable private fun BottomNavigationButtonsPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BottomNavigationButtonsPreviewContent() } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContent.kt index eecac30..cbf8d89 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContent.kt @@ -122,7 +122,7 @@ private fun OnboardingContentPreviewStep5Light() { @Preview @Composable private fun OnboardingContentPreviewStep1Dark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContent(OnboardingComponentPreview(step = 1)) } } @@ -130,7 +130,7 @@ private fun OnboardingContentPreviewStep1Dark() { @Preview @Composable private fun OnboardingContentPreviewStep2Dark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContent(OnboardingComponentPreview(step = 2)) } } @@ -138,7 +138,7 @@ private fun OnboardingContentPreviewStep2Dark() { @Preview @Composable private fun OnboardingContentPreviewStep3Dark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContent(OnboardingComponentPreview(step = 3)) } } @@ -146,7 +146,7 @@ private fun OnboardingContentPreviewStep3Dark() { @Preview @Composable private fun OnboardingContentPreviewStep4Dark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContent(OnboardingComponentPreview(step = 4)) } } @@ -154,7 +154,7 @@ private fun OnboardingContentPreviewStep4Dark() { @Preview @Composable private fun OnboardingContentPreviewStep5Dark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContent(OnboardingComponentPreview(step = 5)) } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep1.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep1.kt index 921d966..a9ace46 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep1.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep1.kt @@ -82,7 +82,7 @@ private fun OnboardingContentStep1PreviewLight() { @Preview @Composable private fun OnboardingContentStep1PreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContentStep1(OnboardingStep1ComponentPreview()) } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep2.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep2.kt index 5954f10..52ddb0c 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep2.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep2.kt @@ -74,7 +74,7 @@ private fun OnboardingContentStep2PreviewLight() { @Preview @Composable private fun OnboardingContentStep2PreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContentStep2(OnboardingStep2ComponentPreview()) } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep3.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep3.kt index b4781cd..bec0372 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep3.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep3.kt @@ -106,7 +106,7 @@ private fun OnboardingContentStep3PreviewLight() { @Preview @Composable private fun OnboardingContentStep3PreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContentStep3(OnboardingStep3ComponentPreview()) } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep4.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep4.kt index 24ccb31..6bc9297 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep4.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep4.kt @@ -138,7 +138,7 @@ private fun OnboardingContentStep4PreviewLight() { @Preview @Composable private fun OnboardingContentStep4PreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContentStep4(OnboardingStep4ComponentPreview(true)) } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep5.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep5.kt index ff1adc4..a14db19 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep5.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/onboarding/OnboardingContentStep5.kt @@ -95,7 +95,7 @@ fun OnboardingContentStep5( Row( modifier = Modifier .fillMaxWidth() - .height(height = 32.dp) + .height(height = 48.dp) .selectable( selected = when (index) { 0 -> !model.showInitialSetup @@ -178,7 +178,7 @@ private fun OnboardingContentStep5PreviewLight() { @Preview(widthDp = 440, heightDp = 880) @Composable private fun OnboardingContentStep5PreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { OnboardingContentStep5PreviewContent() } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/preferences/PreferencesContent.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/preferences/PreferencesContent.kt index 360336e..62b2ad7 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/preferences/PreferencesContent.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/preferences/PreferencesContent.kt @@ -381,7 +381,7 @@ private fun StepperButton( ) { FilledTonalIconButton( onClick = onClick, - modifier = modifier.size(size = 40.dp), + modifier = modifier.size(size = 48.dp), ) { Icon( painter = painterResource(resource = iconRes), @@ -401,7 +401,7 @@ private fun Float.asDisplayValue(): String { } } -@Preview(widthDp = 420, heightDp = 1100) +@Preview(widthDp = 420, heightDp = 1020) @Composable private fun PreferencesContentPreviewLight() { BlinklyWidgetPreview { @@ -409,10 +409,10 @@ private fun PreferencesContentPreviewLight() { } } -@Preview(widthDp = 420, heightDp = 1100) +@Preview(widthDp = 420, heightDp = 1020) @Composable private fun PreferencesContentPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { PreferencesContent(component = PreferencesComponentPreview(themeState = ThemeState.DARK)) } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyButton.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyButton.kt index ac7ae61..53d6813 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyButton.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyButton.kt @@ -31,7 +31,7 @@ internal fun BlinklyButton( textColor: Color = MaterialTheme.colorScheme.onSecondaryContainer, buttonColor: Color = MaterialTheme.colorScheme.secondaryContainer, buttonShape: Shape = MaterialTheme.shapes.extraLarge, - buttonHeight: Dp = 44.dp, + buttonHeight: Dp = 48.dp, onClick: () -> Unit = {}, ) { Row( @@ -69,7 +69,7 @@ private fun BlinklyButtonPreviewLight() { @Preview @Composable private fun BlinklyButtonPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BlinklyButtonPreviewContent() } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyEyePanel.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyEyePanel.kt index b8a73f5..2360104 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyEyePanel.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyEyePanel.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -33,6 +34,8 @@ import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.graphics.luminance import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign @@ -72,6 +75,8 @@ fun BlinklyEyePanel( if (currentMovement == null) { pathMovement = null pathProgress.snapTo(0f) + offsetX.snapTo(0f) + offsetY.snapTo(0f) focusState = EyeFocusState.None openness.animateTo( targetValue = restState.openness, @@ -89,6 +94,8 @@ fun BlinklyEyePanel( when (currentMovement) { is EyeMovement.Blink -> { openness.snapTo(OPEN_EYE) + offsetX.snapTo(0f) + offsetY.snapTo(0f) openness.animateTo( targetValue = CLOSED_EYE, animationSpec = tween( @@ -107,11 +114,15 @@ fun BlinklyEyePanel( EyeMovement.AccommodationClose -> { focusState = EyeFocusState.Close + offsetX.snapTo(0f) + offsetY.snapTo(0f) openness.animateToOpen() } EyeMovement.AccommodationFar -> { focusState = EyeFocusState.Far + offsetX.snapTo(0f) + offsetY.snapTo(0f) openness.animateToOpen() } @@ -166,6 +177,8 @@ fun BlinklyEyePanel( -> { focusState = EyeFocusState.None openness.animateToOpen() + offsetX.snapTo(0f) + offsetY.snapTo(0f) pathMovement = currentMovement pathProgress.snapTo(0f) pathProgress.animateTo( @@ -269,8 +282,8 @@ private fun BlinklyEyeContent( movementOffset = movementOffset, focusState = focusState, surfaceColor = colorScheme.surface, - outlineColor = colorScheme.primary, - irisColor = colorScheme.tertiary, + outlineColor = colorScheme.eyeOutline, + irisColor = colorScheme.eyeIris, pupilColor = colorScheme.onSurface, highlightColor = colorScheme.surfaceContainerHighest, ) @@ -290,6 +303,22 @@ private fun BlinklyEyeContent( } } +private val ColorScheme.eyeOutline: Color + get() = + if (surfaceContainer.luminance() < DARK_SURFACE_LUMINANCE_THRESHOLD) { + primary.copy(alpha = DARK_OUTLINE_ALPHA) + } else { + primary + } + +private val ColorScheme.eyeIris: Color + get() = + if (surfaceContainer.luminance() < DARK_SURFACE_LUMINANCE_THRESHOLD) { + lerp(surface, tertiary, DARK_IRIS_BLEND_FRACTION) + } else { + tertiary + } + private fun DrawScope.drawBlinklyEye( openness: Float, movementOffset: Offset, @@ -490,7 +519,7 @@ private fun BlinklyEyeFarFocusPreviewLight() { @Preview @Composable private fun BlinklyEyeOpenPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BlinklyEyePreviewContent( focusState = EyeFocusState.None, openness = OPEN_EYE, @@ -502,7 +531,7 @@ private fun BlinklyEyeOpenPreviewDark() { @Preview @Composable private fun BlinklyEyeClosedPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BlinklyEyePreviewContent( focusState = EyeFocusState.None, openness = CLOSED_EYE, @@ -514,7 +543,7 @@ private fun BlinklyEyeClosedPreviewDark() { @Preview @Composable private fun BlinklyEyeFocusPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BlinklyEyePreviewContent( focusState = EyeFocusState.Close, openness = OPEN_EYE, @@ -527,7 +556,7 @@ private fun BlinklyEyeFocusPreviewDark() { @Preview @Composable private fun BlinklyEyeFarFocusPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BlinklyEyePreviewContent( focusState = EyeFocusState.Far, openness = OPEN_EYE, @@ -585,3 +614,6 @@ private const val EYE_OPEN_DURATION_MS = 180 private const val DIAGONAL_DURATION_MS = 1_000 private const val CIRCLE_DURATION_MS = 2_000 private const val EIGHT_DURATION_MS = 4_000 +private const val DARK_SURFACE_LUMINANCE_THRESHOLD = 0.2f +private const val DARK_OUTLINE_ALPHA = 0.72f +private const val DARK_IRIS_BLEND_FRACTION = 0.82f diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyIconButton.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyIconButton.kt index 282ea63..f53d122 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyIconButton.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyIconButton.kt @@ -48,7 +48,7 @@ internal fun BlinklyIconButton( textColor: Color = MaterialTheme.colorScheme.onSecondaryContainer, buttonColor: Color = MaterialTheme.colorScheme.secondaryContainer, buttonShape: Shape = MaterialTheme.shapes.extraLarge, - buttonHeight: Dp = 44.dp, + buttonHeight: Dp = 48.dp, iconSize: Dp = 22.dp, alphaEnabled: Float = 1f, alphaDisabled: Float = 0.4f, @@ -136,7 +136,7 @@ private fun BlinklyIconButtonPreviewLight() { @Preview @Composable private fun BlinklyIconButtonPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BlinklyIconButtonPreviewContent() } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyOutlineButton.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyOutlineButton.kt index cabbff6..4cfc2c7 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyOutlineButton.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyOutlineButton.kt @@ -39,7 +39,7 @@ internal fun BlinklyOutlineButton( buttonShape: Shape = MaterialTheme.shapes.extraLarge, borderColor: Color = MaterialTheme.colorScheme.secondaryContainer, borderWidth: Dp = 2.dp, - buttonHeight: Dp = 44.dp, + buttonHeight: Dp = 48.dp, alphaEnabled: Float = 1f, alphaDisabled: Float = 0.4f, animationDuration: Int = 150, @@ -93,7 +93,7 @@ private fun BlinklyOutlineButtonPreviewLight() { @Preview @Composable private fun BlinklyOutlineButtonPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BlinklyOutlineButtonPreviewContent() } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyTimePickerDialog.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyTimePickerDialog.kt index c731c47..4ea4abd 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyTimePickerDialog.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/BlinklyTimePickerDialog.kt @@ -1,12 +1,12 @@ package com.sedsoftware.blinkly.compose.ui.widget -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.Card import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TimeInput import androidx.compose.material3.TimePickerState import androidx.compose.material3.rememberTimePickerState @@ -65,23 +65,21 @@ private fun BlinklyTimePickerDialogDialogContent( Row( modifier = Modifier.padding(bottom = 16.dp) ) { - Text( - text = stringResource(resource = Res.string.common_ok), - color = MaterialTheme.colorScheme.secondary, - style = MaterialTheme.typography.labelMedium, - modifier = Modifier - .padding(horizontal = 16.dp) - .clickable(onClick = onConfirm) - ) + TextButton(onClick = onConfirm) { + Text( + text = stringResource(resource = Res.string.common_ok), + color = MaterialTheme.colorScheme.secondary, + style = MaterialTheme.typography.labelMedium, + ) + } - Text( - text = stringResource(resource = Res.string.common_cancel), - color = MaterialTheme.colorScheme.secondary, - style = MaterialTheme.typography.labelMedium, - modifier = Modifier - .padding(horizontal = 16.dp) - .clickable(onClick = onDismiss) - ) + TextButton(onClick = onDismiss) { + Text( + text = stringResource(resource = Res.string.common_cancel), + color = MaterialTheme.colorScheme.secondary, + style = MaterialTheme.typography.labelMedium, + ) + } } } } @@ -98,7 +96,7 @@ private fun BlinklyTimePickerDialogPreviewLight() { @Preview @Composable private fun BlinklyTimePickerDialogPreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { BlinklyTimePickerDialogPreviewContent() } } diff --git a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/WeekDayToggle.kt b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/WeekDayToggle.kt index 0397027..d197a24 100644 --- a/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/WeekDayToggle.kt +++ b/shared/compose/src/commonMain/kotlin/com/sedsoftware/blinkly/compose/ui/widget/WeekDayToggle.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -38,7 +39,7 @@ fun WeekDayToggle( toggledTextColor: Color = MaterialTheme.colorScheme.onPrimaryContainer, backgroundColor: Color = MaterialTheme.colorScheme.background, toggledBackgroundColor: Color = MaterialTheme.colorScheme.primaryContainer, - size: Dp = 44.dp, + size: Dp = 48.dp, borderWidth: Dp = 1.dp, animationDurationMs: Int = 250, onToggle: () -> Unit = {}, @@ -87,6 +88,7 @@ fun WeekDayToggle( .clickableOnce( onClick = onToggle, debounceMs = 150L, + role = Role.Checkbox, ), contentAlignment = Alignment.Center, ) { @@ -110,7 +112,7 @@ private fun WeekDayTogglePreviewLight() { @Preview @Composable private fun WeekDayTogglePreviewDark() { - BlinklyWidgetPreview(isDakTheme = true) { + BlinklyWidgetPreview(isDarkTheme = true) { WeekDayTogglePreviewContent() } } diff --git a/shared/compose/src/iosMain/kotlin/main.kt b/shared/compose/src/iosMain/kotlin/main.kt index 76d5376..0ce2f16 100644 --- a/shared/compose/src/iosMain/kotlin/main.kt +++ b/shared/compose/src/iosMain/kotlin/main.kt @@ -38,16 +38,16 @@ fun MainViewController(): UIViewController { return ComposeUIViewController { RootContent( component = rootComponent, - onThemeChanged = { ThemeChanged(it) }, + onSystemBarsAppearanceChanged = { SystemBarsAppearanceChanged(it) }, ) } } @Composable -private fun ThemeChanged(isDark: Boolean) { - LaunchedEffect(isDark) { +private fun SystemBarsAppearanceChanged(useDarkIcons: Boolean) { + LaunchedEffect(useDarkIcons) { UIApplication.sharedApplication.setStatusBarStyle( - if (isDark) UIStatusBarStyleDarkContent else UIStatusBarStyleLightContent + if (useDarkIcons) UIStatusBarStyleDarkContent else UIStatusBarStyleLightContent ) } } diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewdark.w420dp_h500dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewdark.w420dp_h500dp.png new file mode 100644 index 0000000..03b567f --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewdark.w420dp_h500dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca70d448d1cd8779a676c48c7590860fadfd6093fbc0d61eb6a5334e4133475b +size 121788 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewdark.w420dp_h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewdark.w420dp_h520dp.png deleted file mode 100644 index 116b1d7..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewdark.w420dp_h520dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca7fa49a747662640cc987429c00392a4919f2a870ac0d3a6b33629c0838e94d -size 116019 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewlight.w420dp_h500dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewlight.w420dp_h500dp.png new file mode 100644 index 0000000..e228fa6 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewlight.w420dp_h500dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29262c2e1c3c603463324452de520967603933701e00bd29ff46824094e9bb47 +size 119508 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewlight.w420dp_h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewlight.w420dp_h520dp.png deleted file mode 100644 index 4badb83..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementdetailssheetpreviewlight.w420dp_h520dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56ca2096c5e8b207006a1b6ca869ed94e9716c262b3b7b9647122cf415e2fe25 -size 113707 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewdark.w420dp_h1060dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewdark.w420dp_h1060dp.png new file mode 100644 index 0000000..724f2dd --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewdark.w420dp_h1060dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afc21e57e5fc081543c393459b2da0a4f7bd545fb91a0f51590f7098b3938702 +size 72517 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewdark.w420dp_h1200dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewdark.w420dp_h1200dp.png deleted file mode 100644 index a6fb5b9..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewdark.w420dp_h1200dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:383179b49081bdadd7001372bc7d51860efb95eed46cabb67ae5f560da6ce720 -size 60529 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewlight.w420dp_h1060dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewlight.w420dp_h1060dp.png new file mode 100644 index 0000000..bfd5f84 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewlight.w420dp_h1060dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:754fd39cdfbc52fee6f0c5cc3b42ec89649d829d85cbbfc43c76693c72617ef4 +size 71499 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewlight.w420dp_h1200dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewlight.w420dp_h1200dp.png deleted file mode 100644 index e1e4863..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_achievements.achievementscontentkt.achievementscontentpreviewlight.w420dp_h1200dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d44eab18d991f9e84e36259621561b768607b2e7daa845d8f9224562ec4016fd -size 59821 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreview.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreview.w390dp_h780dp.png new file mode 100644 index 0000000..00fcba8 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreview.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:420eb11c1bb2bb96b972a0c00a8188e0d96260c3b57997f82b9c2912b111c0af +size 44560 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreview.w390dp_h900dp.png deleted file mode 100644 index bcb2819..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01f3c6b4619dfad5e7c98c09d8fd6f5a2b7302e1fc8573a010fd0c624bf8d7bc -size 36960 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreviewdark.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreviewdark.w390dp_h780dp.png new file mode 100644 index 0000000..c2d1a58 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutblinkbreakpreviewdark.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a075f5dd2ee88e4aa69ca9c5cdb1d49ec794f4533a290c143de063ae659bb3b +size 45288 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreview.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreview.w390dp_h780dp.png new file mode 100644 index 0000000..83dee64 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreview.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e55a701762ab89011932d6ba9a65185b86ed248286dbcecc0f432aec3d9cf79f +size 41905 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreview.w390dp_h900dp.png deleted file mode 100644 index e7b1a0a..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f1da951a2b162b37d1b5b9bc1b2e768fb4c727a72b905c0aee192e0b4f7fe3db -size 34705 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreviewdark.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreviewdark.w390dp_h780dp.png new file mode 100644 index 0000000..b870d4b --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutclockrollspreviewdark.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbc144e6adac0108a6a1c0624efff3da44567ea9b247e9c07e820abab355bb4c +size 42827 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreview.w390dp_h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreview.w390dp_h520dp.png new file mode 100644 index 0000000..e65cae9 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreview.w390dp_h520dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e513177d46158baeaccf505548a8e1b09ce1e7fb5a0cd94add0a546769615064 +size 31118 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreview.w390dp_h900dp.png deleted file mode 100644 index acbba51..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:37e6383d389ca85f3943c9c56b17ec7f85452ac7a7f1082ddf1e319bc76ca478 -size 17084 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreviewdark.w390dp_h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreviewdark.w390dp_h520dp.png new file mode 100644 index 0000000..3afe14a --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutcompletedpreviewdark.w390dp_h520dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea3b484cdd38c014b9268120b461c8de96236153e56398c843e5a9547661834c +size 31287 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreview.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreview.w390dp_h780dp.png new file mode 100644 index 0000000..ecb89c8 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreview.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91a79913cdf382faf9dd01eced5bcbbbb6a09273dd9e71bf7c57ada99461100a +size 45139 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreview.w390dp_h900dp.png deleted file mode 100644 index 2f2beb7..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ccedb8c5b7490d12e28dcbe20a5b0e254ed969a14ab4d237e818e40cdf7da32 -size 37684 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreviewdark.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreviewdark.w390dp_h780dp.png new file mode 100644 index 0000000..5f8933c --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutdiagonalpreviewdark.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fd0764232dc6dea11e270960f27eb8b284214c67fab8bc902443bd8ea8b6226 +size 45988 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreview.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreview.w390dp_h780dp.png new file mode 100644 index 0000000..45dd720 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreview.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:add0b25570740e430a5fc1d26a2db92c24102fb6964afe897c65bea11504d225 +size 42848 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreview.w390dp_h900dp.png deleted file mode 100644 index 57a20a8..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb52ddb28b78252e8f524e7d7e67988a73a131b67a2fe9e4a23c286abd4cf7e0 -size 35582 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreviewdark.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreviewdark.w390dp_h780dp.png new file mode 100644 index 0000000..1c8a16c --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutfigureeightpreviewdark.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:766903b490883974af723a788612ca9ff1e0e44e4d12f2a90735b6d774af7c8e +size 43803 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreview.w390dp_h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreview.w390dp_h520dp.png new file mode 100644 index 0000000..1c17ddd --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreview.w390dp_h520dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4216e96c7361748935cfb76bba3e098bf663fd9b224b7b5408ed52527e6efe35 +size 61102 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreview.w390dp_h900dp.png deleted file mode 100644 index f96a4e6..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5b70222d041126dd52b54f1b29336e1f8d0ea7aae3cfc6cfbd71f328c22cf52a -size 32879 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreviewdark.w390dp_h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreviewdark.w390dp_h520dp.png new file mode 100644 index 0000000..6badaff --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutintropreviewdark.w390dp_h520dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69e7d4e7495d568423eb804f934a1718f6e04582ea68f913305a376e9e6c04b8 +size 61394 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreview.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreview.w390dp_h780dp.png new file mode 100644 index 0000000..1f670ab --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreview.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93c58ff6f557feec332fd9896e8a9ae1ecbddbf18c931f6367a50eed9d5bd7c7 +size 51714 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreview.w390dp_h900dp.png deleted file mode 100644 index a02666a..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd85c2dd5e9d25d623e731b7d0bba74bdb65c5074c556865cf1845e6c25e6ffc -size 42796 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreviewdark.w390dp_h780dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreviewdark.w390dp_h780dp.png new file mode 100644 index 0000000..e90d3d7 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutnearfarpreviewdark.w390dp_h780dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8578f133892f25863575778a8bdd6795b204a5e8d2869f4db57fee95325a5024 +size 52893 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreview.w390dp_h860dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreview.w390dp_h860dp.png new file mode 100644 index 0000000..43ea061 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreview.w390dp_h860dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f5457bf84327f0748731f11a5099bdd502fbd4d04c2ee28839d242604b3eca9 +size 50168 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreview.w390dp_h900dp.png deleted file mode 100644 index 447d995..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e59aed564e70dfeae24620f92f60c966bd729c505cf274f7649e57374fc30257 -size 47485 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreviewdark.w390dp_h860dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreviewdark.w390dp_h860dp.png new file mode 100644 index 0000000..895d919 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workoutpalmingpreviewdark.w390dp_h860dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73fd6f1697402a4b38800c82fa5fa0e994860db87c4c6217af64f379ab8f5674 +size 51346 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreview.w390dp_h800dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreview.w390dp_h800dp.png new file mode 100644 index 0000000..ae9dd22 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreview.w390dp_h800dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7202083d8198ff853904b07a497726f51d673fcdd695bacc11ecec0d969c44e7 +size 44366 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreview.w390dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreview.w390dp_h900dp.png deleted file mode 100644 index 39827c3..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreview.w390dp_h900dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:451a19522fd70fa9cfc7df9d1f43dcab0257218e70f270ed4cbc28963f39a1d7 -size 38109 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreviewdark.w390dp_h800dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreviewdark.w390dp_h800dp.png new file mode 100644 index 0000000..1c30602 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_exercises.workoutcontentkt.workouttwentypreviewdark.w390dp_h800dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6651b2737c9868f936f0946328d9a4de87866ed3bfa2683b6ec0c29829cee2dc +size 45229 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewdark.w420dp_h1100dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewdark.w420dp_h1100dp.png deleted file mode 100644 index 79197bb..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewdark.w420dp_h1100dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a002be6f7f5c640cbe67f550715c6331585a87c8ba0537e571485abf4920a2d9 -size 80450 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewdark.w420dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewdark.w420dp_h900dp.png new file mode 100644 index 0000000..06e0432 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewdark.w420dp_h900dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97dc64cefa3cffd54cbcc30885af146380461c7296565ff657a33050e8e6f7a1 +size 110306 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewlight.w420dp_h1100dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewlight.w420dp_h1100dp.png deleted file mode 100644 index 0ea5450..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewlight.w420dp_h1100dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfe2b566bae76ae0459bea0168b89326fe0d0fb7a57f20dc5f406fba17dcb3ec -size 79281 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewlight.w420dp_h900dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewlight.w420dp_h900dp.png new file mode 100644 index 0000000..191f174 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardencontentpreviewlight.w420dp_h900dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5a29188de32457a3d0bcb734f220dc4c3a843da345cad1bf794f8b4edfcbd28 +size 108508 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewdark.w420dp_h500dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewdark.w420dp_h500dp.png new file mode 100644 index 0000000..7590871 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewdark.w420dp_h500dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ea20d8d3c03de2455bbc86014efeb8aa56ef05b3eff3fbc2f9cc2b82a704b4b +size 349346 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewdark.w420dp_h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewdark.w420dp_h520dp.png deleted file mode 100644 index 44fdc55..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewdark.w420dp_h520dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5eddddf504a248b11aee7de283420bbe1d4674134f7ab6cdac6023a68e94e3b1 -size 324734 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewlight.w420dp_h500dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewlight.w420dp_h500dp.png new file mode 100644 index 0000000..1887590 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewlight.w420dp_h500dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1b3d3944af1f8a7829ee4aa20d11ec3be170d254ee6dfd756f87d8a537ffd55 +size 343555 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewlight.w420dp_h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewlight.w420dp_h520dp.png deleted file mode 100644 index d9e7c45..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_garden.gardencontentkt.gardentreedetailssheetpreviewlight.w420dp_h520dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7dc35457278f54fbac75b1f1846d2221018cfc3cb21c01a18e1e476fd4cc40a -size 319316 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.bottomnavigation.bottomnavigationbarkt.bottomnavigationbarpreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.bottomnavigation.bottomnavigationbarkt.bottomnavigationbarpreviewdark.png index 4799f0d..dd18dc1 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.bottomnavigation.bottomnavigationbarkt.bottomnavigationbarpreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.bottomnavigation.bottomnavigationbarkt.bottomnavigationbarpreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1245f0380fc3dbaed6ed6295770cce7a75339144626bd60553c82bd88fa3164 -size 21015 +oid sha256:c47bbf4bdc526a676682895bcf4613f3ccf23ac75110f2424e322e6e6a4a3097 +size 19115 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.bottomnavigation.bottomnavigationbarkt.bottomnavigationbarpreviewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.bottomnavigation.bottomnavigationbarkt.bottomnavigationbarpreviewlight.png index b927e95..59e006a 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.bottomnavigation.bottomnavigationbarkt.bottomnavigationbarpreviewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.bottomnavigation.bottomnavigationbarkt.bottomnavigationbarpreviewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60af24ebd47eb6373a8ecf77c91580a7fe6aea6a07748571309fd17816b6478c -size 21032 +oid sha256:3374e6ebe781fd3c7c67a774efa5bd2d8086b1cc24a96435fb34ca8d5f61f096 +size 19180 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewdark.w420dp_h740dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewdark.w420dp_h740dp.png new file mode 100644 index 0000000..4920c7b --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewdark.w420dp_h740dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b46182793bdd7035563479492680e12d3b547ca68b314c5009fe3d07eccf2ba +size 76423 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewdark.w420dp_h920dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewdark.w420dp_h920dp.png deleted file mode 100644 index ff977a3..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewdark.w420dp_h920dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a32e9f87e384c87c975eb3de07e0d0492a36721b1b97d1f5f9b66540e0de580d -size 57000 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewlight.w420dp_h740dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewlight.w420dp_h740dp.png new file mode 100644 index 0000000..b32a80c --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewlight.w420dp_h740dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67e0fea33d9fa956849d60d05a284ce0daed10628396d4458a5a4015097f6ed7 +size 77693 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewlight.w420dp_h920dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewlight.w420dp_h920dp.png deleted file mode 100644 index b935689..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentphonepreviewlight.w420dp_h920dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8748af5fab1722fd450004ac0418cdb127089fb0205cd1ac71874e44d7eb9457 -size 57757 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewdark.w1200dp_h1700dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewdark.w1200dp_h1700dp.png new file mode 100644 index 0000000..9cce518 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewdark.w1200dp_h1700dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d39dd2500251d40cbf875f4d2b21976cfb7d6d11bc6c0e4ddd86dd16be64a3a6 +size 159181 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewdark.w1200dp_h1800dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewdark.w1200dp_h1800dp.png deleted file mode 100644 index eb3fd7e..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewdark.w1200dp_h1800dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1189d88894100fcdb6dd5c2184f334ab40ba840f3ca90ef6f9b0d6b5f48d717e -size 146511 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewlight.w1200dp_h1700dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewlight.w1200dp_h1700dp.png new file mode 100644 index 0000000..b095927 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewlight.w1200dp_h1700dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:626fc3d931672712c5f5e44bc23cd7db94c78b1e97f365171abe70aa38ed6086 +size 159319 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewlight.w1200dp_h1800dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewlight.w1200dp_h1800dp.png deleted file mode 100644 index 2fbbaa0..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.maintabcontentkt.maintabcontentpreviewlight.w1200dp_h1800dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7e99f48626e3d060d8059ea784ac4c9ffe3ab167986bd52eb25d5fa11eefc59 -size 146746 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewdark.w420dp_h1060dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewdark.w420dp_h1060dp.png deleted file mode 100644 index f54adf0..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewdark.w420dp_h1060dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:459bfd9395dfdf219242542b96c9c059ce92128cc74939b1b174438b58777395 -size 86989 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewdark.w420dp_h920dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewdark.w420dp_h920dp.png new file mode 100644 index 0000000..886418c --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewdark.w420dp_h920dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac015cbeda0904239933cacc4b81e4dc95d88365d5b1ecd93bd20df701612136 +size 107914 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewlight.w420dp_h1060dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewlight.w420dp_h1060dp.png deleted file mode 100644 index ae76c37..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewlight.w420dp_h1060dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07b1d2bcbe83c94de64f797a347a08d0dadab03cc9a0746454930450152a832b -size 85731 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewlight.w420dp_h920dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewlight.w420dp_h920dp.png new file mode 100644 index 0000000..0bd1099 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.progresstabcontentkt.progresstabcontentpreviewlight.w420dp_h920dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9ddb39bb09a38835ef897766ffd2fdf96ceeff6cad7d639f165267218e6535d +size 106298 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentemptypreviewlight.w440dp_h560dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentemptypreviewlight.w440dp_h560dp.png new file mode 100644 index 0000000..4eb71fd --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentemptypreviewlight.w440dp_h560dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:224017f4f8de60d1a8f6965a7fa942dad6ea566c7cb9ffeffd01c875a1c9ced2 +size 45633 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentemptypreviewlight.w440dp_h880dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentemptypreviewlight.w440dp_h880dp.png deleted file mode 100644 index 4e67ccf..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentemptypreviewlight.w440dp_h880dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2122d31a51cc936cdee63102d9c703d420fa5976bd5a8b3ede2e518b2acd3862 -size 28133 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewdark.w440dp_h560dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewdark.w440dp_h560dp.png new file mode 100644 index 0000000..597da78 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewdark.w440dp_h560dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a443fa78c1bdf4e070df8ee85ed6fad38dfaa724036ddd1aae0e050f7f360fe9 +size 98848 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewdark.w440dp_h880dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewdark.w440dp_h880dp.png deleted file mode 100644 index 1aa0a70..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewdark.w440dp_h880dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6548ef7a2981756b0cdd97da61b78a5a5864e4602d778cf4ab7abd5aba1f5278 -size 58676 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewlight.w440dp_h560dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewlight.w440dp_h560dp.png new file mode 100644 index 0000000..3952b69 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewlight.w440dp_h560dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b22b29865ab2f4b7d176ee003a33522fb429f0f28f1c01c400d2100cff2462c8 +size 98905 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewlight.w440dp_h880dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewlight.w440dp_h880dp.png deleted file mode 100644 index 4fd1190..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.reminderstabcontentkt.reminderstabcontentpreviewlight.w440dp_h880dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2b6a814c4beed545690fb078857e4b65e30f625d4b80d4965d3592a62c53a88 -size 58134 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewdark.w420dp_h800dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewdark.w420dp_h800dp.png new file mode 100644 index 0000000..e57d202 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewdark.w420dp_h800dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29991dd10ed22e6f22ad3cf5d16e9c0704bad64d369ba1d4bb6a2f702650d810 +size 85718 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewdark.w420dp_h920dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewdark.w420dp_h920dp.png deleted file mode 100644 index fbe6dbb..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewdark.w420dp_h920dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c266a874b62998a7a528f21d73b950577c45a09963729253afb03a1b4fba108d -size 73133 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewlight.w420dp_h800dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewlight.w420dp_h800dp.png new file mode 100644 index 0000000..15b22f9 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewlight.w420dp_h800dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb0b488b82bfb13061f04c57950df64d28ebb769e40801452f4d61de8707f557 +size 81308 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewlight.w420dp_h920dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewlight.w420dp_h920dp.png deleted file mode 100644 index 9b495ab..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_home.tabs.trainingstabcontentkt.trainingstabcontentpreviewlight.w420dp_h920dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3f4591452a046d9fa0fa4ea9eaf1189fbdaca54a23999ef7de5500785449886 -size 69539 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewdark.w440dp_h660dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewdark.w440dp_h660dp.png new file mode 100644 index 0000000..f1b6a1c --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewdark.w440dp_h660dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:829b7a7caf2df6303d127a3208c39bb033d4f0d5e308bec4bfeb1c29ee599e60 +size 73449 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewlight.w440dp_h660dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewlight.w440dp_h660dp.png new file mode 100644 index 0000000..3dba560 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewlight.w440dp_h660dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:246629ccd63198098c5db531b2c539dbe90dde226f87b1379b60cb14c4bead69 +size 73826 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewlight.w440dp_h880dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewlight.w440dp_h880dp.png deleted file mode 100644 index d3f3707..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentdailypreviewlight.w440dp_h880dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aee8f501ea7ca88fae42e87ec1fc39c53c8f4f3f171d31e51d203435d022ed5a -size 58084 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewdark.w440dp_h840dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewdark.w440dp_h840dp.png new file mode 100644 index 0000000..e130fd4 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewdark.w440dp_h840dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2091a09ca98ebc1524690b33e0a7ec6147946d0ddba717ff8487b000cf1a5675 +size 91135 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewdark.w440dp_h880dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewdark.w440dp_h880dp.png deleted file mode 100644 index 42f817d..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewdark.w440dp_h880dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2cf5534331267492406777220519dc3ffd1f1a47f131dc8aba96f7a2fa0b3459 -size 83712 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewlight.w440dp_h840dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewlight.w440dp_h840dp.png new file mode 100644 index 0000000..273cfed --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewlight.w440dp_h840dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:236de19aa0bf21367ca41840881997ebc2fdcd8141ebd5051613203554717268 +size 92061 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewlight.w440dp_h880dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewlight.w440dp_h880dp.png deleted file mode 100644 index 0117f7a..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addnewremindercontentkt.addnewremindercontentpreviewlight.w440dp_h880dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11485c9ee55885d2a5957ea74206914364cb63b90ed266a5c15d2b38c4efd5db -size 84887 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcreatedpreviewdark.h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcreatedpreviewdark.h520dp.png new file mode 100644 index 0000000..206c646 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcreatedpreviewdark.h520dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83b2e7aaa4c5b51341b35b8b4ae3fe9ad4c3ae3b2973425497e227c52cd6342a +size 71994 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcreatedpreviewlight.h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcreatedpreviewlight.h520dp.png new file mode 100644 index 0000000..2c666d1 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcreatedpreviewlight.h520dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dab1b116699e0f630ac9eb56f75605328c5caf40f4bebd8f3817825e72b0394 +size 72074 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcustompreviewdark.h560dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcustompreviewdark.h560dp.png new file mode 100644 index 0000000..dc16a43 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcustompreviewdark.h560dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:857a7c58e3914d35914362e06e64c2b65b51394f17e7e456f4b3a97c8fa40490 +size 66732 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcustompreviewlight.h560dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcustompreviewlight.h560dp.png new file mode 100644 index 0000000..b383b17 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentcustompreviewlight.h560dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:964474d4c1f1233d3e7dc5f051c9e6429b0c57a89174c4a88428dbf9d9bdee72 +size 66956 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentdefaultpreviewdark.h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentdefaultpreviewdark.h520dp.png new file mode 100644 index 0000000..60362dd --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentdefaultpreviewdark.h520dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c32bd052f5eaa4869c568290fb43859d4ab36d5bb8cb3464f75e667952ac1e7 +size 66233 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentdefaultpreviewlight.h520dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentdefaultpreviewlight.h520dp.png new file mode 100644 index 0000000..d1e148c --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentdefaultpreviewlight.h520dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1b73f795a03cfb4ee68ae7b3b9bd874b12d9b56349544f7e6368330a0977561 +size 66261 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentpreviewdark.h1600dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentpreviewdark.h1600dp.png deleted file mode 100644 index 5a88e92..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentpreviewdark.h1600dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0a7105ea196dffaa32694160c1e91ffce27efb6e4e65c0da517041c195151d4 -size 48807 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentpreviewlight.h1600dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentpreviewlight.h1600dp.png deleted file mode 100644 index 887929c..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_newreminder.addweeklyperiodcontentkt.addweeklyperiodcontentpreviewlight.h1600dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:046da4d9dcd4b47ed897099fa75d69bd6ddbbba4c8826c494c3a3699fec9e204 -size 48340 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.bottomnavigationbuttonskt.bottomnavigationbuttonspreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.bottomnavigationbuttonskt.bottomnavigationbuttonspreviewdark.png index ecae269..a066564 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.bottomnavigationbuttonskt.bottomnavigationbuttonspreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.bottomnavigationbuttonskt.bottomnavigationbuttonspreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7830ede53d26547fef2b14c38164ceb0f386ca48482305d1f3d3d6267c87856c -size 17583 +oid sha256:17e223910e0ca5cdec306d37d203140f1bdfe1b85246440aa6fe9245cbc43c0b +size 18232 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.bottomnavigationbuttonskt.bottomnavigationbuttonspreviewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.bottomnavigationbuttonskt.bottomnavigationbuttonspreviewlight.png index 22dfb2b..2211d41 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.bottomnavigationbuttonskt.bottomnavigationbuttonspreviewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.bottomnavigationbuttonskt.bottomnavigationbuttonspreviewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76f01756066afb34283df2176f3a8aa481e665392fe74b7a779694412fbc728e -size 17378 +oid sha256:39bf19f2ca0bf5d3daf5c6cdb4cf56aa4a20230c20872a314ac7364f3ce2a339 +size 17943 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep1dark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep1dark.png index 8b64f46..ea8b85f 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep1dark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep1dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3247dc968632e97ebc7c547949fd5d92810ae89d7f261611d521fae4b9f325b8 -size 20149 +oid sha256:b677fa48801ab3fadcba9a58ad2a6d1d3fc4e5838cab370418dc0c9be3b5e5a4 +size 20297 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep1light.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep1light.png index e9b2409..90c1601 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep1light.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep1light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e04551e241a8ee4a8139a7d2cc69aff76f1035c6cb5c7638bb973a46bd500522 -size 20089 +oid sha256:9d23c6a9d47e8ed3d09c4e1bbc44ea4e64e7fb8fcf863f553ece92f84f3b393f +size 20211 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep2dark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep2dark.png index 279ce02..bf062e0 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep2dark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep2dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45dac7abe0b56abae25ed6de6d037359fcb60030c5ee470ef5afbbfd034606e8 -size 32270 +oid sha256:30b50c53a236f816db31a91195f7e70bacf89e853d598979505959fced465447 +size 32549 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep2light.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep2light.png index dbdaa62..5342c18 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep2light.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep2light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bd021d49547d9c7853364d9b492da663dad60ab26157eaf82a5b3d866d022e9 -size 32309 +oid sha256:7499a4c03ce9b307593087e7a31c74c4ea6a4e8c09208a16ce7a0df1482b7a2a +size 32548 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep3dark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep3dark.png index 7302376..9a47525 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep3dark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep3dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99d6e1c0a5932338774869b003f723aec13174772f264c1fa83e092e62b52f31 -size 122188 +oid sha256:c16d73d6a47f92fa6744930cafc078b0f67c9ed5e64b13a4a6b4e9462955e757 +size 122087 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep3light.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep3light.png index dfb80c5..f7d3873 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep3light.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep3light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59a1c078735cde8547540078ca5e083eee1ea4917c6d323e9ec04322eb5a85bc -size 121502 +oid sha256:ca067829c0890178100ecb2860cb0c19a28016756bf66ede864ada2e82cdaebb +size 121371 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep4dark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep4dark.png index 375d01d..ab4a458 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep4dark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep4dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2ca4c86735685c39704d547e925a99cc356dd8142a7e79007165a01019ee7df -size 105641 +oid sha256:e489cb33ba15ac8dbc91f8ab01d42ac9c8536af2476f6f0d55b0443c21811794 +size 105940 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep4light.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep4light.png index a78a38f..1a75517 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep4light.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep4light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0da75d7d13612b3183f1d67727ada7e84114b2c1549a72c3d980a836807533f8 -size 105767 +oid sha256:797f6df65798c01e2e72fa98d37d0fca809f4e8d4bc377a315e61b5366ff5bb7 +size 106017 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep5dark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep5dark.png index bd6fc86..c3fb482 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep5dark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep5dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:708b62bc6834f738db557dc4f159cb21307e31234fccd5747f33f05fa740f592 -size 46649 +oid sha256:8d33f57f3e0078d003bd70210c7897d5cd6dd08705624c0379d94c9bab5fd30a +size 47204 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep5light.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep5light.png index 2bb9667..cf1221d 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep5light.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentkt.onboardingcontentpreviewstep5light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49c0ce7f45da92d42c45fb0afbd23e85e79128377c6b9316be3648839a25ffae -size 46629 +oid sha256:b95c8e6212b087debe594ec8cb8db52d99686a4575ccb120bc60f562c5d863c6 +size 47126 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep1kt.onboardingcontentstep1previewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep1kt.onboardingcontentstep1previewdark.png index d6126eb..0b077ca 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep1kt.onboardingcontentstep1previewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep1kt.onboardingcontentstep1previewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d08ceb907d6daae215a1914c47b4e3b69ddeb650e7016f241720ce606cb33e7 -size 18524 +oid sha256:4f08cae122d147e7851e0abf5cc69d5a726231c9e3ccbfd1ed1defb15fba7320 +size 18660 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep1kt.onboardingcontentstep1previewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep1kt.onboardingcontentstep1previewlight.png index ee8115a..6c59997 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep1kt.onboardingcontentstep1previewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep1kt.onboardingcontentstep1previewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b90f0f3daa2dfafee52e3b99a2d5557f923d40ffe3a4ea2cd44dd787272e78e -size 18380 +oid sha256:32ab7887bf219c4f225a6c78f2c4bfde646deb3bc1d434220a122c005886fcd0 +size 18507 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep2kt.onboardingcontentstep2previewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep2kt.onboardingcontentstep2previewdark.png index efae73b..63da0e3 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep2kt.onboardingcontentstep2previewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep2kt.onboardingcontentstep2previewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2eda9851dadb6c11c4e3b9ae35ff1f800d28c707400437f122b15d9ec6395c5b -size 30633 +oid sha256:72f0da33459b186f3259fbf2811711cfdedf94281cea3fa98def9a9bfd0865d4 +size 30914 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep2kt.onboardingcontentstep2previewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep2kt.onboardingcontentstep2previewlight.png index 4f44ac2..8711b47 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep2kt.onboardingcontentstep2previewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep2kt.onboardingcontentstep2previewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd7fd39663cd5044d50971077c1012b937a5d9bb96b2e4d7fa8accc111a3ca8d -size 30568 +oid sha256:eadeafb78225a2be5ac66f596354be1ef77eb7d89b25fd5593c65320f8402304 +size 30791 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep3kt.onboardingcontentstep3previewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep3kt.onboardingcontentstep3previewdark.png index 5952af0..0ceb83b 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep3kt.onboardingcontentstep3previewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep3kt.onboardingcontentstep3previewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:329c6f53490d2db58adbb7213e63cdc28b503ee9b4603c4f2349f0612c93393c -size 120734 +oid sha256:f4e92e0704746a52bbdf0cbd36a62684ecb04fced24f6121d5aa60e2d14f9599 +size 120621 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep3kt.onboardingcontentstep3previewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep3kt.onboardingcontentstep3previewlight.png index 74b21cf..9851436 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep3kt.onboardingcontentstep3previewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep3kt.onboardingcontentstep3previewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ff93696e5fa0f203421dfe208cfa8e11cd446050146b721503e29b4e0d9b838 -size 119959 +oid sha256:aaba7d1514efe0d3f5f5cd1c1c21f8fd844969ac1380099c290045d97e77ad71 +size 119826 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep4kt.onboardingcontentstep4previewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep4kt.onboardingcontentstep4previewdark.png index ed36173..dcc9832 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep4kt.onboardingcontentstep4previewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep4kt.onboardingcontentstep4previewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91785ad8be70b7c1f4b99d8496bea891aa524876ca13ea6d289fa72857955398 -size 104121 +oid sha256:42d5458ee1ce3b3a70e326327c8537bf54591f58068c4ab6bdd642191937548f +size 104420 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep4kt.onboardingcontentstep4previewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep4kt.onboardingcontentstep4previewlight.png index aa14021..4e327a7 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep4kt.onboardingcontentstep4previewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep4kt.onboardingcontentstep4previewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5e0811ffafbd9d489ce279b4d4f007d5b0476f52710c6af5cad56e8cd87798d -size 103800 +oid sha256:7acb5b51113b88020e233d2102390af634b9c7d4e5a9ba64f600d467252c25d3 +size 104102 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep5kt.onboardingcontentstep5previewdark.w440dp_h880dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep5kt.onboardingcontentstep5previewdark.w440dp_h880dp.png index 5cf6c8a..de73210 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep5kt.onboardingcontentstep5previewdark.w440dp_h880dp.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep5kt.onboardingcontentstep5previewdark.w440dp_h880dp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a6d413f99ce3cc0a79d0c6afe90b05583f7a14d870c87a8f89c7f500fe164bd -size 75534 +oid sha256:3e4f796f6e44280017f4f1c214175fb10a9a561e0646586dffe963779650cb7f +size 71405 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep5kt.onboardingcontentstep5previewlight.w440dp_h880dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep5kt.onboardingcontentstep5previewlight.w440dp_h880dp.png index d5a823c..6610d74 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep5kt.onboardingcontentstep5previewlight.w440dp_h880dp.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_onboarding.onboardingcontentstep5kt.onboardingcontentstep5previewlight.w440dp_h880dp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e907dae75c8e111ab1c199d6178e3958d2a9ed67d2822355fb4f41b0da82fcb3 -size 75441 +oid sha256:999e42f380bf3428b2800e58b2b6a879566f17b263cd0ef9aa8f9a8487e3c21e +size 71412 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewdark.w420dp_h1020dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewdark.w420dp_h1020dp.png new file mode 100644 index 0000000..5a20a50 --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewdark.w420dp_h1020dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:292cde9b1056123f5049399ea301aeb2ce4710c9bacf923d7e449ba7196f5cf8 +size 60967 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewdark.w420dp_h1100dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewdark.w420dp_h1100dp.png deleted file mode 100644 index 2e998b7..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewdark.w420dp_h1100dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7eadfb8c1808a6f44b08f66a3fa1f7d48e67c6307e5aa517ccedda0d75e0f92b -size 53385 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewlight.w420dp_h1020dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewlight.w420dp_h1020dp.png new file mode 100644 index 0000000..2ad9b0d --- /dev/null +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewlight.w420dp_h1020dp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20ccd502b1d0ebcede77d1ebe58bba858d6efc94f6d45e16e828d8f8ebd603df +size 59868 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewlight.w420dp_h1100dp.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewlight.w420dp_h1100dp.png deleted file mode 100644 index 0dc7405..0000000 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_preferences.preferencescontentkt.preferencescontentpreviewlight.w420dp_h1100dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:159cad8d5f2cde2ecfb3c53b26efa671ec7bcb890f9c3c079f7b841e0c7591c2 -size 52701 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklybuttonkt.blinklybuttonpreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklybuttonkt.blinklybuttonpreviewdark.png index 60dd422..178f9cd 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklybuttonkt.blinklybuttonpreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklybuttonkt.blinklybuttonpreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f87f19d4e33d1a45a8562da6579e84a1634536c4467030cd5cf57b804134d29c -size 11878 +oid sha256:f1f0548aeb5d04c82a0acfe6e98b20416826f46956b843214e3e4fe85f8e8d94 +size 12293 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklybuttonkt.blinklybuttonpreviewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklybuttonkt.blinklybuttonpreviewlight.png index e22ef8a..7c3589d 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklybuttonkt.blinklybuttonpreviewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklybuttonkt.blinklybuttonpreviewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:deff0baaf69ce281923457e4c73bf62ff2795c34de692425a27635af39449211 -size 11704 +oid sha256:c1d2d1036bfe888c18bc2f527cfb07ef3ad169eda6381ff4be34edbaed3b73ba +size 12123 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyeclosedpreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyeclosedpreviewdark.png index 42768fd..54616a7 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyeclosedpreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyeclosedpreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:910f134555e231926ee3aa83cc602b4776ecbfc9c8e263a2da83303a893beea9 -size 6377 +oid sha256:d151be3dcfff9c2e98b9a0fb289f2e1d9a859d524ee90cbef56ed565320e9171 +size 6357 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyefarfocuspreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyefarfocuspreviewdark.png index 877a5fd..c37155c 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyefarfocuspreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyefarfocuspreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37783b5d1f58ef75f95e73ae61a59458c0b0ecb2a98b1b1ca6d82552e34e0d1e -size 17055 +oid sha256:f52812cd0a5a74e3966fd86a0851284a754cef0e59343325e2c5013119ea1f33 +size 17725 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyefocuspreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyefocuspreviewdark.png index 9382ad4..38866e5 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyefocuspreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyefocuspreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8f0fb13079bf725a39724fc3fe6c9941ce37ac168c3ec042d1b7eddcb791f11 -size 18396 +oid sha256:a8756dbd1d67b65b6002a6b24943754f0e01efaf2ef655ee3c7a98b60047ddb4 +size 18960 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyeopenpreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyeopenpreviewdark.png index d996655..a535553 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyeopenpreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyeyepanelkt.blinklyeyeopenpreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f91284624f17f3403d35ffce3e7471631d18fce7a8b4b5f89935cc6df44991b0 -size 15073 +oid sha256:d7f8b8c761d5c118e12736d1a130fc4e493990762d1b90f0fb343b47cbb3617f +size 15700 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyiconbuttonkt.blinklyiconbuttonpreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyiconbuttonkt.blinklyiconbuttonpreviewdark.png index 05b5861..d585c65 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyiconbuttonkt.blinklyiconbuttonpreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyiconbuttonkt.blinklyiconbuttonpreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a8c64b29cc435730bc2f20ce09dec22e31ac97173d9ba7cb3966363e3512d91 -size 16299 +oid sha256:7261cc6dade9a8461509ce16cacd1b10e2ef60e48f45ae9a4bcbcc0abd37539d +size 16960 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyiconbuttonkt.blinklyiconbuttonpreviewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyiconbuttonkt.blinklyiconbuttonpreviewlight.png index 024ac31..a1d2f9a 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyiconbuttonkt.blinklyiconbuttonpreviewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyiconbuttonkt.blinklyiconbuttonpreviewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:063e30fa2ad47af2880d01898737311b49a2d0cbcbebd0cbabb285e9dc50b904 -size 16128 +oid sha256:70eec8b9097daa6c154a26ae909cac32570af2b5b54c07efe7a514ec798d2024 +size 16518 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyoutlinebuttonkt.blinklyoutlinebuttonpreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyoutlinebuttonkt.blinklyoutlinebuttonpreviewdark.png index 77bd8b3..dac31af 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyoutlinebuttonkt.blinklyoutlinebuttonpreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyoutlinebuttonkt.blinklyoutlinebuttonpreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfdb81de54e56524f71e538dd85a571f1be476f52cb7a5c168fe6edeab8ecd9e -size 15213 +oid sha256:dd0bf45211c53037c515171308a7303052c64a232bf0b347c7f5a3513129bcf4 +size 15846 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyoutlinebuttonkt.blinklyoutlinebuttonpreviewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyoutlinebuttonkt.blinklyoutlinebuttonpreviewlight.png index e24c72e..e8ac012 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyoutlinebuttonkt.blinklyoutlinebuttonpreviewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklyoutlinebuttonkt.blinklyoutlinebuttonpreviewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19ed96bf867b2ec512e13b7210f40a0bd499e85c04eb93d829391d83115b448c -size 14483 +oid sha256:60a11a4e4dd82ba5a68e2e6e1c7cd48ccb43334beaa5f8e9cdae205c1cda8865 +size 15322 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklytimepickerdialogkt.blinklytimepickerdialogpreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklytimepickerdialogkt.blinklytimepickerdialogpreviewdark.png index 405c493..32ca9f6 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklytimepickerdialogkt.blinklytimepickerdialogpreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklytimepickerdialogkt.blinklytimepickerdialogpreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d684b90e2ed50adfcae0695619bf21218006d4d6200fcf24c3441409c5102e6e -size 21020 +oid sha256:a590bc10b67e96c07b41880f7bcb5e7e23be7d7ab3b80c68fae4479278f79593 +size 21555 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklytimepickerdialogkt.blinklytimepickerdialogpreviewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklytimepickerdialogkt.blinklytimepickerdialogpreviewlight.png index 71bed7e..709818e 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklytimepickerdialogkt.blinklytimepickerdialogpreviewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.blinklytimepickerdialogkt.blinklytimepickerdialogpreviewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e402dedb8403fc304db694f1d851dbc465b6ac87dccedae7bfd3706cf5732e41 -size 21272 +oid sha256:3bee950ae19c8ac5540851d5bca66a67d8855623e023256ba4c8aa6137e5ab8c +size 21809 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.weekdaytogglekt.weekdaytogglepreviewdark.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.weekdaytogglekt.weekdaytogglepreviewdark.png index fa636b8..1b39e60 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.weekdaytogglekt.weekdaytogglepreviewdark.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.weekdaytogglekt.weekdaytogglepreviewdark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77980779933f3daed7153be07bcdb90a305d1dc427697438ad51f2345ef7e884 -size 6564 +oid sha256:69ce5b219f453264bf5391421a64728ef74ec209f439bd45a65fb32b1cd911bf +size 6874 diff --git a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.weekdaytogglekt.weekdaytogglepreviewlight.png b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.weekdaytogglekt.weekdaytogglepreviewlight.png index 974148f..7e478f8 100644 --- a/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.weekdaytogglekt.weekdaytogglepreviewlight.png +++ b/shared/compose/src/test/snapshots/images/Paparazzi_Preview_Test_widget.weekdaytogglekt.weekdaytogglepreviewlight.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78a821d0e61ec772ea7f46634f142315a9a9cf29c085607cc51dd3b528d1465e -size 6641 +oid sha256:580d487ebb948705e26623fe2027451f2fddaaa751f9c88a091737cc4409d3c4 +size 6959 diff --git a/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/impl/BlinklyExerciseManagerImpl.kt b/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/impl/BlinklyExerciseManagerImpl.kt index c9145e8..1ff1b6a 100644 --- a/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/impl/BlinklyExerciseManagerImpl.kt +++ b/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/impl/BlinklyExerciseManagerImpl.kt @@ -13,6 +13,7 @@ import com.sedsoftware.blinkly.domain.external.BlinklyDatabase import com.sedsoftware.blinkly.domain.external.BlinklyDispatchers import com.sedsoftware.blinkly.domain.external.BlinklySettings import com.sedsoftware.blinkly.domain.external.BlinklyTimeUtils +import com.sedsoftware.blinkly.domain.model.BlinklyError import com.sedsoftware.blinkly.domain.model.Exercise import com.sedsoftware.blinkly.domain.model.ExerciseBlock import com.sedsoftware.blinkly.domain.model.ExerciseEvent @@ -112,17 +113,30 @@ internal class BlinklyExerciseManagerImpl( } CompleteExerciseNode -> { - database.saveExercise(Exercise(block, type, timeUtils.now())) + saveExercise(block, type) _events.emit(ExerciseEvent.ExerciseCompleted(block, type)) } CompleteBlockNode -> { - database.saveExercise(Exercise(block, type, timeUtils.now())) + saveExercise(block, type) _events.emit(ExerciseEvent.BlockCompleted(block)) } } } + @Suppress("TooGenericExceptionCaught") + private suspend fun saveExercise( + block: ExerciseBlock, + type: ExerciseType, + ) { + try { + database.saveExercise(Exercise(block, type, timeUtils.now())) + } catch (throwable: Throwable) { + _events.emit(ExerciseEvent.Error(block, type, BlinklyError.WorkoutSaving(throwable))) + throw throwable + } + } + private fun blockExercises(block: ExerciseBlock) = when (block) { ExerciseBlock.A -> listOf( diff --git a/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/BlinklyError.kt b/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/BlinklyError.kt new file mode 100644 index 0000000..9f78c9e --- /dev/null +++ b/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/BlinklyError.kt @@ -0,0 +1,30 @@ +package com.sedsoftware.blinkly.domain.model + +sealed class BlinklyError(message: String, cause: Throwable? = null) : Exception(message, cause) { + + class MainDataLoading(cause: Throwable) : BlinklyError("Unable to load main screen data", cause) + class ProgressDataLoading(cause: Throwable) : BlinklyError("Unable to load progress data", cause) + class TrainingsDataLoading(cause: Throwable) : BlinklyError("Unable to load trainings data", cause) + class PreferencesLoading(cause: Throwable) : BlinklyError("Unable to load preferences", cause) + class PreferencesSaving(cause: Throwable) : BlinklyError("Unable to save preferences", cause) + class AchievementsLoading(cause: Throwable) : BlinklyError("Unable to load achievements", cause) + class GardenLoading(cause: Throwable) : BlinklyError("Unable to load garden", cause) + class RemindersLoading(cause: Throwable) : BlinklyError("Unable to load reminders", cause) + class ReminderDeleting(cause: Throwable) : BlinklyError("Unable to delete reminder", cause) + class ReminderRestoring(cause: Throwable) : BlinklyError("Unable to restore reminder", cause) + class ReminderCreating(cause: Throwable) : BlinklyError("Unable to create reminder", cause) + class InitialRemindersLoading(cause: Throwable) : BlinklyError("Unable to load initial reminders", cause) + class InitialRemindersCreating(cause: Throwable) : BlinklyError("Unable to create initial reminders", cause) + class InitialRemindersClearing(cause: Throwable) : BlinklyError("Unable to clear initial reminders", cause) + class NotificationPermissionChecking(cause: Throwable) : BlinklyError("Unable to check notification permission", cause) + class NotificationPermissionRequesting(cause: Throwable) : BlinklyError("Unable to request notification permission", cause) + class WorkoutDataLoading(cause: Throwable) : BlinklyError("Unable to update workout data", cause) + class WorkoutSaving(cause: Throwable) : BlinklyError("Unable to save workout", cause) + class Unknown(cause: Throwable) : BlinklyError("Unknown error", cause) +} + +fun Throwable.asBlinklyError(mapper: (Throwable) -> BlinklyError): BlinklyError = + when (this) { + is BlinklyError -> this + else -> mapper(this) + } diff --git a/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/ExerciseEvent.kt b/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/ExerciseEvent.kt index 1fa182a..3d2a865 100644 --- a/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/ExerciseEvent.kt +++ b/shared/domain/src/commonMain/kotlin/com/sedsoftware/blinkly/domain/model/ExerciseEvent.kt @@ -36,4 +36,10 @@ sealed interface ExerciseEvent { override val block: ExerciseBlock, override val exercise: ExerciseType? = null, ) : ExerciseEvent + + data class Error( + override val block: ExerciseBlock, + override val exercise: ExerciseType?, + val throwable: Throwable, + ) : ExerciseEvent }