From 3629660414d8c87d906d93565849cf3da5cf2cd6 Mon Sep 17 00:00:00 2001 From: "babichev.a" Date: Wed, 23 Oct 2024 23:19:47 +0400 Subject: [PATCH] type-safe navigation --- android-compose-app/build.gradle.kts | 1 + .../notedelight/ui/DesktopUiTests.kt | 2 +- gradle/libs.versions.toml | 11 +- iosApp/Podfile.lock | 2 +- iosApp/Pods/Manifest.lock | 2 +- iosApp/Pods/Pods.xcodeproj/project.pbxproj | 194 +++++++++--------- .../Pods-iosApp/Pods-iosApp-frameworks.sh | 2 +- iosApp/iosApp/Info.plist | 2 + .../softartdev/notedelight/UiThreadRouter.kt | 6 +- shared-compose-ui/build.gradle.kts | 1 - .../kotlin/com/softartdev/notedelight/App.kt | 49 ++--- .../notedelight/navigation/RouterImpl.kt | 6 +- shared/build.gradle.kts | 2 + .../presentation/main/MainViewModelTest.kt | 6 +- .../presentation/note/NoteViewModelTest.kt | 4 +- .../settings/SettingsViewModelTest.kt | 10 +- .../signin/SignInViewModelTest.kt | 4 +- .../splash/SplashViewModelTest.kt | 6 +- .../shared/navigation/AppNavGraph.kt | 77 ++++--- .../notedelight/shared/navigation/Router.kt | 6 +- .../shared/presentation/main/MainViewModel.kt | 6 +- .../shared/presentation/note/NoteViewModel.kt | 10 +- .../settings/SettingsViewModel.kt | 18 +- .../presentation/signin/SignInViewModel.kt | 4 +- .../presentation/splash/SplashViewModel.kt | 6 +- .../shared/navigation/RouterStub.kt | 6 +- 26 files changed, 226 insertions(+), 217 deletions(-) diff --git a/android-compose-app/build.gradle.kts b/android-compose-app/build.gradle.kts index a29d8ef9..1a34c3f3 100644 --- a/android-compose-app/build.gradle.kts +++ b/android-compose-app/build.gradle.kts @@ -95,6 +95,7 @@ dependencies { androidTestImplementation(compose.desktop.uiTestJUnit4) androidTestImplementation(libs.turbine) androidTestImplementation(libs.leakCanary.android.instrumentation) + lintChecks(libs.android.security.lint) } tasks.withType{ dependsOn("processDebugGoogleServices") diff --git a/desktop-compose-app/src/jvmTest/kotlin/com/softartdev/notedelight/ui/DesktopUiTests.kt b/desktop-compose-app/src/jvmTest/kotlin/com/softartdev/notedelight/ui/DesktopUiTests.kt index a770af25..ef204412 100644 --- a/desktop-compose-app/src/jvmTest/kotlin/com/softartdev/notedelight/ui/DesktopUiTests.kt +++ b/desktop-compose-app/src/jvmTest/kotlin/com/softartdev/notedelight/ui/DesktopUiTests.kt @@ -5,13 +5,13 @@ package com.softartdev.notedelight.ui import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.performClick +import androidx.lifecycle.compose.LocalLifecycleOwner import com.softartdev.notedelight.App import com.softartdev.notedelight.TestLifecycleOwner import com.softartdev.notedelight.di.uiTestModules diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a6f16f2..d57bd2db 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ kotlin = "2.0.21" agp = "8.7.1" gms = "4.4.2" crashlytics = "3.0.2" -compose = "1.6.11" +compose = "1.7.0" coroutines = "1.8.1" sqlDelight = "2.0.2" androidxSqlite = "2.4.0" @@ -15,11 +15,12 @@ saferoom = "1.3.0" androidSqlCipher = "4.5.4" iosSqlCipher = "4.5.4" koin = "3.5.6" +kotlinx-serialization = "1.7.3" kotlinx-datetime = "0.6.0" napier = "2.7.1" materialThemePrefs = "0.9.0" androidxViewModel = "2.8.3" -androidxNavigation = "2.7.0-alpha07" +androidxNavigation = "2.8.0-alpha10" androidxActivityCompose = "1.9.3" androidxComposeTest = "1.7.4" androidxCoreSplashscreen = "1.0.1" @@ -29,13 +30,14 @@ androidxArch = "2.2.0" androidxTestExt = "1.2.1" androidxTest = "1.6.2" androidxTestOrchestrator = "1.5.1" -firebase = "33.4.0" +firebase = "33.5.0" leakCanary = "2.14" junit = "4.13.2" mockito = "5.14.1" turbine = "1.1.0" espresso = "3.6.1" desugar = "2.1.2" +androidSecurityLint = "1.0.3" appdirs = "1.2.2" [libraries] @@ -65,6 +67,7 @@ koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", versi koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } koin-test-junit4 = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } napier = { module = "io.github.aakira:napier", version.ref = "napier" } @@ -118,6 +121,8 @@ espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-re desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar" } +android-security-lint = { module = "com.android.security.lint:lint", version.ref = "androidSecurityLint" } + [plugins] gradle-convention = { id = "com.softartdev.notedelight.buildlogic.convention", version = "unspecified" } diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index bf773ca3..641ccb94 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -24,4 +24,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 0dc93a6f6109335ea8cd3f91d2c87cc8c99f04a3 -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/iosApp/Pods/Manifest.lock b/iosApp/Pods/Manifest.lock index bf773ca3..641ccb94 100644 --- a/iosApp/Pods/Manifest.lock +++ b/iosApp/Pods/Manifest.lock @@ -24,4 +24,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 0dc93a6f6109335ea8cd3f91d2c87cc8c99f04a3 -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/iosApp/Pods/Pods.xcodeproj/project.pbxproj b/iosApp/Pods/Pods.xcodeproj/project.pbxproj index dc78df1d..816688e6 100644 --- a/iosApp/Pods/Pods.xcodeproj/project.pbxproj +++ b/iosApp/Pods/Pods.xcodeproj/project.pbxproj @@ -14,40 +14,40 @@ 0E4FE4DB27D59D3921AB6E40F1563B7A /* [CP-User] Build iosComposePod */, ); dependencies = ( - B83D4F885DD70CA410CBE4D9F5616736 /* PBXTargetDependency */, + 2EB6C3B1DC3EDE9F23C4C79666EF4688 /* PBXTargetDependency */, ); name = iosComposePod; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 0BF0AC9ACD0BA0A371B14221FC53817E /* Pods-iosApp-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F87A626222A8D9B26448A9FEB04FD0DF /* Pods-iosApp-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0BF0AC9ACD0BA0A371B14221FC53817E /* Pods-iosApp-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 015E0D7EA7331961AB63E5AFECA86BB5 /* Pods-iosApp-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3BC2DA8B911C40FAD4FF25C229B94B23 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 952925E838A0DE4D7BDC4EE54022633D /* Foundation.framework */; }; 3C486A28B2618549A56FE9CB93610820 /* SQLCipher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 13A2872E600E9A746D096C16F2EBCBCA /* SQLCipher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 481730739E8F23EC45A2D64B2927AA71 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3B2A0B779CE31153B2C0D8A7F8DC7FE /* Security.framework */; }; 62AA43FCE46E6F7922E526782308A46E /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = FC29DBE0409C118D71AA46627E3967DF /* sqlite3.c */; settings = {COMPILER_FLAGS = "-DNDEBUG -DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLITE_SOUNDEX -DSQLITE_THREADSAFE -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_STAT3 -DSQLITE_ENABLE_STAT4 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_LOAD_EXTENSION -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS4_UNICODE61 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLCIPHER_CRYPTO_CC -DHAVE_USLEEP=1 -DSQLITE_MAX_VARIABLE_NUMBER=99999 -fno-objc-arc"; }; }; - 63A5F98B5DA17C01C8E5205A43C75005 /* Pods-iosApp-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 54DC7F9924F99FEE67A9AD3BEE09A10C /* Pods-iosApp-dummy.m */; }; + 63A5F98B5DA17C01C8E5205A43C75005 /* Pods-iosApp-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = E6DB2A5F5DADA1DDE45F36B1A2D6AC16 /* Pods-iosApp-dummy.m */; }; BA2A98B300F6A2F438A5439822646CB7 /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = E2ACA340E697922AEC7D81820FD63046 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; CF4DB579139E8F533154A4599B63C213 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 952925E838A0DE4D7BDC4EE54022633D /* Foundation.framework */; }; E827C96F61C81146EA5A05B7E69CA78A /* SQLCipher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4769AA64068661236B3D136F70EEB862 /* SQLCipher-dummy.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 2B82EA63A716387213906CA518AB4956 /* PBXContainerItemProxy */ = { + 7627C37550D2B50AACDA7DB0AC64F6E8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = D21962D0DE148A440FADB55935BD4264; - remoteInfo = SQLCipher; + remoteGlobalIDString = 8CD5AC086628776F77663F3736B36F45; + remoteInfo = iosComposePod; }; - CA1E7F5225CA23634E533E03E7E407EA /* PBXContainerItemProxy */ = { + AE467846ED8A5338064443CE30739F0A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = 8CD5AC086628776F77663F3736B36F45; - remoteInfo = iosComposePod; + remoteGlobalIDString = D21962D0DE148A440FADB55935BD4264; + remoteInfo = SQLCipher; }; - EC63FDCA68DEC87EB5A1B097C5A4A3D6 /* PBXContainerItemProxy */ = { + BBA589BE5E7234AAA4ECA0830566B100 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; @@ -57,32 +57,34 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 0187F135D5F61750CD24C3E04B6E8B64 /* Pods-iosApp-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-iosApp-frameworks.sh"; sourceTree = ""; }; - 0873377E3857ADC44E919597C03BFA43 /* iosComposeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = iosComposeKit.framework; path = build/cocoapods/framework/iosComposeKit.framework; sourceTree = ""; }; + 015E0D7EA7331961AB63E5AFECA86BB5 /* Pods-iosApp-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-iosApp-umbrella.h"; sourceTree = ""; }; + 056CC6A06008B475FFB4DB8426C39819 /* iosComposePod.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = iosComposePod.debug.xcconfig; sourceTree = ""; }; + 11C9B998CE0869936AE6BE69270DAAC9 /* Pods-iosApp.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-iosApp.modulemap"; sourceTree = ""; }; 13A2872E600E9A746D096C16F2EBCBCA /* SQLCipher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SQLCipher-umbrella.h"; sourceTree = ""; }; 1FD5F5B0E4809A3AE2ECDE142F44A6BB /* SQLCipher */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SQLCipher; path = SQLCipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 244FC2DAA936A0B07ACEFDC74E2D54B8 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.release.xcconfig"; sourceTree = ""; }; 3ECD5041F27CC497ED97A9B292B3F0BC /* SQLCipher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SQLCipher-prefix.pch"; sourceTree = ""; }; - 3F64BADB16C311E9D09DAD0116F684F4 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; 3FF32DBA096086AD31F16EC316FF7140 /* SQLCipher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SQLCipher.modulemap; sourceTree = ""; }; + 43907F8241745A4AA5B48578B4BDFDFB /* compose-resources */ = {isa = PBXFileReference; includeInIndex = 1; name = "compose-resources"; path = "build/compose/cocoapods/compose-resources"; sourceTree = ""; }; + 458C71A8882D0088D940108A6FF236CD /* iosComposePod.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = iosComposePod.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 4769AA64068661236B3D136F70EEB862 /* SQLCipher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SQLCipher-dummy.m"; sourceTree = ""; }; - 54DC7F9924F99FEE67A9AD3BEE09A10C /* Pods-iosApp-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-iosApp-dummy.m"; sourceTree = ""; }; - 5AC974169B09D4545F741E9A2147610E /* Pods-iosApp-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-iosApp-Info.plist"; sourceTree = ""; }; - 83AEBFFCA34953AFE8F7D4C0EE78427A /* iosComposePod.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = iosComposePod.debug.xcconfig; sourceTree = ""; }; - 878F5B1E91E548FCF1BC50A0BB157712 /* iosComposePod.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = iosComposePod.release.xcconfig; sourceTree = ""; }; - 8B524AABE529FB8A2D151B05321336E0 /* Pods-iosApp.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-iosApp.modulemap"; sourceTree = ""; }; + 55ABB06C8A1800962A74E007E7733796 /* Pods-iosApp-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-iosApp-frameworks.sh"; sourceTree = ""; }; + 6F3B5FED07117E377CFAC3D49B490F17 /* Pods-iosApp-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-iosApp-resources.sh"; sourceTree = ""; }; + 76A263267985B0185D85E24D40FCEE9A /* Pods-iosApp-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-iosApp-Info.plist"; sourceTree = ""; }; + 76D28488EA8CF5C697DFF07967A9960E /* Pods-iosApp-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-iosApp-acknowledgements.plist"; sourceTree = ""; }; 952925E838A0DE4D7BDC4EE54022633D /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - 9BB0B8B698D4C35D4F9A07EF70910170 /* Pods-iosApp-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-iosApp-acknowledgements.plist"; sourceTree = ""; }; 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 9F1FFC8B10F4D7D4F37606E8959B3F29 /* Pods-iosApp-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-iosApp-acknowledgements.markdown"; sourceTree = ""; }; - 9FECCB5AFF87D121F15BA01E683C7932 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.release.xcconfig"; sourceTree = ""; }; AF73D48D694B262EA8800D1CA5A7A175 /* SQLCipher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SQLCipher-Info.plist"; sourceTree = ""; }; B097DD7534E741D5C41838011D755842 /* Pods-iosApp */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-iosApp"; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C3B2A0B779CE31153B2C0D8A7F8DC7FE /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; - CEEC2E1232AEB6ACA3AC36F9342EFBDD /* iosComposePod.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = iosComposePod.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + C72E850B172798319EF8ADB399785CF4 /* iosComposeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = iosComposeKit.framework; path = build/cocoapods/framework/iosComposeKit.framework; sourceTree = ""; }; E2ACA340E697922AEC7D81820FD63046 /* sqlite3.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = sqlite3.h; sourceTree = ""; }; + E462E23B3674BF94EAB1504D506F2803 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; + E4C923318724794E3CC670804C2D6A6B /* Pods-iosApp-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-iosApp-acknowledgements.markdown"; sourceTree = ""; }; + E6DB2A5F5DADA1DDE45F36B1A2D6AC16 /* Pods-iosApp-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-iosApp-dummy.m"; sourceTree = ""; }; EFE434E808F4ACDAD53CAD2893B9A8DE /* SQLCipher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SQLCipher.release.xcconfig; sourceTree = ""; }; F1DBEF6B264AD5F07660CCEDCB9E0F82 /* SQLCipher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SQLCipher.debug.xcconfig; sourceTree = ""; }; - F87A626222A8D9B26448A9FEB04FD0DF /* Pods-iosApp-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-iosApp-umbrella.h"; sourceTree = ""; }; + F3A43B865572C68F39872D2F7194AC56 /* iosComposePod.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = iosComposePod.release.xcconfig; sourceTree = ""; }; FC29DBE0409C118D71AA46627E3967DF /* sqlite3.c */ = {isa = PBXFileReference; includeInIndex = 1; path = sqlite3.c; sourceTree = ""; }; /* End PBXFileReference section */ @@ -107,22 +109,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0C6E05A4FEDEE0D095764C93447C807A /* Frameworks */ = { - isa = PBXGroup; - children = ( - 0873377E3857ADC44E919597C03BFA43 /* iosComposeKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 11C970DEAE48C6D0282DFE54684F53F1 /* Targets Support Files */ = { - isa = PBXGroup; - children = ( - 52A898E555C2DBD7D25A7E44754C3FC6 /* Pods-iosApp */, - ); - name = "Targets Support Files"; - sourceTree = ""; - }; 1628BF05B4CAFDCC3549A101F5A10A17 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -131,12 +117,12 @@ name = Frameworks; sourceTree = ""; }; - 2863FB413E6D4B726B6D4BE69D99BE6A /* Pod */ = { + 1A3D275F3BC4A7810FB3728EEF569A25 /* Frameworks */ = { isa = PBXGroup; children = ( - CEEC2E1232AEB6ACA3AC36F9342EFBDD /* iosComposePod.podspec */, + C72E850B172798319EF8ADB399785CF4 /* iosComposeKit.framework */, ); - name = Pod; + name = Frameworks; sourceTree = ""; }; 4F56363B58525BCB3F7D2228EF3BFCA6 /* Support Files */ = { @@ -154,21 +140,16 @@ path = "../Target Support Files/SQLCipher"; sourceTree = ""; }; - 52A898E555C2DBD7D25A7E44754C3FC6 /* Pods-iosApp */ = { + 55BF10C492F0787F6F31FDE71C5109DF /* iosComposePod */ = { isa = PBXGroup; children = ( - 8B524AABE529FB8A2D151B05321336E0 /* Pods-iosApp.modulemap */, - 9F1FFC8B10F4D7D4F37606E8959B3F29 /* Pods-iosApp-acknowledgements.markdown */, - 9BB0B8B698D4C35D4F9A07EF70910170 /* Pods-iosApp-acknowledgements.plist */, - 54DC7F9924F99FEE67A9AD3BEE09A10C /* Pods-iosApp-dummy.m */, - 0187F135D5F61750CD24C3E04B6E8B64 /* Pods-iosApp-frameworks.sh */, - 5AC974169B09D4545F741E9A2147610E /* Pods-iosApp-Info.plist */, - F87A626222A8D9B26448A9FEB04FD0DF /* Pods-iosApp-umbrella.h */, - 3F64BADB16C311E9D09DAD0116F684F4 /* Pods-iosApp.debug.xcconfig */, - 9FECCB5AFF87D121F15BA01E683C7932 /* Pods-iosApp.release.xcconfig */, + 43907F8241745A4AA5B48578B4BDFDFB /* compose-resources */, + 1A3D275F3BC4A7810FB3728EEF569A25 /* Frameworks */, + ABD794A40111A749E5B96FD96B354F3F /* Pod */, + A5449CB1139726204A039B3607B5400F /* Support Files */, ); - name = "Pods-iosApp"; - path = "Target Support Files/Pods-iosApp"; + name = iosComposePod; + path = "../../ios-compose-kit"; sourceTree = ""; }; 5818F946CC9DCBBEFC2CB7DE0E037777 /* Products */ = { @@ -180,64 +161,87 @@ name = Products; sourceTree = ""; }; - 665F49DF961ED93D1ACE0D16BA2DE7A0 /* Support Files */ = { + 6746CCF30CAE34654B19DDA6B383D05D /* Pods */ = { isa = PBXGroup; children = ( - 83AEBFFCA34953AFE8F7D4C0EE78427A /* iosComposePod.debug.xcconfig */, - 878F5B1E91E548FCF1BC50A0BB157712 /* iosComposePod.release.xcconfig */, + E74569A9C027A82A021AAB6F29D8BE3B /* SQLCipher */, ); - name = "Support Files"; - path = "../iosApp/Pods/Target Support Files/iosComposePod"; + name = Pods; sourceTree = ""; }; - 6746CCF30CAE34654B19DDA6B383D05D /* Pods */ = { + 7CE426AC267333500FABE7BA0A5A42A0 /* iOS */ = { isa = PBXGroup; children = ( - E74569A9C027A82A021AAB6F29D8BE3B /* SQLCipher */, + 952925E838A0DE4D7BDC4EE54022633D /* Foundation.framework */, + C3B2A0B779CE31153B2C0D8A7F8DC7FE /* Security.framework */, ); - name = Pods; + name = iOS; sourceTree = ""; }; - 74312BEF1C762D20E36E394E8AAF8D9C /* Development Pods */ = { + A5449CB1139726204A039B3607B5400F /* Support Files */ = { isa = PBXGroup; children = ( - 787250B755F418B1E823F0A4C48F32FA /* iosComposePod */, + 056CC6A06008B475FFB4DB8426C39819 /* iosComposePod.debug.xcconfig */, + F3A43B865572C68F39872D2F7194AC56 /* iosComposePod.release.xcconfig */, ); - name = "Development Pods"; + name = "Support Files"; + path = "../iosApp/Pods/Target Support Files/iosComposePod"; sourceTree = ""; }; - 787250B755F418B1E823F0A4C48F32FA /* iosComposePod */ = { + ABD794A40111A749E5B96FD96B354F3F /* Pod */ = { isa = PBXGroup; children = ( - 0C6E05A4FEDEE0D095764C93447C807A /* Frameworks */, - 2863FB413E6D4B726B6D4BE69D99BE6A /* Pod */, - 665F49DF961ED93D1ACE0D16BA2DE7A0 /* Support Files */, + 458C71A8882D0088D940108A6FF236CD /* iosComposePod.podspec */, ); - name = iosComposePod; - path = "../../ios-compose-kit"; + name = Pod; sourceTree = ""; }; - 7CE426AC267333500FABE7BA0A5A42A0 /* iOS */ = { + BA6B7BC2729F657E9D3682E55CA6E980 /* Pods-iosApp */ = { isa = PBXGroup; children = ( - 952925E838A0DE4D7BDC4EE54022633D /* Foundation.framework */, - C3B2A0B779CE31153B2C0D8A7F8DC7FE /* Security.framework */, + 11C9B998CE0869936AE6BE69270DAAC9 /* Pods-iosApp.modulemap */, + E4C923318724794E3CC670804C2D6A6B /* Pods-iosApp-acknowledgements.markdown */, + 76D28488EA8CF5C697DFF07967A9960E /* Pods-iosApp-acknowledgements.plist */, + E6DB2A5F5DADA1DDE45F36B1A2D6AC16 /* Pods-iosApp-dummy.m */, + 55ABB06C8A1800962A74E007E7733796 /* Pods-iosApp-frameworks.sh */, + 76A263267985B0185D85E24D40FCEE9A /* Pods-iosApp-Info.plist */, + 6F3B5FED07117E377CFAC3D49B490F17 /* Pods-iosApp-resources.sh */, + 015E0D7EA7331961AB63E5AFECA86BB5 /* Pods-iosApp-umbrella.h */, + E462E23B3674BF94EAB1504D506F2803 /* Pods-iosApp.debug.xcconfig */, + 244FC2DAA936A0B07ACEFDC74E2D54B8 /* Pods-iosApp.release.xcconfig */, ); - name = iOS; + name = "Pods-iosApp"; + path = "Target Support Files/Pods-iosApp"; + sourceTree = ""; + }; + BEA9DB10AD5229A07D3458D6147B3990 /* Development Pods */ = { + isa = PBXGroup; + children = ( + 55BF10C492F0787F6F31FDE71C5109DF /* iosComposePod */, + ); + name = "Development Pods"; sourceTree = ""; }; CF1408CF629C7361332E53B88F7BD30C = { isa = PBXGroup; children = ( 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, - 74312BEF1C762D20E36E394E8AAF8D9C /* Development Pods */, + BEA9DB10AD5229A07D3458D6147B3990 /* Development Pods */, 1628BF05B4CAFDCC3549A101F5A10A17 /* Frameworks */, 6746CCF30CAE34654B19DDA6B383D05D /* Pods */, 5818F946CC9DCBBEFC2CB7DE0E037777 /* Products */, - 11C970DEAE48C6D0282DFE54684F53F1 /* Targets Support Files */, + D456857FB6E5BC3266BEC21401D60DB5 /* Targets Support Files */, ); sourceTree = ""; }; + D456857FB6E5BC3266BEC21401D60DB5 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + BA6B7BC2729F657E9D3682E55CA6E980 /* Pods-iosApp */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; E74569A9C027A82A021AAB6F29D8BE3B /* SQLCipher */ = { isa = PBXGroup; children = ( @@ -310,8 +314,8 @@ buildRules = ( ); dependencies = ( - 0D4C4E291864EBC518E3B0902D2F7CD6 /* PBXTargetDependency */, - 5235EBFBF7382849292F34FF910D3A0C /* PBXTargetDependency */, + 57BF9787BB098C6F14E266342C04EE27 /* PBXTargetDependency */, + 6112B3F4F8AFA28E725A3EB18B299B06 /* PBXTargetDependency */, ); name = "Pods-iosApp"; productName = Pods_iosApp; @@ -324,8 +328,8 @@ BFDFE7DC352907FC980B868725387E98 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1300; - LastUpgradeCheck = 1300; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; }; buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; compatibilityVersion = "Xcode 12.0"; @@ -398,30 +402,30 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 0D4C4E291864EBC518E3B0902D2F7CD6 /* PBXTargetDependency */ = { + 2EB6C3B1DC3EDE9F23C4C79666EF4688 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SQLCipher; target = D21962D0DE148A440FADB55935BD4264 /* SQLCipher */; - targetProxy = 2B82EA63A716387213906CA518AB4956 /* PBXContainerItemProxy */; + targetProxy = AE467846ED8A5338064443CE30739F0A /* PBXContainerItemProxy */; }; - 5235EBFBF7382849292F34FF910D3A0C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = iosComposePod; - target = 8CD5AC086628776F77663F3736B36F45 /* iosComposePod */; - targetProxy = CA1E7F5225CA23634E533E03E7E407EA /* PBXContainerItemProxy */; - }; - B83D4F885DD70CA410CBE4D9F5616736 /* PBXTargetDependency */ = { + 57BF9787BB098C6F14E266342C04EE27 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SQLCipher; target = D21962D0DE148A440FADB55935BD4264 /* SQLCipher */; - targetProxy = EC63FDCA68DEC87EB5A1B097C5A4A3D6 /* PBXContainerItemProxy */; + targetProxy = BBA589BE5E7234AAA4ECA0830566B100 /* PBXContainerItemProxy */; + }; + 6112B3F4F8AFA28E725A3EB18B299B06 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = iosComposePod; + target = 8CD5AC086628776F77663F3736B36F45 /* iosComposePod */; + targetProxy = 7627C37550D2B50AACDA7DB0AC64F6E8 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 052D17EECF4180EC4608BC2A6F212303 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 878F5B1E91E548FCF1BC50A0BB157712 /* iosComposePod.release.xcconfig */; + baseConfigurationReference = F3A43B865572C68F39872D2F7194AC56 /* iosComposePod.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -501,7 +505,7 @@ }; 670E43FB90517C126953C6A6DB2147B3 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 83AEBFFCA34953AFE8F7D4C0EE78427A /* iosComposePod.debug.xcconfig */; + baseConfigurationReference = 056CC6A06008B475FFB4DB8426C39819 /* iosComposePod.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -584,7 +588,7 @@ }; CC7C1D1A3B5075BD66875E59A070E404 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9FECCB5AFF87D121F15BA01E683C7932 /* Pods-iosApp.release.xcconfig */; + baseConfigurationReference = 244FC2DAA936A0B07ACEFDC74E2D54B8 /* Pods-iosApp.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; @@ -622,7 +626,7 @@ }; D2C52C24E863F310EA4C30647BF8BA35 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3F64BADB16C311E9D09DAD0116F684F4 /* Pods-iosApp.debug.xcconfig */; + baseConfigurationReference = E462E23B3674BF94EAB1504D506F2803 /* Pods-iosApp.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; diff --git a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh index 94102de6..79bf656a 100755 --- a/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh +++ b/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh @@ -18,7 +18,7 @@ echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" -SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" +SWIFT_STDLIB_PATH="${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" BCSYMBOLMAP_DIR="BCSymbolMaps" diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index af6afcdd..312a3601 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -51,5 +51,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + CADisableMinimumFrameDurationOnPhone + diff --git a/jvm-compose-test/src/main/kotlin/com/softartdev/notedelight/UiThreadRouter.kt b/jvm-compose-test/src/main/kotlin/com/softartdev/notedelight/UiThreadRouter.kt index 70dae606..7ddc691e 100644 --- a/jvm-compose-test/src/main/kotlin/com/softartdev/notedelight/UiThreadRouter.kt +++ b/jvm-compose-test/src/main/kotlin/com/softartdev/notedelight/UiThreadRouter.kt @@ -11,13 +11,13 @@ class UiThreadRouter(private val router: Router) : Router { override fun releaseController() = runOnUiThread { router.releaseController() } - override fun navigate(route: String) = runOnUiThread { router.navigate(route) } + override fun navigate(route: T) = runOnUiThread { router.navigate(route) } - override fun navigateClearingBackStack(route: String) = runOnUiThread { + override fun navigateClearingBackStack(route: T) = runOnUiThread { router.navigateClearingBackStack(route) } - override fun popBackStack(route: String, inclusive: Boolean, saveState: Boolean): Boolean = + override fun popBackStack(route: T, inclusive: Boolean, saveState: Boolean): Boolean = runOnUiThread { router.popBackStack(route, inclusive, saveState) } override fun popBackStack(): Boolean = runOnUiThread { router.popBackStack() } diff --git a/shared-compose-ui/build.gradle.kts b/shared-compose-ui/build.gradle.kts index 48758752..0dcec11e 100644 --- a/shared-compose-ui/build.gradle.kts +++ b/shared-compose-ui/build.gradle.kts @@ -8,7 +8,6 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.compose) alias(libs.plugins.compose.compiler) - alias(libs.plugins.kotlin.serialization) } compose.resources { publicResClass = true diff --git a/shared-compose-ui/src/commonMain/kotlin/com/softartdev/notedelight/App.kt b/shared-compose-ui/src/commonMain/kotlin/com/softartdev/notedelight/App.kt index 0d21ecc5..41555d13 100644 --- a/shared-compose-ui/src/commonMain/kotlin/com/softartdev/notedelight/App.kt +++ b/shared-compose-ui/src/commonMain/kotlin/com/softartdev/notedelight/App.kt @@ -4,12 +4,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController -import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.dialog import androidx.navigation.compose.rememberNavController -import androidx.navigation.navArgument +import androidx.navigation.toRoute import com.softartdev.notedelight.di.getViewModel import com.softartdev.notedelight.shared.navigation.AppNavGraph import com.softartdev.notedelight.shared.navigation.Router @@ -40,30 +39,27 @@ fun App( } NavHost( navController = navController, - startDestination = AppNavGraph.Splash.route, + startDestination = AppNavGraph.Splash, ) { - composable(route = AppNavGraph.Splash.route) { + composable { SplashScreen(splashViewModel = getViewModel()) } - composable(route = AppNavGraph.SignIn.route) { + composable { SignInScreen(signInViewModel = getViewModel()) } - composable(route = AppNavGraph.Main.route) { + composable { MainScreen(mainViewModel = getViewModel()) } - composable( - route = AppNavGraph.Details.route, - arguments = listOf(navArgument(name = AppNavGraph.ARG_NOTE_ID) { type = NavType.LongType }) - ) { backStackEntry: NavBackStackEntry -> + composable { backStackEntry: NavBackStackEntry -> NoteDetail( noteViewModel = getViewModel(), - noteId = AppNavGraph.ARG_NOTE_ID.let(backStackEntry.arguments!!::getLong), + noteId = backStackEntry.toRoute().noteId, ) } - composable(route = AppNavGraph.Settings.route) { + composable { SettingsScreen(settingsViewModel = getViewModel()) } - dialog(route = AppNavGraph.ThemeDialog.route) { + dialog { val preferenceHelper: PreferenceHelper = themePrefs.preferenceHelper ThemeDialog( darkThemeState = themePrefs.darkThemeState, @@ -71,39 +67,30 @@ fun App( dismissDialog = navController::navigateUp, ) } - dialog(route = AppNavGraph.SaveChangesDialog.route) { + dialog { SaveDialog(saveViewModel = getViewModel()) } - dialog( - route = AppNavGraph.EditTitleDialog.route, - arguments = listOf(navArgument(name = AppNavGraph.ARG_NOTE_ID) { type = NavType.LongType }) - ) { backStackEntry: NavBackStackEntry -> + dialog { backStackEntry: NavBackStackEntry -> EditTitleDialog( - noteId = AppNavGraph.ARG_NOTE_ID.let(backStackEntry.arguments!!::getLong), + noteId = backStackEntry.toRoute().noteId, editTitleViewModel = getViewModel() ) } - dialog(route = AppNavGraph.DeleteNoteDialog.route) { + dialog { DeleteDialog(deleteViewModel = getViewModel()) } - dialog(route = AppNavGraph.EnterPasswordDialog.route) { + dialog { EnterPasswordDialog(enterViewModel = getViewModel()) } - dialog(route = AppNavGraph.ConfirmPasswordDialog.route) { + dialog { ConfirmPasswordDialog(confirmViewModel = getViewModel()) } - dialog(route = AppNavGraph.ChangePasswordDialog.route) { + dialog { ChangePasswordDialog(changeViewModel = getViewModel()) } - dialog( - route = AppNavGraph.ErrorDialog.route, - arguments = listOf(navArgument(name = AppNavGraph.ARG_MESSAGE) { - type = NavType.StringType - nullable = true - }) - ) { backStackEntry: NavBackStackEntry -> + dialog { backStackEntry: NavBackStackEntry -> ErrorDialog( - message = AppNavGraph.ARG_MESSAGE.let(backStackEntry.arguments!!::getString), + message = backStackEntry.toRoute().message, dismissDialog = navController::navigateUp ) } diff --git a/shared-compose-ui/src/commonMain/kotlin/com/softartdev/notedelight/navigation/RouterImpl.kt b/shared-compose-ui/src/commonMain/kotlin/com/softartdev/notedelight/navigation/RouterImpl.kt index 48f7f14f..94942c83 100644 --- a/shared-compose-ui/src/commonMain/kotlin/com/softartdev/notedelight/navigation/RouterImpl.kt +++ b/shared-compose-ui/src/commonMain/kotlin/com/softartdev/notedelight/navigation/RouterImpl.kt @@ -15,9 +15,9 @@ class RouterImpl : Router { navController = null } - override fun navigate(route: String) = navController!!.navigate(route) + override fun navigate(route: T) = navController!!.navigate(route) - override fun navigateClearingBackStack(route: String) { + override fun navigateClearingBackStack(route: T) { var popped = true while (popped) { popped = navController!!.popBackStack() @@ -25,7 +25,7 @@ class RouterImpl : Router { navController!!.navigate(route) } - override fun popBackStack(route: String, inclusive: Boolean, saveState: Boolean): Boolean = + override fun popBackStack(route: T, inclusive: Boolean, saveState: Boolean): Boolean = navController!!.popBackStack(route, inclusive, saveState) override fun popBackStack() = navController!!.popBackStack() diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index b5829fb8..c26efc6b 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -6,6 +6,7 @@ plugins { alias(libs.plugins.gradle.convention) alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.cocoapods) + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.sqlDelight) alias(libs.plugins.android.library) } @@ -65,6 +66,7 @@ kotlin { implementation(libs.koin.core) api(libs.material.theme.prefs) implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.kotlinx.serialization.json) } commonTest.dependencies { implementation(kotlin("test")) diff --git a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/main/MainViewModelTest.kt b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/main/MainViewModelTest.kt index 7ec657cb..df0644a3 100644 --- a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/main/MainViewModelTest.kt +++ b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/main/MainViewModelTest.kt @@ -63,7 +63,7 @@ class MainViewModelTest { Mockito.`when`(mockNoteDAO.listFlow).thenReturn(flow { throw SQLiteException() }) mainViewModel.updateNotes() assertEquals(NoteListResult.Error(null), awaitItem()) - Mockito.verify(mockRouter).navigateClearingBackStack(route = AppNavGraph.SignIn.name) + Mockito.verify(mockRouter).navigateClearingBackStack(route = AppNavGraph.SignIn) cancelAndIgnoreRemainingEvents() } @@ -72,13 +72,13 @@ class MainViewModelTest { @Test fun onNoteClicked() { mainViewModel.onNoteClicked(1) - Mockito.verify(mockRouter).navigate(route = "${AppNavGraph.Details.name}/1") + Mockito.verify(mockRouter).navigate(route = AppNavGraph.Details(noteId = 1)) } @Test fun onSettingsClicked() { mainViewModel.onSettingsClicked() - Mockito.verify(mockRouter).navigate(route = AppNavGraph.Settings.name) + Mockito.verify(mockRouter).navigate(route = AppNavGraph.Settings) } @Test diff --git a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/note/NoteViewModelTest.kt b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/note/NoteViewModelTest.kt index ac70d527..8a81a1db 100644 --- a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/note/NoteViewModelTest.kt +++ b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/note/NoteViewModelTest.kt @@ -118,7 +118,7 @@ class NoteViewModelTest { noteViewModel.setIdForTest(id) noteViewModel.editTitle() - Mockito.verify(mockRouter).navigate(route = "${AppNavGraph.EditTitleDialog.name}/$id") + Mockito.verify(mockRouter).navigate(route = AppNavGraph.EditTitleDialog(noteId = id)) UpdateTitleUseCase.titleChannel.send(title) assertEquals(NoteResult.TitleUpdated(title), awaitItem()) @@ -148,7 +148,7 @@ class NoteViewModelTest { noteViewModel.setIdForTest(id) Mockito.`when`(mockNoteDAO.load(id)).thenReturn(note.copy(text = "new text")) noteViewModel.checkSaveChange(title, text) - Mockito.verify(mockRouter).navigate(route = AppNavGraph.SaveChangesDialog.name) + Mockito.verify(mockRouter).navigate(route = AppNavGraph.SaveChangesDialog) SaveNoteUseCase.dialogChannel.send(true) Mockito.verify(mockRouter).popBackStack() diff --git a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/settings/SettingsViewModelTest.kt b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/settings/SettingsViewModelTest.kt index 00d264d5..dedb9528 100644 --- a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/settings/SettingsViewModelTest.kt +++ b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/settings/SettingsViewModelTest.kt @@ -35,7 +35,7 @@ class SettingsViewModelTest { @Test fun changeTheme() = runTest { settingsViewModel.stateFlow.value.changeTheme.invoke() - Mockito.verify(mockRouter).navigate(route = AppNavGraph.ThemeDialog.name) + Mockito.verify(mockRouter).navigate(route = AppNavGraph.ThemeDialog) Mockito.verifyNoMoreInteractions(mockRouter) } @@ -65,7 +65,7 @@ class SettingsViewModelTest { settingsViewModel.stateFlow.test { assertFalse(awaitItem().loading) settingsViewModel.stateFlow.value.changeEncryption.invoke(true) - Mockito.verify(mockRouter).navigate(route = AppNavGraph.ConfirmPasswordDialog.name) + Mockito.verify(mockRouter).navigate(route = AppNavGraph.ConfirmPasswordDialog) expectNoEvents() } Mockito.verifyNoMoreInteractions(mockRouter) @@ -77,7 +77,7 @@ class SettingsViewModelTest { settingsViewModel.stateFlow.test { assertFalse(awaitItem().loading) settingsViewModel.stateFlow.value.changeEncryption.invoke(false) - Mockito.verify(mockRouter).navigate(route = AppNavGraph.EnterPasswordDialog.name) + Mockito.verify(mockRouter).navigate(route = AppNavGraph.EnterPasswordDialog) expectNoEvents() } Mockito.verifyNoMoreInteractions(mockRouter) @@ -102,7 +102,7 @@ class SettingsViewModelTest { settingsViewModel.stateFlow.test { assertFalse(awaitItem().loading) settingsViewModel.stateFlow.value.changePassword.invoke() - Mockito.verify(mockRouter).navigate(route = AppNavGraph.ChangePasswordDialog.name) + Mockito.verify(mockRouter).navigate(route = AppNavGraph.ChangePasswordDialog) expectNoEvents() } Mockito.verifyNoMoreInteractions(mockRouter) @@ -114,7 +114,7 @@ class SettingsViewModelTest { settingsViewModel.stateFlow.test { assertFalse(awaitItem().loading) settingsViewModel.stateFlow.value.changePassword.invoke() - Mockito.verify(mockRouter).navigate(route = AppNavGraph.ConfirmPasswordDialog.name) + Mockito.verify(mockRouter).navigate(route = AppNavGraph.ConfirmPasswordDialog) expectNoEvents() } Mockito.verifyNoMoreInteractions(mockRouter) diff --git a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/signin/SignInViewModelTest.kt b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/signin/SignInViewModelTest.kt index 80784b57..27c81a2c 100644 --- a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/signin/SignInViewModelTest.kt +++ b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/signin/SignInViewModelTest.kt @@ -51,7 +51,7 @@ class SignInViewModelTest { val pass = StubEditable("pass") Mockito.`when`(mockCheckPasswordUseCase(pass)).thenReturn(true) signInViewModel.signIn(pass) - Mockito.verify(mockRouter).navigateClearingBackStack(route = AppNavGraph.Main.name) + Mockito.verify(mockRouter).navigateClearingBackStack(route = AppNavGraph.Main) cancelAndIgnoreRemainingEvents() } @@ -92,7 +92,7 @@ class SignInViewModelTest { Mockito.`when`(mockCheckPasswordUseCase(anyObject())).thenThrow(throwable) signInViewModel.signIn(StubEditable("pass")) Mockito.verify(mockRouter).navigate( - route = AppNavGraph.ErrorDialog.argRoute(message = throwable.message) + route = AppNavGraph.ErrorDialog(message = throwable.message) ) cancelAndIgnoreRemainingEvents() } diff --git a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/splash/SplashViewModelTest.kt b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/splash/SplashViewModelTest.kt index 70ed8da3..a49648b0 100644 --- a/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/splash/SplashViewModelTest.kt +++ b/shared/src/androidUnitTest/kotlin/com/softartdev/notedelight/shared/presentation/splash/SplashViewModelTest.kt @@ -35,7 +35,7 @@ class SplashViewModelTest { splashViewModel.checkEncryption() - Mockito.verify(mockRouter).navigateClearingBackStack(route = AppNavGraph.SignIn.name) + Mockito.verify(mockRouter).navigateClearingBackStack(route = AppNavGraph.SignIn) assertEquals(false, awaitItem()) cancelAndIgnoreRemainingEvents() @@ -51,7 +51,7 @@ class SplashViewModelTest { splashViewModel.checkEncryption() - Mockito.verify(mockRouter).navigateClearingBackStack(route = AppNavGraph.Main.name) + Mockito.verify(mockRouter).navigateClearingBackStack(route = AppNavGraph.Main) assertEquals(false, awaitItem()) cancelAndIgnoreRemainingEvents() @@ -68,7 +68,7 @@ class SplashViewModelTest { splashViewModel.checkEncryption() Mockito.verify(mockRouter).navigate( - route = AppNavGraph.ErrorDialog.argRoute(message = null) + route = AppNavGraph.ErrorDialog(message = null) ) assertEquals(false, awaitItem()) diff --git a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/navigation/AppNavGraph.kt b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/navigation/AppNavGraph.kt index 31a92c2b..7613681d 100644 --- a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/navigation/AppNavGraph.kt +++ b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/navigation/AppNavGraph.kt @@ -1,36 +1,45 @@ package com.softartdev.notedelight.shared.navigation -enum class AppNavGraph { - Splash, - SignIn, - Main, - Details, - Settings, - ThemeDialog, - SaveChangesDialog, - EditTitleDialog, - DeleteNoteDialog, - EnterPasswordDialog, - ConfirmPasswordDialog, - ChangePasswordDialog, - ErrorDialog; - - val route: String - get() = when (this) { - Details, EditTitleDialog -> "$name/{$ARG_NOTE_ID}" - ErrorDialog -> "$name?$ARG_MESSAGE={$ARG_MESSAGE}" - else -> name - } - - fun argRoute(message: String?): String = when (message) { - null -> name - else -> "$name?$ARG_MESSAGE=$message" - } - - fun argRoute(noteId: Long): String = "$name/$noteId" - - companion object { - const val ARG_NOTE_ID = "noteId" - const val ARG_MESSAGE = "message" - } -} \ No newline at end of file +import kotlinx.serialization.Serializable + +sealed interface AppNavGraph { + + @Serializable + data object Splash : AppNavGraph + + @Serializable + data object SignIn : AppNavGraph + + @Serializable + data object Main : AppNavGraph + + @Serializable + data class Details(val noteId: Long) : AppNavGraph + + @Serializable + data object Settings : AppNavGraph + + @Serializable + data object ThemeDialog : AppNavGraph + + @Serializable + data object SaveChangesDialog : AppNavGraph + + @Serializable + data class EditTitleDialog(val noteId: Long) : AppNavGraph + + @Serializable + data object DeleteNoteDialog : AppNavGraph + + @Serializable + data object EnterPasswordDialog : AppNavGraph + + @Serializable + data object ConfirmPasswordDialog : AppNavGraph + + @Serializable + data object ChangePasswordDialog : AppNavGraph + + @Serializable + data class ErrorDialog(val message: String?) : AppNavGraph +} diff --git a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/navigation/Router.kt b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/navigation/Router.kt index 47d4cfb5..c2df26fb 100644 --- a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/navigation/Router.kt +++ b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/navigation/Router.kt @@ -6,11 +6,11 @@ interface Router { fun releaseController() - fun navigate(route: String) + fun navigate(route: T) - fun navigateClearingBackStack(route: String) + fun navigateClearingBackStack(route: T) - fun popBackStack(route: String, inclusive: Boolean, saveState: Boolean): Boolean + fun popBackStack(route: T, inclusive: Boolean, saveState: Boolean): Boolean fun popBackStack(): Boolean } diff --git a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/main/MainViewModel.kt b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/main/MainViewModel.kt index 209161fe..cd7fb53c 100644 --- a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/main/MainViewModel.kt +++ b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/main/MainViewModel.kt @@ -41,12 +41,12 @@ class MainViewModel( Napier.e("❌", throwable) mutableStateFlow.value = NoteListResult.Error(throwable.message) if (throwable::class.simpleName.orEmpty().contains("SQLite")) { - router.navigateClearingBackStack(AppNavGraph.SignIn.name) + router.navigateClearingBackStack(AppNavGraph.SignIn) } }.launchIn(this) } - fun onNoteClicked(id: Long) = router.navigate(route = AppNavGraph.Details.argRoute(noteId = id)) + fun onNoteClicked(id: Long) = router.navigate(route = AppNavGraph.Details(noteId = id)) - fun onSettingsClicked() = router.navigate(route = AppNavGraph.Settings.name) + fun onSettingsClicked() = router.navigate(route = AppNavGraph.Settings) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/note/NoteViewModel.kt b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/note/NoteViewModel.kt index faac8096..4ad76a01 100644 --- a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/note/NoteViewModel.kt +++ b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/note/NoteViewModel.kt @@ -70,7 +70,7 @@ class NoteViewModel( fun editTitle() = viewModelScope.launch { subscribeToEditTitle() - router.navigate(route = AppNavGraph.EditTitleDialog.argRoute(noteId = noteId)) + router.navigate(route = AppNavGraph.EditTitleDialog(noteId = noteId)) } fun deleteNote() = viewModelScope.launch { @@ -83,7 +83,7 @@ class NoteViewModel( val empty: Boolean = isEmpty(noteId) when { changed -> { - router.navigate(route = AppNavGraph.SaveChangesDialog.name) + router.navigate(route = AppNavGraph.SaveChangesDialog) subscribeToSaveNote(title, text) } empty -> mutableStateFlow.value = deleteNoteForResult() @@ -120,7 +120,7 @@ class NoteViewModel( } fun subscribeToDeleteNote() = viewModelScope.launch { - router.navigate(route = AppNavGraph.DeleteNoteDialog.name) + router.navigate(route = AppNavGraph.DeleteNoteDialog) val doDelete: Boolean = withContext(coroutineDispatchers.io) { DeleteNoteUseCase.deleteChannel.receive() } @@ -137,7 +137,7 @@ class NoteViewModel( deleteNoteUseCase.invoke(id = noteId) } Napier.d("Deleted note with id=$noteId") - router.popBackStack(route = AppNavGraph.Main.name, inclusive = false, saveState = false) + router.popBackStack(route = AppNavGraph.Main, inclusive = false, saveState = false) return NoteResult.Deleted } @@ -150,7 +150,7 @@ class NoteViewModel( mutableStateFlow.value = NoteResult.TitleUpdated(title) } catch (e: Throwable) { Napier.e("❌", e) - router.navigate(route = AppNavGraph.ErrorDialog.argRoute(message = e.message)) + router.navigate(route = AppNavGraph.ErrorDialog(message = e.message)) } } diff --git a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/settings/SettingsViewModel.kt b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/settings/SettingsViewModel.kt index 9800115f..6ebb8bb8 100644 --- a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/settings/SettingsViewModel.kt +++ b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/settings/SettingsViewModel.kt @@ -34,7 +34,7 @@ class SettingsViewModel( private val dbIsEncrypted: Boolean get() = safeRepo.databaseState == PlatformSQLiteState.ENCRYPTED - private fun changeTheme() = router.navigate(route = AppNavGraph.ThemeDialog.name) + private fun changeTheme() = router.navigate(route = AppNavGraph.ThemeDialog) private fun checkEncryption() = viewModelScope.launch { mutableStateFlow.update(SecurityResult::showLoading) @@ -42,7 +42,7 @@ class SettingsViewModel( mutableStateFlow.update { result -> result.copy(encryption = dbIsEncrypted) } } catch (e: Throwable) { Napier.e("❌", e) - router.navigate(route = AppNavGraph.ErrorDialog.argRoute(message = e.message)) + router.navigate(route = AppNavGraph.ErrorDialog(message = e.message)) } finally { mutableStateFlow.update(SecurityResult::hideLoading) } @@ -52,15 +52,15 @@ class SettingsViewModel( mutableStateFlow.update(SecurityResult::showLoading) try { when { - checked -> router.navigate(route = AppNavGraph.ConfirmPasswordDialog.name) + checked -> router.navigate(route = AppNavGraph.ConfirmPasswordDialog) else -> when { - dbIsEncrypted -> router.navigate(route = AppNavGraph.EnterPasswordDialog.name) + dbIsEncrypted -> router.navigate(route = AppNavGraph.EnterPasswordDialog) else -> mutableStateFlow.update(SecurityResult::hideEncryption) } } } catch (e: Throwable) { Napier.e("❌", e) - router.navigate(route = AppNavGraph.ErrorDialog.argRoute(message = e.message)) + router.navigate(route = AppNavGraph.ErrorDialog(message = e.message)) } finally { mutableStateFlow.update(SecurityResult::hideLoading) } @@ -70,12 +70,12 @@ class SettingsViewModel( mutableStateFlow.update(SecurityResult::showLoading) try { when { - dbIsEncrypted -> router.navigate(route = AppNavGraph.ChangePasswordDialog.name) - else -> router.navigate(route = AppNavGraph.ConfirmPasswordDialog.name) + dbIsEncrypted -> router.navigate(route = AppNavGraph.ChangePasswordDialog) + else -> router.navigate(route = AppNavGraph.ConfirmPasswordDialog) } } catch (e: Throwable) { Napier.e("❌", e) - router.navigate(route = AppNavGraph.ErrorDialog.argRoute(message = e.message)) + router.navigate(route = AppNavGraph.ErrorDialog(message = e.message)) } finally { mutableStateFlow.update(SecurityResult::hideLoading) } @@ -88,7 +88,7 @@ class SettingsViewModel( mutableStateFlow.update { result -> result.copy(snackBarMessage = cipherVersion) } } catch (e: Throwable) { Napier.e("❌", e) - router.navigate(route = AppNavGraph.ErrorDialog.argRoute(message = e.message)) + router.navigate(route = AppNavGraph.ErrorDialog(message = e.message)) } finally { mutableStateFlow.update(SecurityResult::hideLoading) } diff --git a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/signin/SignInViewModel.kt b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/signin/SignInViewModel.kt index df461cc6..df4bc678 100644 --- a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/signin/SignInViewModel.kt +++ b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/signin/SignInViewModel.kt @@ -25,14 +25,14 @@ class SignInViewModel( mutableStateFlow.value = when { pass.isEmpty() -> SignInResult.ShowEmptyPassError checkPasswordUseCase(pass) -> { - router.navigateClearingBackStack(AppNavGraph.Main.name) + router.navigateClearingBackStack(AppNavGraph.Main) SignInResult.ShowSignInForm } else -> SignInResult.ShowIncorrectPassError } } catch (error: Throwable) { Napier.e("❌", error) - router.navigate(route = AppNavGraph.ErrorDialog.argRoute(message = error.message)) + router.navigate(route = AppNavGraph.ErrorDialog(message = error.message)) mutableStateFlow.value = SignInResult.ShowSignInForm } } diff --git a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/splash/SplashViewModel.kt b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/splash/SplashViewModel.kt index 1ea78a93..38402d6f 100644 --- a/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/splash/SplashViewModel.kt +++ b/shared/src/commonMain/kotlin/com/softartdev/notedelight/shared/presentation/splash/SplashViewModel.kt @@ -22,13 +22,13 @@ class SplashViewModel( try { router.navigateClearingBackStack( route = when (safeRepo.databaseState) { - PlatformSQLiteState.ENCRYPTED -> AppNavGraph.SignIn.name - else -> AppNavGraph.Main.name + PlatformSQLiteState.ENCRYPTED -> AppNavGraph.SignIn + else -> AppNavGraph.Main } ) } catch (error: Throwable) { Napier.e("❌", error) - router.navigate(route = AppNavGraph.ErrorDialog.argRoute(message = error.message)) + router.navigate(route = AppNavGraph.ErrorDialog(message = error.message)) } mutableStateFlow.value = false } diff --git a/shared/src/jvmTest/kotlin/com/softartdev/notedelight/shared/navigation/RouterStub.kt b/shared/src/jvmTest/kotlin/com/softartdev/notedelight/shared/navigation/RouterStub.kt index 5916af7d..557b7607 100644 --- a/shared/src/jvmTest/kotlin/com/softartdev/notedelight/shared/navigation/RouterStub.kt +++ b/shared/src/jvmTest/kotlin/com/softartdev/notedelight/shared/navigation/RouterStub.kt @@ -8,12 +8,12 @@ class RouterStub : Router { override fun releaseController() = Napier.d(message = "releaseController") - override fun navigate(route: String) = Napier.d(message = "navigate: $route") + override fun navigate(route: T) = Napier.d(message = "navigate: $route") - override fun navigateClearingBackStack(route: String) = + override fun navigateClearingBackStack(route: T) = Napier.d("navigateClearingBackStack: $route") - override fun popBackStack(route: String, inclusive: Boolean, saveState: Boolean): Boolean { + override fun popBackStack(route: T, inclusive: Boolean, saveState: Boolean): Boolean { Napier.d("popBackStack: $route, inclusive: $inclusive, saveState: $saveState") return true }