From e974093b603dbba823e6a5e0e0dc3f88106127cd Mon Sep 17 00:00:00 2001 From: seiko Date: Fri, 8 Apr 2022 19:31:57 +0800 Subject: [PATCH 01/18] [wip]migrate precompose viewmodel --- app/build.gradle.kts | 6 +- .../com/dimension/maskbook/ComposeActivity.kt | 6 +- buildSrc/src/main/kotlin/Versions.kt | 1 - common/build.gradle.kts | 77 +- common/gecko/sample/build.gradle.kts | 2 +- .../dimension/maskbook/common/CommonSetup.kt | 8 +- .../dimension/maskbook/common/ModuleSetup.kt | 8 +- .../dimension/maskbook/common/ext/FlowExt.kt | 21 - .../com/dimension/maskbook/common/ext/Json.kt | 2 +- .../dimension/maskbook/common/ext/KoinExt.kt | 13 +- .../maskbook/common/ext/NavControllerExt.kt | 66 +- .../common/ext/NavControllerResult.kt | 23 +- .../maskbook/common/route/AnimatedNavHost.kt | 19 + .../dimension/maskbook/common/route/Consts.kt | 93 +-- .../ui/notification/InAppNotification.kt | 6 +- .../common/ui/scene/SetupPaymentPassword.kt | 9 +- .../ui/scene/VerifyMnemonicWordsScene.kt | 2 +- .../maskbook/common/ui/tab/TabScreen.kt | 2 +- .../maskbook/common/ui/theme/IsDarkTheme.kt | 6 +- .../maskbook/common/ui/theme/MoreColors.kt | 4 +- .../common/ui/widget/MaskInAppNotification.kt | 2 +- .../common/ui/widget/WalletTokenImage.kt | 6 +- .../viewmodel/BaseMnemonicPhraseViewModel.kt | 4 +- .../viewmodel/BiometricEnableViewModel.kt | 2 +- .../common/viewmodel/BiometricViewModel.kt | 4 +- .../SetUpPaymentPasswordViewModel.kt | 4 +- .../lifecycle/PreComposeActivity.kt | 142 ++++ .../kotlin/moe/tlaster/koin/ViewModel.kt | 43 ++ .../moe/tlaster/koin/compose/ComposeExt.kt | 48 ++ .../moe/tlaster/koin/compose/ViewModelExt.kt | 90 +++ .../tlaster/precompose/lifecycle/Lifecycle.kt | 35 + .../precompose/lifecycle/LifecycleObserver.kt | 25 + .../precompose/lifecycle/LifecycleOwner.kt | 25 + .../precompose/lifecycle/LifecycleRegistry.kt | 52 ++ .../precompose/lifecycle/RepeatOnLifecycle.kt | 162 ++++ .../precompose/navigation/BackHandler.kt | 60 ++ .../precompose/navigation/BackStackEntry.kt | 94 +++ .../navigation/NavControllerViewModel.kt | 54 ++ .../tlaster/precompose/navigation/NavHost.kt | 127 ++++ .../precompose/navigation/NavOptions.kt | 38 + .../precompose/navigation/Navigator.kt | 118 +++ .../tlaster/precompose/navigation/PopUpTo.kt | 32 + .../precompose/navigation/QueryString.kt | 54 ++ .../precompose/navigation/RouteBuilder.kt | 85 +++ .../precompose/navigation/RouteGraph.kt | 28 + .../precompose/navigation/RouteMatch.kt | 64 ++ .../precompose/navigation/RouteMatchResult.kt | 28 + .../precompose/navigation/RouteParser.kt | 695 ++++++++++++++++++ .../precompose/navigation/RouteStack.kt | 72 ++ .../navigation/RouteStackManager.kt | 223 ++++++ .../navigation/route/ComposeRoute.kt | 34 + .../navigation/route/DialogRoute.kt | 29 + .../precompose/navigation/route/Route.kt | 27 + .../precompose/navigation/route/SceneRoute.kt | 41 ++ .../transition/AnimatedDialogRoute.kt | 129 ++++ .../navigation/transition/AnimatedRoute.kt | 132 ++++ .../navigation/transition/DialogTransition.kt | 41 ++ .../navigation/transition/NavTransition.kt | 77 ++ .../tlaster/precompose/ui/BackPressAdapter.kt | 54 ++ .../precompose/ui/ComposeCompositionLocal.kt | 33 + .../tlaster/precompose/ui/ViewModelAdapter.kt | 70 ++ .../viewmodel/CloseableCoroutineScope.kt | 57 ++ .../tlaster/precompose/viewmodel/ViewModel.kt | 63 ++ .../precompose/viewmodel/ViewModelProvider.kt | 50 ++ .../precompose/viewmodel/ViewModelStore.kt | 45 ++ .../viewmodel/ViewModelStoreOwner.kt | 25 + .../precompose/viewmodel/compose/ViewModel.kt | 42 ++ .../maskbook/common/route/CommonRoute.kt | 0 .../maskbook/common/route/Deeplinks.kt | 0 .../maskbook/common/route/WebDeepLinks.kt | 0 .../dimension/maskbook/entry/EntrySetup.kt | 6 +- .../maskbook/entry/ui/ComposeDebugTool.kt | 6 +- .../maskbook/entry/ui/scene/IntroScene.kt | 4 +- .../maskbook/entry/ui/scene/MainHost.kt | 2 +- .../maskbook/extension/ExtensionSetup.kt | 27 +- .../maskbook/extension/ui/WebContentScene.kt | 4 +- .../com/dimension/maskbook/labs/LabsSetup.kt | 8 +- .../maskbook/labs/ui/scenes/LabsScene.kt | 6 +- .../labs/ui/scenes/LabsTransakScene.kt | 6 +- .../labs/ui/scenes/PluginSettingsScene.kt | 4 +- .../ui/scenes/redpacket/LuckyDropModal.kt | 4 +- .../maskbook/labs/ui/tab/LabsTabScreen.kt | 2 +- .../maskbook/labs/viewmodel/LabsViewModel.kt | 4 +- .../labs/viewmodel/LuckDropViewModel.kt | 4 +- .../labs/viewmodel/PluginSettingsViewModel.kt | 4 +- .../maskbook/persona/PersonaSetup.kt | 10 +- .../maskbook/persona/route/RegisterRoute.kt | 20 +- .../persona/route/SynchronizationRoute.kt | 5 +- .../persona/ui/scenes/BackupPasswordScene.kt | 11 +- .../persona/ui/scenes/DownloadQrCodeScene.kt | 10 +- .../ui/scenes/ExportPrivateKeyScene.kt | 6 +- .../persona/ui/scenes/LogoutDialog.kt | 5 +- .../persona/ui/scenes/PersonaInfoScene.kt | 4 +- .../persona/ui/scenes/PersonaMenuScene.kt | 4 +- .../persona/ui/scenes/PersonaScene.kt | 2 +- .../persona/ui/scenes/RenamePersonaModal.kt | 4 +- .../persona/ui/scenes/SwitchPersonaModal.kt | 10 +- .../ui/scenes/avatar/PersonaAvatarModal.kt | 4 +- .../ui/scenes/avatar/SetAvatarScene.kt | 2 +- .../persona/ui/scenes/post/PostScene.kt | 6 +- .../createidentity/CreateIdentityHost.kt | 11 +- .../recovery/local/RecoveryLocalHost.kt | 4 +- .../remote/RemoteBackupRecoveryHost.kt | 40 +- .../ui/scenes/social/ConnectAccountModal.kt | 7 +- .../persona/ui/scenes/social/ConnectSocial.kt | 2 +- .../scenes/social/DisconnectSocialDialog.kt | 4 +- .../ui/scenes/social/SelectPlatformModal.kt | 4 +- .../persona/ui/tab/PersonasTabScreen.kt | 12 +- .../viewmodel/BackUpPasswordViewModel.kt | 2 +- .../viewmodel/DownloadQrCodeViewModel.kt | 4 +- .../viewmodel/ExportPrivateKeyViewModel.kt | 4 +- .../persona/viewmodel/PersonaMenuViewModel.kt | 4 +- .../persona/viewmodel/PersonaViewModel.kt | 4 +- .../viewmodel/RenamePersonaViewModel.kt | 4 +- .../viewmodel/SwitchPersonaViewModel.kt | 4 +- .../viewmodel/avatar/SetAvatarViewModel.kt | 2 +- .../viewmodel/contacts/ContactsViewModel.kt | 4 +- .../persona/viewmodel/post/PostViewModel.kt | 4 +- .../viewmodel/recovery/IdentityViewModel.kt | 4 +- .../viewmodel/recovery/PrivateKeyViewModel.kt | 4 +- .../recovery/RecoveryLocalViewModel.kt | 4 +- .../register/CreateIdentityViewModel.kt | 2 +- .../register/RemoteBackupRecoveryViewModel.kt | 4 +- .../social/DisconnectSocialViewModel.kt | 2 +- .../social/UserNameModalViewModel.kt | 4 +- .../maskbook/setting/SettingSetup.kt | 10 +- .../maskbook/setting/route/BackupRoute.kt | 67 +- .../maskbook/setting/route/SettingsRoute.kt | 95 ++- .../setting/ui/scenes/AppearanceSettings.kt | 6 +- .../ui/scenes/ChangeBackUpPasswordModal.kt | 18 +- .../ui/scenes/ChangePaymentPasswordModal.kt | 16 +- .../setting/ui/scenes/DataSourceSettings.kt | 6 +- .../setting/ui/scenes/LanguageSettings.kt | 6 +- .../setting/ui/scenes/SettingsScene.kt | 25 +- .../ui/scenes/backup/BackupCloudScene.kt | 16 +- .../ui/scenes/backup/BackupLocalScene.kt | 16 +- .../setting/ui/tab/SettingsTabScreen.kt | 2 +- .../viewmodel/AppearanceSettingsViewModel.kt | 4 +- .../viewmodel/BackupCloudExecuteViewModel.kt | 2 +- .../setting/viewmodel/BackupCloudViewModel.kt | 4 +- .../setting/viewmodel/BackupLocalViewModel.kt | 4 +- .../viewmodel/BackupMergeConfirmViewModel.kt | 4 +- .../BackupPasswordSettingsViewModel.kt | 4 +- .../viewmodel/DataSourceSettingsViewModel.kt | 4 +- .../setting/viewmodel/EmailBackupViewModel.kt | 2 +- .../setting/viewmodel/EmailSetupViewModel.kt | 2 +- .../viewmodel/LanguageSettingsViewModel.kt | 4 +- .../PaymentPasswordSettingsViewModel.kt | 4 +- .../base/RemoteBackupRecoveryViewModelBase.kt | 4 +- .../dimension/maskbook/wallet/WalletSetup.kt | 12 +- .../maskbook/wallet/route/WalletsRoute.kt | 73 +- .../create/CreateOrImportWalletScene.kt | 2 +- .../wallets/create/create/CreateWalletHost.kt | 2 +- .../import/ImportWalletDerivationPathScene.kt | 14 +- .../import/ImportWalletKeystoreScene.kt | 17 +- .../import/ImportWalletMnemonicScene.kt | 12 +- .../import/ImportWalletPrivateKeyScene.kt | 15 +- .../scenes/wallets/intro/WalletIntroHost.kt | 26 +- .../intro/password/BiometricsEnableScene.kt | 2 +- .../intro/password/TouchIdEnableScene.kt | 2 +- .../wallets/send/SendTokenConfirmModal.kt | 51 +- .../ui/scenes/wallets/send/TranserHost.kt | 75 +- .../walletconnect/WalletConnectModal.kt | 24 +- .../maskbook/wallet/ui/tab/WalletTabScreen.kt | 4 +- .../ui/widget/CollectibleCollectionCard.kt | 2 +- .../wallet/viewmodel/WelcomeViewModel.kt | 4 +- .../viewmodel/wallets/TokenDetailViewModel.kt | 4 +- .../wallets/TouchIdEnableViewModel.kt | 2 +- .../wallets/UnlockWalletViewModel.kt | 2 +- .../wallets/WalletBalancesViewModel.kt | 4 +- .../WalletConnectManagementViewModel.kt | 4 +- .../wallets/WalletManagementModalViewModel.kt | 4 +- .../collectible/CollectibleDetailViewModel.kt | 4 +- .../collectible/CollectiblesViewModel.kt | 2 +- .../CreateWalletRecoveryKeyViewModel.kt | 2 +- .../ImportWalletDerivationPathViewModel.kt | 4 +- .../import/ImportWalletKeystoreViewModel.kt | 4 +- .../import/ImportWalletMnemonicViewModel.kt | 4 +- .../import/ImportWalletPrivateKeyViewModel.kt | 4 +- .../management/WalletBackupViewModel.kt | 2 +- .../management/WalletDeleteViewModel.kt | 4 +- .../management/WalletRenameViewModel.kt | 4 +- .../management/WalletSwitchEditViewModel.kt | 2 +- .../management/WalletSwitchViewModel.kt | 4 +- .../WalletTransactionHistoryViewModel.kt | 4 +- .../wallets/send/AddContactViewModel.kt | 4 +- .../viewmodel/wallets/send/GasFeeViewModel.kt | 4 +- .../wallets/send/SearchAddressViewModel.kt | 4 +- .../wallets/send/SearchTradableViewModel.kt | 4 +- .../wallets/send/SendConfirmViewModel.kt | 4 +- .../wallets/send/TransferDetailViewModel.kt | 4 +- .../send/Web3TransactionConfirmViewModel.kt | 4 +- .../walletconnect/WalletConnectViewModel.kt | 4 +- 193 files changed, 4095 insertions(+), 740 deletions(-) create mode 100644 common/src/androidMain/kotlin/com/dimension/maskbook/common/route/AnimatedNavHost.kt create mode 100644 common/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeActivity.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/koin/ViewModel.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/koin/compose/ComposeExt.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/koin/compose/ViewModelExt.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/Lifecycle.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleObserver.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleOwner.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleRegistry.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/RepeatOnLifecycle.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavControllerViewModel.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavOptions.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/PopUpTo.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedDialogRoute.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedRoute.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/DialogTransition.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/NavTransition.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/ui/ComposeCompositionLocal.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/ui/ViewModelAdapter.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/CloseableCoroutineScope.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModel.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelProvider.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelStore.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelStoreOwner.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/compose/ViewModel.kt rename common/src/commonMain/{kotlin => route}/com/dimension/maskbook/common/route/CommonRoute.kt (100%) rename common/src/commonMain/{kotlin => route}/com/dimension/maskbook/common/route/Deeplinks.kt (100%) rename common/src/commonMain/{kotlin => route}/com/dimension/maskbook/common/route/WebDeepLinks.kt (100%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a8ea5167..53d5aaa2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -54,12 +54,14 @@ android { } dependencies { - implementation("androidx.core:core-splashscreen:1.0.0-beta01") - implementation("androidx.activity:activity-compose:${Versions.Androidx.activityCompose}") + implementation("androidx.core:core-splashscreen:1.0.0-beta02") implementation(projects.entry) implementation(projects.common) implementation(projects.common.gecko) + // Koin + implementation("io.insert-koin:koin-android:${Versions.koin}") + if (enableFirebase) { implementation("com.google.firebase:firebase-analytics-ktx:${Versions.Firebase.analytics}") implementation(platform("com.google.firebase:firebase-bom:${Versions.Firebase.bom}")) diff --git a/app/src/main/java/com/dimension/maskbook/ComposeActivity.kt b/app/src/main/java/com/dimension/maskbook/ComposeActivity.kt index a7aa364d..26a4b821 100644 --- a/app/src/main/java/com/dimension/maskbook/ComposeActivity.kt +++ b/app/src/main/java/com/dimension/maskbook/ComposeActivity.kt @@ -23,12 +23,10 @@ package com.dimension.maskbook import android.content.Intent import android.os.Bundle import android.view.WindowManager -import androidx.activity.compose.setContent import androidx.compose.runtime.CompositionLocalProvider import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat -import androidx.fragment.app.FragmentActivity import coil.compose.LocalImageLoader import com.dimension.maskbook.common.gecko.PromptFeatureDelegate import com.dimension.maskbook.common.gecko.WebContentController @@ -36,10 +34,12 @@ import com.dimension.maskbook.common.manager.ImageLoaderManager import com.dimension.maskbook.common.ui.widget.LocalWindowInsetsController import com.dimension.maskbook.entry.ui.App import com.google.accompanist.insets.ProvideWindowInsets +import moe.tlaster.precompose.lifecycle.PreComposeActivity +import moe.tlaster.precompose.lifecycle.setContent import org.koin.android.ext.android.get import org.koin.android.ext.android.inject -class ComposeActivity : FragmentActivity() { +class ComposeActivity : PreComposeActivity() { private lateinit var promptFeature: PromptFeatureDelegate private val imageLoaderManager: ImageLoaderManager by inject() diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index ffca0021..758323aa 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -61,7 +61,6 @@ object Versions { const val paging = "3.1.0" const val pagingCompose = "1.0.0-alpha14" const val annotation = "1.3.0" - const val activityCompose = "1.4.0" const val biometric = "1.2.0-alpha04" const val activity = "1.4.0" const val fragment = "1.3.6" diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 122ec84d..f7648794 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -10,29 +10,18 @@ kotlin { android() sourceSets { val commonMain by getting { - dependencies { - implementation(projects.common.routeProcessor.annotations) - kspAndroid(projects.common.routeProcessor) - api(projects.common.bigDecimal) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - } - } - val androidMain by getting { + kotlin.srcDir("src/commonMain/route") dependencies { api(projects.wallet.export) api(projects.labs.export) api(projects.persona.export) api(projects.setting.export) api(projects.extension.export) - api(projects.localization) - api(projects.common.retrofit) - api(projects.common.okhttp) api(projects.common.bigDecimal) + implementation(projects.common.routeProcessor.annotations) + kspAndroid(projects.common.routeProcessor) + // Compose api("org.jetbrains.compose.ui:ui:${Versions.compose_jb}") api("org.jetbrains.compose.ui:ui-util:${Versions.compose_jb}") @@ -43,16 +32,44 @@ kotlin { api("org.jetbrains.compose.ui:ui-tooling:${Versions.compose_jb}") // Koin - api("io.insert-koin:koin-android:${Versions.koin}") - api("io.insert-koin:koin-androidx-compose:${Versions.koin}") + api("io.insert-koin:koin-core:${Versions.koin}") + + // coroutines + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") + + // serialization + api("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") + + // okhttp + api("com.squareup.okhttp3:okhttp:${Versions.okhttp}") + implementation("com.squareup.okhttp3:logging-interceptor:${Versions.okhttp}") + + // retrofit + api("com.squareup.retrofit2:retrofit:${Versions.retrofit}") + api("com.squareup.retrofit2:converter-scalars:${Versions.retrofit}") + api("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:${Versions.retrofitSerialization}") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + val androidMain by getting { + dependencies { + api(projects.localization) + + // Koin + // api("io.insert-koin:koin-android:${Versions.koin}") + // api("io.insert-koin:koin-androidx-compose:${Versions.koin}") // Lifecycle - api("androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}") - api("androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}") - api("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}") - api("androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.lifecycle}") - api("androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.lifecycle}") - api("androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycle}") + // api("androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}") + // api("androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}") + // api("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}") + // api("androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.lifecycle}") + // api("androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.lifecycle}") + // api("androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycle}") // Coil api("io.coil-kt:coil-compose:${Versions.coil}") @@ -62,28 +79,26 @@ kotlin { api("com.google.accompanist:accompanist-pager:${Versions.accompanist}") api("com.google.accompanist:accompanist-pager-indicators:${Versions.accompanist}") api("com.google.accompanist:accompanist-swiperefresh:${Versions.accompanist}") - api("com.google.accompanist:accompanist-navigation-animation:${Versions.accompanist}") - api("com.google.accompanist:accompanist-navigation-material:${Versions.accompanist}") + // api("com.google.accompanist:accompanist-navigation-animation:${Versions.accompanist}") + // api("com.google.accompanist:accompanist-navigation-material:${Versions.accompanist}") api("com.google.accompanist:accompanist-permissions:${Versions.accompanist}") api("com.google.accompanist:accompanist-insets:${Versions.accompanist}") // coroutines - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") + // api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") api("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.Kotlin.coroutines}") // Androidx api("androidx.core:core-ktx:${Versions.Androidx.core}") api("androidx.appcompat:appcompat:${Versions.Androidx.appcompat}") api("androidx.activity:activity-ktx:${Versions.Androidx.activity}") + api("androidx.activity:activity-compose:${Versions.Androidx.activity}") api("androidx.fragment:fragment-ktx:${Versions.Androidx.fragment}") api("androidx.datastore:datastore-preferences:${Versions.datastore}") - api("androidx.navigation:navigation-ui-ktx:${Versions.navigation}") - api("androidx.navigation:navigation-compose:${Versions.navigation}") + // api("androidx.navigation:navigation-ui-ktx:${Versions.navigation}") + // api("androidx.navigation:navigation-compose:${Versions.navigation}") implementation("androidx.biometric:biometric-ktx:${Versions.Androidx.biometric}") - // serialization - api("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") - // sqlite api("androidx.room:room-runtime:${Versions.Androidx.room}") api("androidx.room:room-ktx:${Versions.Androidx.room}") diff --git a/common/gecko/sample/build.gradle.kts b/common/gecko/sample/build.gradle.kts index 47ec85d7..5d41fd81 100644 --- a/common/gecko/sample/build.gradle.kts +++ b/common/gecko/sample/build.gradle.kts @@ -32,7 +32,7 @@ android { } dependencies { - implementation("androidx.activity:activity-compose:${Versions.Androidx.activityCompose}") + implementation("androidx.activity:activity-compose:${Versions.Androidx.activity}") implementation("org.jetbrains.compose.ui:ui:${Versions.compose_jb}") implementation("org.jetbrains.compose.ui:ui-util:${Versions.compose_jb}") implementation("org.jetbrains.compose.foundation:foundation:${Versions.compose_jb}") diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt index 20c0ca25..06d42ad0 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.common -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.manager.ImageLoaderManager import com.dimension.maskbook.common.manager.KeyStoreManager import com.dimension.maskbook.common.util.BiometricAuthenticator @@ -32,12 +30,14 @@ import com.dimension.maskbook.common.viewmodel.SetUpPaymentPasswordViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob -import org.koin.androidx.viewmodel.dsl.viewModel +import moe.tlaster.koin.viewModel +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.RouteBuilder import org.koin.core.qualifier.named import org.koin.dsl.module object CommonSetup : ModuleSetup { - override fun NavGraphBuilder.route(navController: NavController) { + override fun RouteBuilder.route(navController: NavController) { } override fun dependencyInject() = module { diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ModuleSetup.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ModuleSetup.kt index 68e07959..6363435a 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ModuleSetup.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ModuleSetup.kt @@ -20,15 +20,15 @@ */ package com.dimension.maskbook.common -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.RouteBuilder import org.koin.core.module.Module interface ModuleSetup { - fun NavGraphBuilder.route(navController: NavController) + fun RouteBuilder.route(navController: NavController) fun dependencyInject(): Module fun onExtensionReady() {} } -fun ModuleSetup.route(builder: NavGraphBuilder, navController: NavController) = +fun ModuleSetup.route(builder: RouteBuilder, navController: NavController) = builder.route(navController) diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/FlowExt.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/FlowExt.kt index 98f46007..974055aa 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/FlowExt.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/FlowExt.kt @@ -20,33 +20,12 @@ */ package com.dimension.maskbook.common.ext -import android.annotation.SuppressLint -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.lifecycle.flowWithLifecycle import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn -@Composable -fun Flow.observeAsState(initial: T): State { - val lifecycleOwner = LocalLifecycleOwner.current - return remember(this, lifecycleOwner) { - flowWithLifecycle(lifecycleOwner.lifecycle) - }.collectAsState(initial = initial) -} - -@SuppressLint("StateFlowValueCalledInComposition") -@Composable -fun StateFlow.observeAsState(): State { - return observeAsState(initial = value) -} - fun Flow.asStateIn(scope: CoroutineScope, initial: T) = this.stateIn( scope, diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/Json.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/Json.kt index dc1de17c..fbb92aaf 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/Json.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/Json.kt @@ -35,7 +35,7 @@ import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.long -val JSON by lazy { +val JSON: Json by lazy { Json { ignoreUnknownKeys = true isLenient = true diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/KoinExt.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/KoinExt.kt index e194bc3d..e03ac3a2 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/KoinExt.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/KoinExt.kt @@ -22,10 +22,9 @@ package com.dimension.maskbook.common.ext import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.lifecycle.ViewModel -import androidx.navigation.NavController -import org.koin.androidx.viewmodel.ViewModelOwner -import org.koin.androidx.viewmodel.scope.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.viewmodel.ViewModel import org.koin.core.annotation.KoinInternalApi import org.koin.core.context.GlobalContext import org.koin.core.parameter.ParametersDefinition @@ -48,9 +47,9 @@ inline fun NavController.getNestedNavigationViewModel( scope: Scope = GlobalContext.get().scopeRegistry.rootScope, noinline parameters: ParametersDefinition? = null, ): T { - return remember(route, qualifier, parameters) { + return remember(route, qualifier, scope, parameters) { val backStackEntry = getBackStackEntry(route) - val owner = ViewModelOwner.from(backStackEntry, backStackEntry) - scope.getViewModel(qualifier, { owner }, parameters) + ?: throw RuntimeException("not find backStackEntry with route: $route") + backStackEntry.getViewModel(qualifier, scope, parameters) } } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt index 21429d6d..f022ffe5 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt @@ -21,38 +21,76 @@ package com.dimension.maskbook.common.ext import android.net.Uri -import androidx.navigation.NavController -import androidx.navigation.NavOptionsBuilder -import androidx.navigation.navOptions import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.NavOptions +import moe.tlaster.precompose.navigation.PopUpTo -fun NavController.navigate(uri: Uri, builder: NavOptionsBuilder.() -> Unit) { - navigate(uri, navOptions(builder)) +class NavOptionsBuilder internal constructor() { + + var launchSingleTop = false + + private var popUpTo: PopUpTo? = null + + fun popUpTo(route: String, popUpBuilder: PopUpToBuilder.() -> Unit) { + popUpTo = PopUpToBuilder().apply(popUpBuilder).build(route) + } + + fun build(): NavOptions { + return NavOptions( + launchSingleTop = launchSingleTop, + popUpTo = popUpTo, + ) + } +} + +class PopUpToBuilder internal constructor() { + + var inclusive = false + + fun build(route: String): PopUpTo { + return PopUpTo( + route = route, + inclusive = inclusive + ) + } +} + +fun navOptions(builder: NavOptionsBuilder.() -> Unit): NavOptions { + return NavOptionsBuilder().apply(builder).build() +} + +fun NavController.navigate(route: String, builder: NavOptionsBuilder.() -> Unit) { + navigate(route, navOptions(builder)) +} + +fun NavController.navigateUri(uri: Uri, builder: NavOptionsBuilder.() -> Unit = {}) { + navigate(uri.toString(), navOptions(builder)) } -fun NavController.navigateUri(uri: String, builder: NavOptionsBuilder.() -> Unit = {}) { - navigate(Uri.parse(uri), navOptions(builder)) +fun NavController.navigateUri(uri: Uri, navOptions: NavOptions) { + navigate(uri.toString(), navOptions) } fun NavController.navigateWithPopSelf(route: String) { navigate(route) { - currentDestination?.id?.let { popId -> - popUpTo(popId) { inclusive = true } + currentDestination?.route?.let { popRoute -> + popUpTo(popRoute.route) { inclusive = true } } } } -fun NavController.navigateUriWithPopSelf(uri: String) { - navigate(Uri.parse(uri)) { - currentDestination?.id?.let { popId -> - popUpTo(popId) { inclusive = true } +fun NavController.navigateUriWithPopSelf(uri: Uri) { + navigateUri(uri) { + currentDestination?.route?.let { popRoute -> + popUpTo(popRoute.route) { inclusive = true } } } } fun NavController.navigateToExtension(site: String? = null) { - navigateUri(Deeplinks.WebContent(site)) { + navigate(Deeplinks.WebContent(site)) { launchSingleTop = true popUpTo(CommonRoute.Main.Home.path) { inclusive = true diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerResult.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerResult.kt index 015f724b..f6fb973b 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerResult.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerResult.kt @@ -20,11 +20,6 @@ */ package com.dimension.maskbook.common.ext -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelStoreOwner -import androidx.lifecycle.viewModelScope -import androidx.navigation.NavController import com.dimension.maskbook.common.model.ResultEvent import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -34,6 +29,11 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.launch +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner +import moe.tlaster.precompose.viewmodel.getViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope /** * use for navigate result, use like: @@ -78,15 +78,4 @@ private class EventResultViewModel : ViewModel() { } private val ViewModelStoreOwner.eventResultViewModel: EventResultViewModel - get() { - return ViewModelProvider( - this, EventResultViewModelFactory - )[EventResultViewModel::class.java] - } - -private object EventResultViewModelFactory : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - @Suppress("UNCHECKED_CAST") - return EventResultViewModel() as T - } -} + get() = viewModelStore.getViewModel { EventResultViewModel() } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/AnimatedNavHost.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/AnimatedNavHost.kt new file mode 100644 index 00000000..4ab656df --- /dev/null +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/AnimatedNavHost.kt @@ -0,0 +1,19 @@ +package com.dimension.maskbook.common.route + +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.NavHost +import moe.tlaster.precompose.navigation.RouteBuilder + +@Composable +fun AnimatedNavHost( + navController: NavController, + initialRoute: String, + builder: RouteBuilder.() -> Unit, +) { + NavHost( + navController = navController, + initialRoute = initialRoute, + builder = builder, + ) +} \ No newline at end of file diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt index e4151d84..a38fe1c0 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt @@ -20,21 +20,11 @@ */ package com.dimension.maskbook.common.route -import androidx.compose.animation.AnimatedVisibilityScope -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.runtime.Composable -import androidx.navigation.NamedNavArgument -import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavDeepLink -import androidx.navigation.NavGraphBuilder -import com.google.accompanist.navigation.animation.composable -import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi -import com.google.accompanist.navigation.material.bottomSheet +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.RouteBuilder -const val navigationComposeDialogPackage = "androidx.navigation.compose" +const val navigationComposeDialogPackage = "com.dimension.maskbook.common.route" const val navigationComposeDialog = "dialog" const val navigationComposeAnimComposablePackage = "com.dimension.maskbook.common.route" @@ -46,55 +36,74 @@ const val navigationComposeModalComposable = "modalComposable" const val navigationComposeBottomSheetPackage = "com.dimension.maskbook.common.route" const val navigationComposeBottomSheet = "bottomSheet" -@OptIn(ExperimentalAnimationApi::class) -fun NavGraphBuilder.composable( +fun RouteBuilder.composable( route: String, - arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit + // arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit ) { - composable( + scene( route = route, - arguments = arguments, + // arguments = arguments, deepLinks = deepLinks, content = content, ) } -@OptIn(ExperimentalAnimationApi::class) -fun NavGraphBuilder.modalComposable( +fun RouteBuilder.modalComposable( route: String, - arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit + // arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit ) { - composable( + scene( route = route, - arguments = arguments, + // arguments = arguments, deepLinks = deepLinks, content = content, - enterTransition = { - slideInVertically { it } - }, - exitTransition = null, - popEnterTransition = null, - popExitTransition = { - slideOutVertically { it } - }, + // enterTransition = { + // slideInVertically { it } + // }, + // exitTransition = null, + // popEnterTransition = null, + // popExitTransition = { + // slideOutVertically { it } + // }, ) } -@OptIn(ExperimentalMaterialNavigationApi::class) -fun NavGraphBuilder.bottomSheet( +fun RouteBuilder.bottomSheet( route: String, - arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable ColumnScope.(backstackEntry: NavBackStackEntry) -> Unit + // arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit ) { - bottomSheet( + scene( route = route, - arguments = arguments, + // arguments = arguments, deepLinks = deepLinks, content = content ) } + +fun RouteBuilder.dialog( + route: String, + // arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + dialog( + route = route, + // arguments = arguments, + // deepLinks = deepLinks, + content = content, + ) +} + +fun RouteBuilder.navigation( + route: String, + startDestination: String, + content: @Composable RouteBuilder.(BackStackEntry) -> Unit +) { + // TODO +} diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/notification/InAppNotification.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/notification/InAppNotification.kt index 0227f619..144692f9 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/notification/InAppNotification.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/notification/InAppNotification.kt @@ -22,8 +22,8 @@ package com.dimension.maskbook.common.ui.notification import androidx.annotation.StringRes import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.res.stringResource -import com.dimension.maskbook.common.ext.observeAsState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow @@ -103,6 +103,6 @@ class InAppNotification { } @Composable - fun observeAsState(initial: Event? = null) = - source.observeAsState(initial = initial) + fun collectAsState(initial: Event? = null) = + source.collectAsState(initial = initial) } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/SetupPaymentPassword.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/SetupPaymentPassword.kt index 3679dea4..d095e010 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/SetupPaymentPassword.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/SetupPaymentPassword.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -40,16 +41,16 @@ import com.dimension.maskbook.common.ui.widget.MaskModal import com.dimension.maskbook.common.ui.widget.MaskPasswordInputField import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.common.viewmodel.SetUpPaymentPasswordViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @Composable fun SetUpPaymentPassword( onNext: () -> Unit, ) { val viewModel: SetUpPaymentPasswordViewModel = getViewModel() - val newPassword by viewModel.newPassword.observeAsState(initial = "") - val newPasswordConfirm by viewModel.newPasswordConfirm.observeAsState(initial = "") - val canConfirm by viewModel.canConfirm.observeAsState(initial = false) + val newPassword by viewModel.newPassword.collectAsState(initial = "") + val newPasswordConfirm by viewModel.newPasswordConfirm.collectAsState(initial = "") + val canConfirm by viewModel.canConfirm.collectAsState(initial = false) MaskModal( title = { Text( diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/VerifyMnemonicWordsScene.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/VerifyMnemonicWordsScene.kt index 2f30adc9..be9c695a 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/VerifyMnemonicWordsScene.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/VerifyMnemonicWordsScene.kt @@ -20,7 +20,6 @@ */ package com.dimension.maskbook.common.ui.scene -import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -72,6 +71,7 @@ import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.calculateCurrentOffsetForPage import com.google.accompanist.pager.rememberPagerState +import moe.tlaster.precompose.navigation.BackHandler import kotlin.math.absoluteValue @OptIn(ExperimentalPagerApi::class, ExperimentalMaterialApi::class) diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/tab/TabScreen.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/tab/TabScreen.kt index 131ac529..a73d1008 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/tab/TabScreen.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/tab/TabScreen.kt @@ -21,7 +21,7 @@ package com.dimension.maskbook.common.ui.tab import androidx.compose.runtime.Composable -import androidx.navigation.NavController +import moe.tlaster.precompose.navigation.NavController interface TabScreen { diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/theme/IsDarkTheme.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/theme/IsDarkTheme.kt index f13c3161..aaa8124c 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/theme/IsDarkTheme.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/theme/IsDarkTheme.kt @@ -24,12 +24,12 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.staticCompositionLocalOf -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.setting.export.SettingServices import com.dimension.maskbook.setting.export.model.Appearance -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get internal val LocalIsDarkTheme = staticCompositionLocalOf { false } @@ -41,7 +41,7 @@ val MaterialTheme.isDarkTheme: Boolean @Composable internal fun isDarkTheme(): Boolean { val repo = get() - val appearance by repo.appearance.observeAsState(initial = Appearance.default) + val appearance by repo.appearance.collectAsState(initial = Appearance.default) return when (appearance) { Appearance.default -> isSystemInDarkTheme() Appearance.light -> false diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/theme/MoreColors.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/theme/MoreColors.kt index 6376a4bc..628bd169 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/theme/MoreColors.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/theme/MoreColors.kt @@ -36,9 +36,9 @@ class MoreColors( caption: Color, onCaption: Color, ) { - var caption by mutableStateOf(caption, structuralEqualityPolicy()) + var caption: Color by mutableStateOf(caption, structuralEqualityPolicy()) internal set - var onCaption by mutableStateOf(onCaption, structuralEqualityPolicy()) + var onCaption: Color by mutableStateOf(onCaption, structuralEqualityPolicy()) internal set } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskInAppNotification.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskInAppNotification.kt index 42110ca4..206a7a30 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskInAppNotification.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskInAppNotification.kt @@ -41,7 +41,7 @@ fun MaskInAppNotification( ) { val inAppNotification = LocalInAppNotification.current Log.d("MaskInAppNotification", "inAppNotification: $inAppNotification") - val notification by inAppNotification.observeAsState(null) + val notification by inAppNotification.collectAsState(null) val event = notification?.getContentIfNotHandled() val message = event?.getMessage() val actionMessage = event?.let { diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/WalletTokenImage.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/WalletTokenImage.kt index 98f1ed9e..b7041ab4 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/WalletTokenImage.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/WalletTokenImage.kt @@ -29,13 +29,13 @@ import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp -import coil.compose.ImagePainter @Composable fun WalletTokenImage( - painter: ImagePainter, - chainPainter: ImagePainter, + painter: Painter, + chainPainter: Painter, ) { Box { Image( diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BaseMnemonicPhraseViewModel.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BaseMnemonicPhraseViewModel.kt index 5dcef857..c1767ac0 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BaseMnemonicPhraseViewModel.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BaseMnemonicPhraseViewModel.kt @@ -20,14 +20,14 @@ */ package com.dimension.maskbook.common.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.BuildConfig import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.model.MnemonicWord import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope abstract class BaseMnemonicPhraseViewModel : ViewModel() { protected val _words = MutableStateFlow(emptyList()) diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BiometricEnableViewModel.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BiometricEnableViewModel.kt index 287c8989..8342597f 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BiometricEnableViewModel.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BiometricEnableViewModel.kt @@ -21,9 +21,9 @@ package com.dimension.maskbook.common.viewmodel import android.content.Context -import androidx.lifecycle.ViewModel import com.dimension.maskbook.common.util.BiometricAuthenticator import com.dimension.maskbook.setting.export.SettingServices +import moe.tlaster.precompose.viewmodel.ViewModel class BiometricEnableViewModel( private val biometricAuthenticator: BiometricAuthenticator, diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BiometricViewModel.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BiometricViewModel.kt index 93956e26..5acb2f90 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BiometricViewModel.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/BiometricViewModel.kt @@ -21,8 +21,6 @@ package com.dimension.maskbook.common.viewmodel import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.R import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.util.BiometricAuthenticator @@ -31,6 +29,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope open class BiometricViewModel( private val biometricAuthenticator: BiometricAuthenticator, diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/SetUpPaymentPasswordViewModel.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/SetUpPaymentPasswordViewModel.kt index c74b3c16..59d2af14 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/SetUpPaymentPasswordViewModel.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/viewmodel/SetUpPaymentPasswordViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.common.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.SettingServices import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class SetUpPaymentPasswordViewModel( private val repository: SettingServices, diff --git a/common/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeActivity.kt b/common/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeActivity.kt new file mode 100644 index 00000000..6abba122 --- /dev/null +++ b/common/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeActivity.kt @@ -0,0 +1,142 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.lifecycle + +import android.view.ViewGroup +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionContext +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.ViewTreeLifecycleOwner +import androidx.savedstate.SavedStateRegistryOwner +import androidx.savedstate.ViewTreeSavedStateRegistryOwner +import moe.tlaster.precompose.ui.BackDispatcher +import moe.tlaster.precompose.ui.BackDispatcherOwner +import moe.tlaster.precompose.ui.LocalBackDispatcherOwner +import moe.tlaster.precompose.ui.LocalLifecycleOwner +import moe.tlaster.precompose.ui.LocalViewModelStoreOwner +import moe.tlaster.precompose.viewmodel.ViewModelStore +import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner + +open class PreComposeActivity : + FragmentActivity(), + LifecycleOwner, + ViewModelStoreOwner, + androidx.lifecycle.LifecycleOwner, + SavedStateRegistryOwner, + BackDispatcherOwner, + androidx.lifecycle.LifecycleObserver { + override val lifecycle by lazy { + LifecycleRegistry() + } + + override val viewModelStore by lazy { + ViewModelStore() + } + + override fun onResume() { + super.onResume() + lifecycle.currentState = Lifecycle.State.Active + } + + override fun onPause() { + super.onPause() + lifecycle.currentState = Lifecycle.State.InActive + } + + override fun onDestroy() { + super.onDestroy() + lifecycle.currentState = Lifecycle.State.Destroyed + } + + override val backDispatcher by lazy { + BackDispatcher() + } + + override fun onBackPressed() { + if (!backDispatcher.onBackPress()) { + super.onBackPressed() + } + } +} + +fun PreComposeActivity.setContent( + parent: CompositionContext? = null, + content: @Composable () -> Unit +) { + val existingComposeView = window.decorView + .findViewById(android.R.id.content) + .getChildAt(0) as? ComposeView + + if (existingComposeView != null) with(existingComposeView) { + setParentCompositionContext(parent) + setContent { + ContentInternal(content) + } + } else ComposeView(this).apply { + // Set content and parent **before** setContentView + // to have ComposeView create the composition on attach + setParentCompositionContext(parent) + setContent { + ContentInternal(content) + } + // Set the view tree owners before setting the content view so that the inflation process + // and attach listeners will see them already present + setOwners() + setContentView(this, DefaultActivityContentLayoutParams) + } +} + +private fun PreComposeActivity.setOwners() { + val decorView = window.decorView + if (ViewTreeLifecycleOwner.get(decorView) == null) { + ViewTreeLifecycleOwner.set(decorView, this) + } + if (ViewTreeSavedStateRegistryOwner.get(decorView) == null) { + ViewTreeSavedStateRegistryOwner.set(decorView, this) + } +} + +@Composable +private fun PreComposeActivity.ContentInternal(content: @Composable () -> Unit) { + ProvideAndroidCompositionLocals { + content.invoke() + } +} + +@Composable +private fun PreComposeActivity.ProvideAndroidCompositionLocals( + content: @Composable () -> Unit, +) { + CompositionLocalProvider( + LocalLifecycleOwner provides this, + LocalViewModelStoreOwner provides this, + LocalBackDispatcherOwner provides this, + ) { + content.invoke() + } +} + +private val DefaultActivityContentLayoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/koin/ViewModel.kt b/common/src/commonMain/kotlin/moe/tlaster/koin/ViewModel.kt new file mode 100644 index 00000000..6267febc --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/koin/ViewModel.kt @@ -0,0 +1,43 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.koin + +import moe.tlaster.precompose.viewmodel.ViewModel +import org.koin.core.annotation.KoinReflectAPI +import org.koin.core.definition.Definition +import org.koin.core.instance.InstanceFactory +import org.koin.core.instance.newInstance +import org.koin.core.module.Module +import org.koin.core.qualifier.Qualifier + +inline fun Module.viewModel( + qualifier: Qualifier? = null, + noinline definition: Definition +): Pair> { + return factory(qualifier, definition) +} + +@KoinReflectAPI +inline fun Module.viewModel( + qualifier: Qualifier? = null +): Pair> { + return factory(qualifier) { newInstance(it) } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/koin/compose/ComposeExt.kt b/common/src/commonMain/kotlin/moe/tlaster/koin/compose/ComposeExt.kt new file mode 100644 index 00000000..4da83723 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/koin/compose/ComposeExt.kt @@ -0,0 +1,48 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.koin.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import org.koin.core.Koin +import org.koin.core.parameter.ParametersDefinition +import org.koin.core.qualifier.Qualifier +import org.koin.mp.KoinPlatformTools + +@Composable +inline fun getRemember( + qualifier: Qualifier? = null, + noinline parameters: ParametersDefinition? = null, +): T = remember(qualifier, parameters) { + get(qualifier, parameters) +} + +@Composable +fun getKoinRemember(): Koin = remember { + getKoin() +} + +inline fun get( + qualifier: Qualifier? = null, + noinline parameters: ParametersDefinition? = null, +): T = KoinPlatformTools.defaultContext().get().get(qualifier, parameters) + +fun getKoin(): Koin = KoinPlatformTools.defaultContext().get() diff --git a/common/src/commonMain/kotlin/moe/tlaster/koin/compose/ViewModelExt.kt b/common/src/commonMain/kotlin/moe/tlaster/koin/compose/ViewModelExt.kt new file mode 100644 index 00000000..a0d9c3ad --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/koin/compose/ViewModelExt.kt @@ -0,0 +1,90 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.koin.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import moe.tlaster.precompose.ui.LocalViewModelStoreOwner +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner +import moe.tlaster.precompose.viewmodel.getViewModel +import org.koin.core.annotation.KoinInternalApi +import org.koin.core.context.GlobalContext +import org.koin.core.parameter.ParametersDefinition +import org.koin.core.qualifier.Qualifier +import org.koin.core.scope.Scope +import kotlin.reflect.KClass + +@OptIn(KoinInternalApi::class) +@Composable +inline fun getViewModel( + qualifier: Qualifier? = null, + owner: ViewModelStoreOwner = LocalViewModelStoreOwner.current, + scope: Scope = GlobalContext.get().scopeRegistry.rootScope, + noinline parameters: ParametersDefinition? = null, +): T { + return remember(qualifier, scope, parameters) { + owner.getViewModel(qualifier, scope, parameters) + } +} + +// inline fun ViewModelStoreOwner.viewModel( +// qualifier: Qualifier? = null, +// mode: LazyThreadSafetyMode = LazyThreadSafetyMode.SYNCHRONIZED, +// noinline parameters: ParametersDefinition? = null, +// ): Lazy { +// return lazy(mode) { +// getViewModel(qualifier, parameters) +// } +// } +// +// fun ViewModelStoreOwner.viewModel( +// qualifier: Qualifier? = null, +// clazz: KClass, +// mode: LazyThreadSafetyMode = LazyThreadSafetyMode.SYNCHRONIZED, +// parameters: ParametersDefinition? = null, +// ): Lazy { +// return lazy(mode) { getViewModel(qualifier, clazz, parameters) } +// } + +@OptIn(KoinInternalApi::class) +inline fun ViewModelStoreOwner.getViewModel( + qualifier: Qualifier? = null, + scope: Scope = GlobalContext.get().scopeRegistry.rootScope, + noinline parameters: ParametersDefinition? = null, +): T { + return getViewModel(qualifier, T::class, scope, parameters) +} + +@OptIn(KoinInternalApi::class) +fun ViewModelStoreOwner.getViewModel( + qualifier: Qualifier? = null, + clazz: KClass, + scope: Scope = GlobalContext.get().scopeRegistry.rootScope, + parameters: ParametersDefinition? = null, +): T { + return this.viewModelStore.getViewModel( + key = qualifier?.value ?: (clazz.toString() + parameters?.invoke()), + clazz = clazz + ) { + scope.get(clazz, qualifier, parameters) + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/Lifecycle.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/Lifecycle.kt new file mode 100644 index 00000000..f76381a7 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/Lifecycle.kt @@ -0,0 +1,35 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.lifecycle + +interface Lifecycle { + enum class State { + Initialized, + Active, + InActive, + Destroyed, + } + + val currentState: State + fun removeObserver(observer: LifecycleObserver) + fun addObserver(observer: LifecycleObserver) + fun hasObserver(): Boolean +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleObserver.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleObserver.kt new file mode 100644 index 00000000..aee3ce24 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleObserver.kt @@ -0,0 +1,25 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.lifecycle + +interface LifecycleObserver { + fun onStateChanged(state: Lifecycle.State) +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleOwner.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleOwner.kt new file mode 100644 index 00000000..542604a1 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleOwner.kt @@ -0,0 +1,25 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.lifecycle + +interface LifecycleOwner { + val lifecycle: Lifecycle +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleRegistry.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleRegistry.kt new file mode 100644 index 00000000..69a4fd53 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleRegistry.kt @@ -0,0 +1,52 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.lifecycle + +class LifecycleRegistry : Lifecycle { + private val observers = arrayListOf() + override var currentState: Lifecycle.State = Lifecycle.State.Initialized + set(value) { + if (field == Lifecycle.State.Destroyed || value == Lifecycle.State.Initialized) { + return + } + field = value + dispatchState(value) + } + + private fun dispatchState(value: Lifecycle.State) { + observers.toMutableList().forEach { + it.onStateChanged(value) + } + } + + override fun removeObserver(observer: LifecycleObserver) { + observers.remove(observer) + } + + override fun addObserver(observer: LifecycleObserver) { + observers.add(observer) + observer.onStateChanged(currentState) + } + + override fun hasObserver(): Boolean { + return observers.isNotEmpty() + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/RepeatOnLifecycle.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/RepeatOnLifecycle.kt new file mode 100644 index 00000000..4ef9fb1e --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/RepeatOnLifecycle.kt @@ -0,0 +1,162 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.lifecycle + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import kotlin.coroutines.resume + +/** + * Runs the given [block] in a new coroutine when `this` [Lifecycle] is at least at [state] and + * suspends the execution until `this` [Lifecycle] is [Lifecycle.State.Destroyed]. + * + * The [block] will cancel and re-launch as the lifecycle moves in and out of the target state. + * + * ``` + * class MyActivity : AppCompatActivity() { + * override fun onCreate(savedInstanceState: Bundle?) { + * /* ... */ + * // Runs the block of code in a coroutine when the lifecycle is at least STARTED. + * // The coroutine will be cancelled when the ON_STOP event happens and will + * // restart executing if the lifecycle receives the ON_START event again. + * lifecycleScope.launch { + * lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + * uiStateFlow.collect { uiState -> + * updateUi(uiState) + * } + * } + * } + * } + * } + * ``` + * + * The best practice is to call this function when the lifecycle is initialized. For + * example, `onCreate` in an Activity, or `onViewCreated` in a Fragment. Otherwise, multiple + * repeating coroutines doing the same could be created and be executed at the same time. + * + * Repeated invocations of `block` will run serially, that is they will always wait for the + * previous invocation to fully finish before re-starting execution as the state moves in and out + * of the required state. + * + * Warning: [Lifecycle.State.Initialized] is not allowed in this API. Passing it as a + * parameter will throw an [IllegalArgumentException]. + * + * @param state [Lifecycle.State] in which `block` runs in a new coroutine. That coroutine + * will cancel if the lifecycle falls below that state, and will restart if it's in that state + * again. + */ +suspend fun Lifecycle.repeatOnLifecycle( + block: suspend CoroutineScope.() -> Unit +) { + if (currentState === Lifecycle.State.Destroyed) { + return + } + + // This scope is required to preserve context before we move to Dispatchers.Main + coroutineScope { + withContext(Dispatchers.Main.immediate) { + // Check the current state of the lifecycle as the previous check is not guaranteed + // to be done on the main thread. + if (currentState === Lifecycle.State.Destroyed) return@withContext + + // Instance of the running repeating coroutine + var launchedJob: Job? = null + + // Registered observer + var observer: LifecycleObserver? = null + try { + // Suspend the coroutine until the lifecycle is destroyed or + // the coroutine is cancelled + suspendCancellableCoroutine { cont -> + // Lifecycle observers that executes `block` when the lifecycle reaches certain state, and + // cancels when it falls below that state. + val mutex = Mutex() + observer = object : LifecycleObserver { + override fun onStateChanged(state: Lifecycle.State) { + when (state) { + Lifecycle.State.Initialized -> Unit + Lifecycle.State.Active -> { + launchedJob = this@coroutineScope.launch { + // Mutex makes invocations run serially, + // coroutineScope ensures all child coroutines finish + mutex.withLock { + coroutineScope { + block() + } + } + } + } + Lifecycle.State.InActive -> { + launchedJob?.cancel() + launchedJob = null + } + Lifecycle.State.Destroyed -> { + cont.resume(Unit) + } + } + } + } + this@repeatOnLifecycle.addObserver(observer as LifecycleObserver) + } + } finally { + launchedJob?.cancel() + observer?.let { + this@repeatOnLifecycle.removeObserver(it) + } + } + } + } +} + +/** + * [LifecycleOwner]'s extension function for [Lifecycle.repeatOnLifecycle] to allow an easier + * call to the API from LifecycleOwners such as Activities and Fragments. + * + * ``` + * class MyActivity : AppCompatActivity() { + * override fun onCreate(savedInstanceState: Bundle?) { + * /* ... */ + * // Runs the block of code in a coroutine when the lifecycle is at least STARTED. + * // The coroutine will be cancelled when the ON_STOP event happens and will + * // restart executing if the lifecycle receives the ON_START event again. + * lifecycleScope.launch { + * repeatOnLifecycle(Lifecycle.State.STARTED) { + * uiStateFlow.collect { uiState -> + * updateUi(uiState) + * } + * } + * } + * } + * } + * ``` + * + * @see Lifecycle.repeatOnLifecycle + */ +suspend fun LifecycleOwner.repeatOnLifecycle( + block: suspend CoroutineScope.() -> Unit +): Unit = lifecycle.repeatOnLifecycle(block) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt new file mode 100644 index 00000000..cf4bcf6d --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt @@ -0,0 +1,60 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import moe.tlaster.precompose.ui.BackHandler +import moe.tlaster.precompose.ui.LocalBackDispatcherOwner +import moe.tlaster.precompose.ui.LocalLifecycleOwner + +@Composable +fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) { + // Safely update the current `onBack` lambda when a new one is provided + val currentOnBack by rememberUpdatedState(onBack) + // Remember in Composition a back callback that calls the `onBack` lambda + val backCallback = remember { + object : BackHandler { + override fun handleBackPress(): Boolean { + if (!enabled) return false + currentOnBack.invoke() + return true + } + } + } + + val backDispatcher = checkNotNull(LocalBackDispatcherOwner.current) { + "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" + }.backDispatcher + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner, backDispatcher) { + // Add callback to the backDispatcher + // backDispatcher.addCallback(lifecycleOwner, backCallback) + backDispatcher.register(backCallback) + // When the effect leaves the Composition, remove the callback + onDispose { + backDispatcher.unregister(backCallback) + } + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt new file mode 100644 index 00000000..3d19a513 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt @@ -0,0 +1,94 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import moe.tlaster.precompose.lifecycle.Lifecycle +import moe.tlaster.precompose.lifecycle.LifecycleOwner +import moe.tlaster.precompose.lifecycle.LifecycleRegistry +import moe.tlaster.precompose.navigation.route.ComposeRoute +import moe.tlaster.precompose.viewmodel.ViewModelStore +import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner + +class BackStackEntry internal constructor( + val id: Long, + val route: ComposeRoute, + val pathMap: Map, + val queryString: QueryString? = null, + internal val viewModel: NavControllerViewModel, +) : ViewModelStoreOwner, LifecycleOwner { + private var destroyAfterTransition = false + + override val viewModelStore: ViewModelStore + get() = viewModel.get(id = id) + + private val lifecycleRegistry: LifecycleRegistry by lazy { + LifecycleRegistry() + } + + override val lifecycle: Lifecycle + get() = lifecycleRegistry + + fun active() { + lifecycleRegistry.currentState = Lifecycle.State.Active + } + + fun inActive() { + lifecycleRegistry.currentState = Lifecycle.State.InActive + if (destroyAfterTransition) { + destroy() + } + } + + fun destroy() { + if (lifecycleRegistry.currentState != Lifecycle.State.InActive) { + destroyAfterTransition = true + } else { + lifecycleRegistry.currentState = Lifecycle.State.Destroyed + viewModelStore.clear() + } + } + + inline fun path(path: String, default: T? = null): T? { + val value = pathMap[path] ?: return default + return convertValue(value) + } + + inline fun query(name: String, default: T? = null): T? { + return queryString?.query(name, default) + } + + inline fun queryList(name: String): List { + val value = queryString?.map?.get(name) ?: return emptyList() + return value.map { convertValue(it) } + } +} + +inline fun convertValue(value: String): T? { + return when (T::class) { + Int::class -> value.toIntOrNull() + Long::class -> value.toLongOrNull() + String::class -> value + Boolean::class -> value.toBooleanStrictOrNull() + Float::class -> value.toFloatOrNull() + Double::class -> value.toDoubleOrNull() + else -> throw NotImplementedError() + } as T +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavControllerViewModel.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavControllerViewModel.kt new file mode 100644 index 00000000..b2c6d03e --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavControllerViewModel.kt @@ -0,0 +1,54 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.ViewModelStore +import moe.tlaster.precompose.viewmodel.getViewModel + +internal class NavControllerViewModel : ViewModel() { + private val viewModelStores = hashMapOf() + + fun clear(id: Long) { + viewModelStores.remove(id)?.clear() + } + + operator fun get(id: Long): ViewModelStore { + return viewModelStores.getOrPut(id) { + ViewModelStore() + } + } + + override fun onCleared() { + viewModelStores.forEach { + it.value.clear() + } + viewModelStores.clear() + } + + companion object { + fun create(viewModelStore: ViewModelStore): NavControllerViewModel { + return viewModelStore.getViewModel { + NavControllerViewModel() + } + } + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt new file mode 100644 index 00000000..353a9356 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt @@ -0,0 +1,127 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveableStateHolder +import moe.tlaster.precompose.navigation.transition.AnimatedDialogRoute +import moe.tlaster.precompose.navigation.transition.AnimatedRoute +import moe.tlaster.precompose.navigation.transition.DialogTransition +import moe.tlaster.precompose.navigation.transition.NavTransition +import moe.tlaster.precompose.ui.LocalBackDispatcherOwner +import moe.tlaster.precompose.ui.LocalLifecycleOwner +import moe.tlaster.precompose.ui.LocalViewModelStoreOwner + +/** + * Provides in place in the Compose hierarchy for self contained navigation to occur. + * + * Once this is called, any Composable within the given [RouteBuilder] can be navigated to from + * the provided [RouteBuilder]. + * + * The builder passed into this method is [remember]ed. This means that for this NavHost, the + * contents of the builder cannot be changed. + * + * @param navController the Navigator for this host + * @param initialRoute the route for the start destination + * @param navTransition navigation transition for the scenes in this [NavHost] + * @param builder the builder used to construct the graph + */ +@Composable +fun NavHost( + navController: NavController, + initialRoute: String, + navTransition: NavTransition = remember { NavTransition() }, + dialogTransition: DialogTransition = remember { DialogTransition() }, + builder: RouteBuilder.() -> Unit, +) { + val stateHolder = rememberSaveableStateHolder() + val manager = remember { + val graph = RouteBuilder(initialRoute = initialRoute).apply(builder).build() + RouteStackManager(stateHolder, graph).apply { + navController.stackManager = this + } + } + + val lifecycleOwner = checkNotNull(LocalLifecycleOwner.current) { + "NavHost requires a LifecycleOwner to be provided via LocalLifecycleOwner" + } + val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "NavHost requires a ViewModelStoreOwner to be provided via LocalViewModelStoreOwner" + } + val backDispatcher = LocalBackDispatcherOwner.current?.backDispatcher + DisposableEffect(manager, lifecycleOwner, viewModelStoreOwner, backDispatcher) { + manager.lifeCycleOwner = lifecycleOwner + manager.setViewModelStore(viewModelStoreOwner.viewModelStore) + manager.backDispatcher = backDispatcher + onDispose { + manager.lifeCycleOwner = null + } + } + + LaunchedEffect(manager, initialRoute) { + manager.navigateInitial(initialRoute) + } + val currentStack = manager.currentStack + if (currentStack != null) { + AnimatedRoute( + currentStack, + navTransition = navTransition, + manager = manager, + ) { routeStack -> + LaunchedEffect(routeStack) { + routeStack.onActive() + } + DisposableEffect(routeStack) { + onDispose { + routeStack.onInActive() + } + } + val currentEntry = routeStack.currentEntry + if (currentEntry != null) { + LaunchedEffect(currentEntry) { + currentEntry.active() + } + DisposableEffect(currentEntry) { + onDispose { + currentEntry.inActive() + } + } + } + AnimatedDialogRoute( + stack = routeStack, + dialogTransition = dialogTransition, + ) { + stateHolder.SaveableStateProvider(it.id) { + CompositionLocalProvider( + LocalViewModelStoreOwner provides it, + LocalLifecycleOwner provides it, + ) { + it.route.content.invoke(it) + } + } + } + } + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavOptions.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavOptions.kt new file mode 100644 index 00000000..4298c48b --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavOptions.kt @@ -0,0 +1,38 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +/** + * [NavOptions] stores special options for navigate actions + */ +data class NavOptions( + /** + * Whether this navigation action should launch as single-top (i.e., there will be at most + * one copy of a given destination on the top of the back stack). + */ + val launchSingleTop: Boolean = false, + /** + * The destination to pop up to before navigating. When set, all non-matching destinations + * should be popped from the back stack. + * @see [PopUpTo] + */ + val popUpTo: PopUpTo? = null, +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt new file mode 100644 index 00000000..9a5514b2 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -0,0 +1,118 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow + +/** + * Creates a [Navigator] that controls the [NavHost]. + * + * @see NavHost + */ +@Composable +fun rememberNavController(): NavController { + return remember { NavController() } +} + +class NavController { + // FIXME: 2021/4/1 Temp workaround for deeplink + private var pendingNavigation: String? = null + internal var stackManager: RouteStackManager? = null + set(value) { + field = value + value?.let { + pendingNavigation?.let { it1 -> it.navigate(it1) } + } + } + + val backQueue: List + get() = stackManager?.backStacks?.mapNotNull { it.currentEntry } ?: emptyList() + + val currentDestination: BackStackEntry? + get() = stackManager?.currentEntry + + fun getBackStackEntry(route: String): BackStackEntry? { + return stackManager?.getBackStackEntry(route) + } + + /** + * Navigate to a route in the current RouteGraph. + * + * @param route route for the destination + * @param options navigation options for the destination + */ + fun navigate(route: String, options: NavOptions? = null) { + stackManager?.navigate(route, options) ?: run { + pendingNavigation = route + } + } + + suspend fun navigateForResult(route: String, options: NavOptions? = null): Any? { + stackManager?.navigate(route, options) ?: run { + pendingNavigation = route + return null + } + val currentEntry = stackManager?.currentEntry ?: return null + return stackManager?.waitingForResult(currentEntry) + } + + /** + * Attempts to navigate up in the navigation hierarchy. Suitable for when the + * user presses the "Up" button marked with a left (or start)-facing arrow in the upper left + * (or starting) corner of the app UI. + */ + fun goBack() { + stackManager?.goBack() + } + + fun goBackWith(result: Any? = null) { + stackManager?.goBack(result) + } + + /** + * Compatibility layer for Jetpack Navigation + */ + fun popBackStack() { + goBack() + } + + /** + * Check if navigator can navigate up + */ + val canGoBack: Boolean + get() = stackManager?.canGoBack ?: false +} + +@Composable +fun NavController.currentBackStackEntryAsState(): State { + val currentNavBackStackEntry = remember { mutableStateOf(stackManager?.currentEntry) } + LaunchedEffect(this) { + snapshotFlow { stackManager?.currentEntry }.collect { + currentNavBackStackEntry.value = it + } + } + return currentNavBackStackEntry +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/PopUpTo.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/PopUpTo.kt new file mode 100644 index 00000000..19eb69f0 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/PopUpTo.kt @@ -0,0 +1,32 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +data class PopUpTo( + /** + * The `popUpTo` destination, if it's an empty string will clear all backstack + */ + val route: String, + /** + * Whether the `popUpTo` destination should be popped from the back stack. + */ + val inclusive: Boolean = false +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.kt new file mode 100644 index 00000000..e4b1cd2b --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.kt @@ -0,0 +1,54 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +data class QueryString( + private val rawInput: String, +) { + val map by lazy { + rawInput + .split("?") + .lastOrNull() + .let { + it ?: "" + } + .split("&") + .asSequence() + .map { it.split("=") } + .filter { !it.firstOrNull().isNullOrEmpty() } + .filter { it.size in 1..2 } + .map { it[0] to it.elementAtOrNull(1) } + .groupBy { it.first } + .map { it.key to it.value.mapNotNull { it.second.takeIf { !it.isNullOrEmpty() } } } + .toList() + .toMap() + } +} + +inline fun QueryString.query(name: String, default: T? = null): T? { + val value = map[name]?.firstOrNull() ?: return default + return convertValue(value) +} + +inline fun QueryString.queryList(name: String): List { + val value = map[name] ?: return emptyList() + return value.map { convertValue(it) } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt new file mode 100644 index 00000000..39d605f4 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt @@ -0,0 +1,85 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.route.DialogRoute +import moe.tlaster.precompose.navigation.route.Route +import moe.tlaster.precompose.navigation.route.SceneRoute +import moe.tlaster.precompose.navigation.transition.NavTransition + +class RouteBuilder( + private val initialRoute: String, +) { + private val route = arrayListOf() + + /** + * Add the scene [Composable] to the [RouteBuilder] + * @param route route for the destination + * @param navTransition navigation transition for current scene + * @param content composable for the destination + */ + fun scene( + route: String, + deepLinks: List = emptyList(), + navTransition: NavTransition? = null, + content: @Composable (BackStackEntry) -> Unit, + ) { + this.route += SceneRoute( + route = route, + navTransition = navTransition, + deepLinks = deepLinks, + content = content, + ) + } + /** + * Add the scene [Composable] to the [RouteBuilder], which will show over the scene + * @param route route for the destination + * @param content composable for the destination + */ + fun dialog( + route: String, + content: @Composable (BackStackEntry) -> Unit, + ) { + this.route += DialogRoute( + route = route, + content = content + ) + } + + fun addRoute(route: Route) { + this.route += route + } + + internal fun build(): RouteGraph { + if (initialRoute.isEmpty() && route.isEmpty()) { + // FIXME: 2021/4/2 Show warning + } else { + require(route.any { it.route == initialRoute }) { + "No initial route target fot this route graph" + } + } + require(!route.groupBy { it.route }.any { it.value.size > 1 }) { + "Duplicate route can not be applied" + } + return RouteGraph(initialRoute, route.toList()) + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt new file mode 100644 index 00000000..35011436 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt @@ -0,0 +1,28 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import moe.tlaster.precompose.navigation.route.Route + +internal data class RouteGraph( + val initialRoute: String, + val routes: List, +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt new file mode 100644 index 00000000..f60a2a8e --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt @@ -0,0 +1,64 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import moe.tlaster.precompose.navigation.route.ComposeRoute +import kotlin.math.min + +internal class RouteMatch { + var matches = false + var route: ComposeRoute? = null + var vars = arrayListOf() + var keys = arrayListOf() + var pathMap = linkedMapOf() + fun key() { + val size = min(keys.size, vars.size) + for (i in 0 until size) { + pathMap[keys[i]] = vars[i] + } + for (i in 0 until size) { + vars.removeFirst() + } + } + + fun truncate(size: Int) { + var sizeInt = size + while (sizeInt < vars.size) { + vars.removeAt(sizeInt++) + } + } + + fun value(value: String) { + vars.add(value) + } + + fun pop() { + if (vars.isNotEmpty()) { + vars.removeLast() + } + } + + fun found(route: ComposeRoute): RouteMatch { + this.route = route + matches = true + return this + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt new file mode 100644 index 00000000..2a37979b --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt @@ -0,0 +1,28 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import moe.tlaster.precompose.navigation.route.Route + +internal data class RouteMatchResult( + val route: Route, + val pathMap: Map = emptyMap(), +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt new file mode 100644 index 00000000..bcdc69e9 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt @@ -0,0 +1,695 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import moe.tlaster.precompose.navigation.route.Route +import kotlin.math.min + +internal class RouteParser { + data class Segment( + val nodeType: Int = 0, + val rexPat: String = "", + val tail: Char = 0.toChar(), + val startIndex: Int = 0, + val endIndex: Int = 0, + ) + + private data class Node( + var type: Int = 0, + var prefix: String = "", + var label: Char = 0.toChar(), + var tail: Char = 0.toChar(), + var rex: Regex? = null, + var paramsKey: String? = null, + var route: Route? = null, + ) : Comparable { + + // subroutes on the leaf node + // Routes subroutes; + // child nodes should be stored in-order for iteration, + // in groups of the node type. + var children = linkedMapOf>() + override operator fun compareTo(other: Node): Int { + return label - other.label + } + + fun insertRoute(pattern: String, route: Route): Node { + var n = this + var parent: Node + var search = pattern + while (true) { + // Handle key exhaustion + if (search.isEmpty()) { + // Insert or update the node's leaf handler + n.applyRoute(route) + return n + } + + // We're going to be searching for a wild node next, + // in this case, we need to get the tail + val label = search[0] + // char segTail; + // int segEndIdx; + // int segTyp; + val seg = if (label == '{' || label == '*') { + // segTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search) + patNextSegment(search) + } else { + Segment() + } + val prefix = if (seg.nodeType == ntRegexp) { + seg.rexPat + } else { + "" + } + + // Look for the edge to attach to + parent = n + n = n.getEdge(seg.nodeType, label, seg.tail, prefix) ?: run { + val child = Node(label = label, tail = seg.tail, prefix = search) + val hn = parent.addChild(child, search) + hn.applyRoute(route) + return hn + } + + // Found an edge to newRuntimeRoute the pattern + if (n.type > ntStatic) { + // We found a param node, trim the param from the search path and continue. + // This param/wild pattern segment would already be on the tree from a previous + // call to addChild when creating a new node. + search = search.substring(seg.endIndex) + continue + } + + // Static nodes fall below here. + // Determine longest prefix of the search key on newRuntimeRoute. + val commonPrefix = longestPrefix(search, n.prefix) + if (commonPrefix == n.prefix.length) { + // the common prefix is as long as the current node's prefix we're attempting to insert. + // keep the search going. + search = search.substring(commonPrefix) + continue + } + + // Split the node + val child = Node(type = ntStatic, prefix = search.substring(0, commonPrefix)) + parent.replaceChild(search[0], seg.tail, child) + + // Restore the existing node + n.label = n.prefix[commonPrefix] + n.prefix = n.prefix.substring(commonPrefix) + child.addChild(n, n.prefix) + + // If the new key is a subset, set the route on this node and finish. + search = search.substring(commonPrefix) + if (search.isEmpty()) { + child.applyRoute(route) + return child + } + + // Create a new edge for the node + val subchild = Node(type = ntStatic, label = search[0], prefix = search) + val hn = child.addChild(subchild, search) + hn.applyRoute(route) + return hn + } + } + + // addChild appends the new `child` node to the tree using the `pattern` as the trie key. + // For a URL router like chi's, we split the static, param, regexp and wildcard segments + // into different nodes. In addition, addChild will recursively call itself until every + // pattern segment is added to the url pattern tree as individual nodes, depending on type. + fun addChild(child: Node, search: String): Node { + var searchStr = search + val n = this + // String search = prefix.toString(); + + // handler leaf node added to the tree is the child. + // this may be overridden later down the flow + var hn = child + + // Parse next segment + // segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search) + val seg = patNextSegment(searchStr) + val segTyp = seg.nodeType + var segStartIdx = seg.startIndex + val segEndIdx = seg.endIndex + when (segTyp) { + ntStatic -> { + } + else -> { + // Search prefix contains a param, regexp or wildcard + if (segTyp == ntRegexp) { + child.prefix = seg.rexPat + child.rex = seg.rexPat.toRegex() + } + if (segStartIdx == 0) { + // Route starts with a param + child.type = segTyp + segStartIdx = if (segTyp == ntCatchAll) { + -1 + } else { + segEndIdx + } + if (segStartIdx < 0) { + segStartIdx = searchStr.length + } + child.tail = seg.tail // for params, we set the tail + child.paramsKey = findParamKey(searchStr) // set paramsKey if it has keys + if (segStartIdx != searchStr.length) { + // add static edge for the remaining part, split the end. + // its not possible to have adjacent param nodes, so its certainly + // going to be a static node next. + searchStr = searchStr.substring(segStartIdx) // advance search position + val nn = Node(type = ntStatic, label = searchStr[0], prefix = searchStr) + hn = child.addChild(nn, searchStr) + } + } else if (segStartIdx > 0) { + // Route has some param + + // starts with a static segment + child.type = ntStatic + child.prefix = searchStr.substring(0, segStartIdx) + child.rex = null + + // add the param edge node + searchStr = searchStr.substring(segStartIdx) + val nn = Node(type = segTyp, label = searchStr[0], tail = seg.tail, paramsKey = findParamKey(searchStr)) + hn = child.addChild(nn, searchStr) + } + } + } + n.children[child.type] = append(n.children[child.type], child).also { + tailSort(it) + } + return hn + } + + private fun findParamKey(pattern: String): String? { + val startChar = "{" + val endChar = "}" + val regexStart = ":" + if (!pattern.startsWith("{") && !pattern.endsWith("}")) return null + val startIndex = pattern.indexOf(startChar) + val endIndex = pattern.indexOf(endChar) + val regexIndex = pattern.indexOf(regexStart).let { + if (it == -1) pattern.length else it + } + return pattern.substring(min(startIndex + 1, pattern.length - 1), min(endIndex, regexIndex)) + } + + fun replaceChild(label: Char, tail: Char, child: Node) { + val n = this + val children = n.children[child.type] ?: return + var i = 0 + while (i < children.size) { + if (children[i].label == label && children[i].tail == tail) { + children[i] = child + children[i].label = label + children[i].tail = tail + return + } + i++ + } + throw IllegalArgumentException("chi: replacing missing child") + } + + fun getEdge(ntyp: Int, label: Char, tail: Char, prefix: String): Node? { + val n = this + val nds = n.children[ntyp] ?: return null + var i = 0 + while (i < nds.size) { + if (nds[i].label == label && nds[i].tail == tail) { + if (ntyp == ntRegexp && nds[i].prefix != prefix) { + i++ + continue + } + return nds[i] + } + i++ + } + return null + } + + fun applyRoute(route: Route?) { + val n = this + n.route = route + } + + // Recursive edge traversal by checking all nodeTyp groups along the way. + // It's like searching through a multi-dimensional radix trie. + fun findRoute(rctx: RouteMatch, path: String): Route? { + for (ntyp in 0 until NODE_SIZE) { + val nds = children[ntyp] + if (nds == null) { + continue + } + var xn: Node? = null + var xsearch = path + val label = if (path.isNotEmpty()) path[0] else ZERO_CHAR + when (ntyp) { + ntStatic -> { + xn = findEdge(nds, label) + if (xn == null || !xsearch.startsWith(xn.prefix)) { + continue + } + xsearch = xsearch.substring(xn.prefix.length) + } + ntParam, ntRegexp -> { + // short-circuit and return no matching route for empty param values + if (xsearch.isEmpty()) { + continue + } + // serially loop through each node grouped by the tail delimiter + var idx = 0 + while (idx < nds.size) { + xn = nds[idx] + if (xn.type != ntStatic) { + xn.paramsKey?.let { + rctx.keys.add(it) + } + } + // label for param nodes is the delimiter byte + var p = xsearch.indexOf(xn.tail) + if (p < 0) { + p = if (xn.tail == '/') { + xsearch.length + } else { + idx++ + continue + } + } + val rex = xn.rex + if (ntyp == ntRegexp && rex != null) { + if (!rex.matches(xsearch.substring(0, p))) { + idx++ + continue + } + } else if (xsearch.substring(0, p).indexOf('/') != -1) { + // avoid a newRuntimeRoute across path segments + idx++ + continue + } + + // rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p]) + val prevlen: Int = rctx.vars.size + rctx.value(xsearch.substring(0, p)) + xsearch = xsearch.substring(p) + if (xsearch.isEmpty()) { + if (xn.isLeaf) { + val h = xn.route + if (h != null) { + rctx.key() + return h + } + } + } + + // recursively find the next node on this branch + val fin = xn.findRoute(rctx, xsearch) + if (fin != null) { + return fin + } + + // not found on this branch, reset vars + rctx.truncate(prevlen) + xsearch = path + idx++ + } + } + else -> { + // catch-all nodes + // rctx.routeParams.Values = append(rctx.routeParams.Values, search) + if (xsearch.isNotEmpty()) { + rctx.value(xsearch) + } + xn = nds[0] + xsearch = "" + } + } + if (xn == null) { + continue + } + + // did we returnType it yet? + if (xsearch.isEmpty()) { + if (xn.isLeaf) { + val h = xn.route + if (h != null) { + // rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) + rctx.key() + return h + } + } + } + + // recursively returnType the next node.. + val fin = xn.findRoute(rctx, xsearch) + if (fin != null) { + return fin + } + + // Did not returnType final handler, let's remove the param here if it was set + if (xn.type > ntStatic) { + // if len(rctx.routeParams.Values) > 0 { + // rctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values) - 1] + // } + rctx.pop() + } + } + return null + } + + fun findEdge(ns: Array, label: Char): Node? { + val num = ns.size + var idx = 0 + var i = 0 + var j = num - 1 + while (i <= j) { + idx = i + (j - i) / 2 + when { + label > ns[idx].label -> { + i = idx + 1 + } + label < ns[idx].label -> { + j = idx - 1 + } + else -> { + i = num // breaks cond + } + } + } + return if (ns[idx].label != label) { + null + } else ns[idx] + } + + val isLeaf: Boolean + get() = route != null + + // longestPrefix finds the filesize of the shared prefix of two strings + fun longestPrefix(k1: String, k2: String): Int { + val len: Int = min(k1.length, k2.length) + for (i in 0 until len) { + if (k1[i] != k2[i]) { + return i + } + } + return len + } + + fun tailSort(ns: Array) { + if (ns.size > 1) { + ns.sort() + for (i in ns.indices.reversed()) { + if (ns[i].type > ntStatic && ns[i].tail == '/') { + val tmp = ns[i] + ns[i] = ns[ns.size - 1] + ns[ns.size - 1] = tmp + return + } + } + } + } + + private fun append(src: Array?, child: Node): Array { + if (src == null) { + return arrayOf(child) + } + val result = src.copyOf() + return result + child + } + + // patNextSegment returns the next segment details from a pattern: + // node type, param key, regexp string, param tail byte, param starting index, param ending index + fun patNextSegment(pattern: String): Segment { + val ps = pattern.indexOf('{') + val ws = pattern.indexOf('*') + if (ps < 0 && ws < 0) { + return Segment( + ntStatic, "", ZERO_CHAR, 0, + pattern.length + ) // we return the entire thing + } + + // Sanity check + require(!(ps >= 0 && ws >= 0 && ws < ps)) { "chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'" } + var tail = '/' // Default endpoint tail to / byte + if (ps >= 0) { + // Param/Regexp pattern is next + var nt = ntParam + + // Read to closing } taking into account opens and closes in curl count (cc) + var cc = 0 + var pe = ps + val range = pattern.substring(ps) + for (i in range.indices) { + val c = range[i] + if (c == '{') { + cc++ + } else if (c == '}') { + cc-- + if (cc == 0) { + pe = ps + i + break + } + } + } + require(pe != ps) { "Router: route param closing delimiter '}' is missing" } + val key = pattern.substring(ps + 1, pe) + pe++ // set end to next position + if (pe < pattern.length) { + tail = pattern[pe] + } + var rexpat = "" + val idx = key.indexOf(':') + if (idx >= 0) { + nt = ntRegexp + rexpat = key.substring(idx + 1) + // key = key.substring(0, idx); + } + if (rexpat.isNotEmpty()) { + if (rexpat[0] != '^') { + rexpat = "^$rexpat" + } + if (rexpat[rexpat.length - 1] != '$') { + rexpat = "$rexpat$" + } + } + return Segment(nt, rexpat, tail, ps, pe) + } + + // Wildcard pattern as finale + // EDIT: should we panic if there is stuff after the * ??? + // We allow naming a wildcard: *path + // String key = ws == pattern.length() - 1 ? "*" : pattern.substring(ws + 1).toString(); + return Segment(ntCatchAll, "", ZERO_CHAR, ws, pattern.length) + } + } + + private val root = Node() + + private val staticPaths = linkedMapOf() + fun insert(pattern: String, route: Route) { + var patternStr = pattern + val baseCatchAll = baseCatchAll(patternStr) + if (baseCatchAll.length > 1) { + // Add route pattern: /static/?* => /static + insert(baseCatchAll, route) + val tail = patternStr.substring(baseCatchAll.length + 2) + patternStr = "$baseCatchAll/$tail" + } + if (patternStr == BASE_CATCH_ALL) { + patternStr = "/*" + } + if (pathKeys(patternStr).isEmpty()) { + staticPaths[patternStr] = route + } + root.insertRoute(patternStr, route) + } + + private fun baseCatchAll(pattern: String): String { + val i = pattern.indexOf(BASE_CATCH_ALL) + return if (i > 0) { + pattern.substring(0, i) + } else "" + } + + fun insert(route: Route) { + insert(route.route, route) + } + + fun find(path: String): RouteMatchResult? { + val staticRoute = staticPaths[path] + return if (staticRoute == null) { + findInternal(path) + } else { + return RouteMatchResult(staticRoute) + } + } + + private fun findInternal(path: String): RouteMatchResult? { + // use radix tree + val result = RouteMatch() + val route = root.findRoute(result, path) ?: return null + return RouteMatchResult(route, result.pathMap) + } + + companion object { + fun pathKeys( + pattern: String, + onItem: (key: String, value: String?) -> Unit = { _, _ -> }, + ): List { + val result = arrayListOf() + var start = -1 + var end = Int.MAX_VALUE + val len = pattern.length + var curly = 0 + var i = 0 + while (i < len) { + val ch = pattern[i] + if (ch == '{') { + if (curly == 0) { + start = i + 1 + end = Int.MAX_VALUE + } + curly += 1 + } else if (ch == ':') { + end = i + } else if (ch == '}') { + curly -= 1 + if (curly == 0) { + val id = pattern.substring(start, min(i, end)) + if (end == Int.MAX_VALUE) { + null + } else { + pattern.substring(end + 1, i) + }.let { + onItem.invoke(id, it) + } + result.add(id) + start = -1 + end = Int.MAX_VALUE + } + } else if (ch == '*') { + val id: String = if (i == len - 1) { + "*" + } else { + pattern.substring(i + 1) + } + onItem.invoke(id, "\\.*") + result.add(id) + i = len + } + i++ + } + return result + } + + fun expandOptionalVariables(pattern: String): List { + if (pattern.isEmpty() || pattern == "/") { + return listOf("/") + } + val len = pattern.length + var key = 0 + val paths = linkedMapOf() + val pathAppender = { index: Int, segment: StringBuilder -> + for (i in index until index - 1) { + paths[i]?.append(segment) + } + paths.getOrPut(index) { + val value = StringBuilder() + if (index > 0) { + val previous = paths[index - 1] + if (previous.toString() != "/") { + value.append(previous) + } + } + value + }.append(segment) + } + val segment = StringBuilder() + var isLastOptional = false + var i = 0 + while (i < len) { + val ch = pattern[i] + if (ch == '/') { + if (segment.isNotEmpty()) { + pathAppender.invoke(key, segment) + segment.setLength(0) + } + segment.append(ch) + i += 1 + } else if (ch == '{') { + segment.append(ch) + var curly = 1 + var j = i + 1 + while (j < len) { + val next = pattern[j++] + segment.append(next) + if (next == '{') { + curly += 1 + } else if (next == '}') { + curly -= 1 + if (curly == 0) { + break + } + } + } + if (j < len && pattern[j] == '?') { + j += 1 + isLastOptional = true + if (paths.isEmpty()) { + paths[0] = StringBuilder("/") + } + pathAppender.invoke(++key, segment) + } else { + isLastOptional = false + pathAppender.invoke(key, segment) + } + segment.setLength(0) + i = j + } else { + segment.append(ch) + i += 1 + } + } + if (paths.isEmpty()) { + return listOf(pattern) + } + if (segment.isNotEmpty()) { + pathAppender.invoke(key, segment) + if (isLastOptional) { + paths[++key] = segment + } + } + return paths.values.map { it.toString() } + } + + private const val ntStatic = 0 // /home + private const val ntRegexp = 1 // /{id:[0-9]+} + private const val ntParam = 2 // /{user} + private const val ntCatchAll = 3 // /api/v1/* + private const val NODE_SIZE = ntCatchAll + 1 + private const val ZERO_CHAR = 0.toChar() + private const val BASE_CATCH_ALL = "/?*" + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt new file mode 100644 index 00000000..70cee6b0 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt @@ -0,0 +1,72 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.snapshots.SnapshotStateList +import moe.tlaster.precompose.navigation.transition.NavTransition + +@Stable +internal class RouteStack( + val id: Long, + val stacks: SnapshotStateList = mutableStateListOf(), + val navTransition: NavTransition? = null, +) { + private var destroyAfterTransition = false + val currentEntry: BackStackEntry? + get() = stacks.lastOrNull() + + val canGoBack: Boolean + get() = stacks.size > 1 + + fun goBack(): BackStackEntry { + return stacks.removeLast().also { + it.destroy() + } + } + + fun onActive() { + currentEntry?.active() + } + + fun onInActive() { + currentEntry?.inActive() + if (destroyAfterTransition) { + onDestroyed() + } + } + + fun destroyAfterTransition() { + destroyAfterTransition = true + } + + fun onDestroyed() { + stacks.forEach { + it.destroy() + } + stacks.clear() + } + + fun hasRoute(route: String): Boolean { + return stacks.any { it.route.route == route } + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt new file mode 100644 index 00000000..a688adf0 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt @@ -0,0 +1,223 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.saveable.SaveableStateHolder +import moe.tlaster.precompose.lifecycle.Lifecycle +import moe.tlaster.precompose.lifecycle.LifecycleObserver +import moe.tlaster.precompose.lifecycle.LifecycleOwner +import moe.tlaster.precompose.navigation.route.ComposeRoute +import moe.tlaster.precompose.navigation.route.DialogRoute +import moe.tlaster.precompose.navigation.route.SceneRoute +import moe.tlaster.precompose.ui.BackDispatcher +import moe.tlaster.precompose.ui.BackHandler +import moe.tlaster.precompose.viewmodel.ViewModelStore +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +@Stable +internal class RouteStackManager( + private val stateHolder: SaveableStateHolder, + private val routeGraph: RouteGraph, +) : LifecycleObserver, BackHandler { + // FIXME: 2021/4/1 Temp workaround for deeplink + private var pendingNavigation: String? = null + private val _suspendResult = linkedMapOf>() + var backDispatcher: BackDispatcher? = null + set(value) { + field?.unregister(this) + field = value + value?.register(this) + } + private var stackEntryId = Long.MIN_VALUE + private var routeStackId = Long.MIN_VALUE + var lifeCycleOwner: LifecycleOwner? = null + set(value) { + field?.lifecycle?.removeObserver(this) + field = value + value?.lifecycle?.addObserver(this) + } + private var viewModel: NavControllerViewModel? = null + private val _backStacks = mutableStateListOf() + + internal val backStacks: List + get() = _backStacks + + internal val currentStack: RouteStack? + get() = _backStacks.lastOrNull() + + internal val currentEntry: BackStackEntry? + get() = currentStack?.currentEntry + + val canGoBack: Boolean + get() = currentStack?.canGoBack != false || _backStacks.size > 1 + + private val routeParser: RouteParser by lazy { + RouteParser().apply { + routeGraph.routes + .map { route -> + RouteParser.expandOptionalVariables(route.route).let { + if (route is SceneRoute) { + it + route.deepLinks.flatMap { + RouteParser.expandOptionalVariables(it) + } + } else { + it + } + } to route + } + .flatMap { it.first.map { route -> route to it.second } }.forEach { + insert(it.first, it.second) + } + } + } + + internal fun getBackStackEntry(route: String): BackStackEntry? { + return _backStacks.find { it.hasRoute(route) }?.currentEntry + } + + internal fun setViewModelStore(viewModelStore: ViewModelStore) { + if (viewModel != NavControllerViewModel.create(viewModelStore)) { + viewModel = NavControllerViewModel.create(viewModelStore) + } + } + + fun navigate(path: String, options: NavOptions? = null) { + val vm = viewModel ?: run { + pendingNavigation = path + return + } + val query = path.substringAfter('?', "") + val routePath = path.substringBefore('?') + val matchResult = routeParser.find(path = routePath) + checkNotNull(matchResult) { "RouteStackManager: navigate target $path not found" } + require(matchResult.route is ComposeRoute) { "RouteStackManager: navigate target $path is not ComposeRoute" } + if (options != null && matchResult.route is SceneRoute && options.launchSingleTop) { + _backStacks.firstOrNull { it.hasRoute(matchResult.route.route) }?.let { + _backStacks.remove(it) + _backStacks.add(it) + } + } else { + val entry = BackStackEntry( + id = stackEntryId++, + route = matchResult.route, + pathMap = matchResult.pathMap, + queryString = query.takeIf { it.isNotEmpty() }?.let { + QueryString(it) + }, + viewModel = vm, + ) + when (matchResult.route) { + is SceneRoute -> { + _backStacks.add( + RouteStack( + id = routeStackId++, + stacks = mutableStateListOf(entry), + navTransition = matchResult.route.navTransition, + ) + ) + } + is DialogRoute -> { + currentStack?.stacks?.add(entry) + } + } + } + + if (options?.popUpTo != null && matchResult.route is SceneRoute) { + val index = _backStacks.indexOfLast { it.hasRoute(options.popUpTo.route) } + if (index != -1 && index != _backStacks.lastIndex) { + _backStacks.removeRange( + if (options.popUpTo.inclusive) index else index + 1, + _backStacks.lastIndex + ) + } else if (options.popUpTo.route.isEmpty()) { + _backStacks.removeRange(0, _backStacks.lastIndex) + } + } + } + + fun goBack(result: Any? = null) { + if (!canGoBack) { + backDispatcher?.onBackPress() + return + } + when { + currentStack?.canGoBack == true -> { + currentStack?.goBack() + } + _backStacks.size > 1 -> { + val stack = _backStacks.removeLast() + val entry = stack.currentEntry + stateHolder.removeState(stack.id) + stack.destroyAfterTransition() + entry + } + else -> { + null + } + }?.takeIf { backStackEntry -> + _suspendResult.containsKey(backStackEntry) + }?.let { + _suspendResult.remove(it)?.resume(result) + } + } + + suspend fun waitingForResult(entry: BackStackEntry): Any? = suspendCoroutine { + _suspendResult[entry] = it + } + + override fun onStateChanged(state: Lifecycle.State) { + when (state) { + Lifecycle.State.Initialized -> Unit + Lifecycle.State.Active -> currentStack?.onActive() + Lifecycle.State.InActive -> currentStack?.onInActive() + Lifecycle.State.Destroyed -> { + _backStacks.forEach { + it.onDestroyed() + } + _backStacks.clear() + } + } + } + + internal fun indexOf(stack: RouteStack): Int { + return _backStacks.indexOf(stack) + } + + override fun handleBackPress(): Boolean { + return if (canGoBack) { + goBack() + true + } else { + false + } + } + + fun navigateInitial(initialRoute: String) { + navigate(initialRoute) + pendingNavigation?.let { + navigate(it) + } + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt new file mode 100644 index 00000000..a21c8e8f --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt @@ -0,0 +1,34 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.route + +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.RouteParser + +abstract class ComposeRoute( + override val route: String, + val content: @Composable (BackStackEntry) -> Unit +) : Route { + override val pathKeys by lazy { + RouteParser.pathKeys(pattern = route) + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt new file mode 100644 index 00000000..c924989a --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt @@ -0,0 +1,29 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.route + +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.BackStackEntry + +internal class DialogRoute( + route: String, + content: @Composable (BackStackEntry) -> Unit +) : ComposeRoute(route, content) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt new file mode 100644 index 00000000..d2a2ed7e --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt @@ -0,0 +1,27 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.route + +interface Route { + val route: String + @Deprecated("store path key in route node in order to match different links in one route") + val pathKeys: List +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt new file mode 100644 index 00000000..6de56897 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt @@ -0,0 +1,41 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.route + +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.RouteParser +import moe.tlaster.precompose.navigation.transition.NavTransition + +internal class SceneRoute( + route: String, + val navTransition: NavTransition?, + val deepLinks: List, + content: @Composable (BackStackEntry) -> Unit, +) : ComposeRoute(route, content) { + override val pathKeys by lazy { + ( + deepLinks.flatMap { + RouteParser.pathKeys(pattern = it) + } + RouteParser.pathKeys(pattern = route) + ).distinct() + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedDialogRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedDialogRoute.kt new file mode 100644 index 00000000..2612a694 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedDialogRoute.kt @@ -0,0 +1,129 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.transition + +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import moe.tlaster.precompose.lifecycle.Lifecycle +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.RouteStack + +@Composable +internal fun AnimatedDialogRoute( + stack: RouteStack, + modifier: Modifier = Modifier, + animationSpec: FiniteAnimationSpec = tween(), + dialogTransition: DialogTransition = remember { DialogTransition() }, + content: @Composable (BackStackEntry) -> Unit +) { + + val items = remember { mutableStateListOf>() } + val stacks = stack.stacks + val targetState = remember(stack.stacks.size) { + stack.currentEntry + } + val transitionState = remember { MutableTransitionState(targetState) } + val targetChanged = (targetState != transitionState.targetState) + val previousState = transitionState.targetState + transitionState.targetState = targetState + val transition = updateTransition(transitionState, label = "AnimatedDialogRouteTransition") + + if (targetChanged || items.isEmpty()) { + val indexOfNew = stacks.indexOf(targetState).takeIf { it >= 0 } ?: Int.MAX_VALUE + val indexOfOld = stacks.indexOf(previousState) + .takeIf { + it >= 0 || + // Workaround for navOptions + targetState?.lifecycle?.currentState == Lifecycle.State.Initialized && + previousState?.lifecycle?.currentState == Lifecycle.State.Active + } ?: Int.MAX_VALUE + // Only manipulate the list when the state is changed, or in the first run. + val keys = items.map { + val type = if (indexOfNew >= indexOfOld) AnimateType.Pause else AnimateType.Destroy + it.key to type + }.toMap().run { + toMutableMap().also { + val type = if (indexOfNew >= indexOfOld) { + AnimateType.Create + } else { + AnimateType.Resume + } + if (targetState != null) { + it[targetState] = type + } + } + } + items.clear() + keys.mapTo(items) { (key, value) -> + AnimatedRouteItem(key, value) { + val factor by transition.animateFloat( + transitionSpec = { animationSpec } + ) { if (it == key) 1f else 0f } + Box( + Modifier.graphicsLayer { + when (value) { + AnimateType.Create -> dialogTransition.createTransition.invoke( + this, + factor + ) + AnimateType.Destroy -> dialogTransition.destroyTransition.invoke( + this, + factor + ) + else -> Unit + } + } + ) { + content(key) + } + } + }.sortByDescending { it.animateType } + } else if (transitionState.currentState == transitionState.targetState) { + // Remove all the intermediate items from the list once the animation is finished. + items.removeAll { it.animateType == AnimateType.Destroy } + } + + Box(modifier) { + for (index in items.indices) { + val item = items[index] + key(item.key) { + Box( + modifier = Modifier + .fillMaxSize() + ) { + item.content.invoke() + } + } + } + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedRoute.kt new file mode 100644 index 00000000..f2c19f98 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedRoute.kt @@ -0,0 +1,132 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.transition + +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import moe.tlaster.precompose.lifecycle.Lifecycle +import moe.tlaster.precompose.navigation.RouteStack +import moe.tlaster.precompose.navigation.RouteStackManager + +@Composable +internal fun AnimatedRoute( + targetState: RouteStack, + modifier: Modifier = Modifier, + manager: RouteStackManager, + animationSpec: FiniteAnimationSpec = tween(), + navTransition: NavTransition = remember { NavTransition() }, + content: @Composable (RouteStack) -> Unit +) { + val items = remember { mutableStateListOf>() } + val transitionState = remember { MutableTransitionState(targetState) } + val targetChanged = (targetState != transitionState.targetState) + val previousState = transitionState.targetState + transitionState.targetState = targetState + val transition = updateTransition(transitionState) + if (targetChanged || items.isEmpty()) { + val indexOfNew = manager.indexOf(targetState).takeIf { it >= 0 } ?: Int.MAX_VALUE + val indexOfOld = manager.indexOf(previousState) + .takeIf { + it >= 0 || + // Workaround for navOptions + targetState.currentEntry?.lifecycle?.currentState == Lifecycle.State.Initialized && + previousState.currentEntry?.lifecycle?.currentState == Lifecycle.State.Active + } ?: Int.MAX_VALUE + val actualNavTransition = run { + if (indexOfNew >= indexOfOld) targetState else previousState + }.navTransition ?: navTransition + // Only manipulate the list when the state is changed, or in the first run. + val keys = items.map { + val type = if (indexOfNew >= indexOfOld) AnimateType.Pause else AnimateType.Destroy + it.key to type + }.toMap().run { + if (!containsKey(targetState)) { + toMutableMap().also { + val type = if (indexOfNew >= indexOfOld) AnimateType.Create else AnimateType.Resume + it[targetState] = type + } + } else { + this + } + } + items.clear() + keys.mapTo(items) { (key, value) -> + AnimatedRouteItem(key, value) { + val factor by transition.animateFloat( + transitionSpec = { animationSpec } + ) { if (it == key) 1f else 0f } + Box( + Modifier.graphicsLayer { + when (value) { + AnimateType.Create -> actualNavTransition.createTransition.invoke(this, factor) + AnimateType.Destroy -> actualNavTransition.destroyTransition.invoke(this, factor) + AnimateType.Pause -> actualNavTransition.pauseTransition.invoke(this, factor) + AnimateType.Resume -> actualNavTransition.resumeTransition.invoke(this, factor) + } + } + ) { + content(key) + } + } + }.sortByDescending { it.animateType } + } else if (transitionState.currentState == transitionState.targetState) { + // Remove all the intermediate items from the list once the animation is finished. + items.removeAll { it.key != transitionState.targetState } + } + + Box(modifier) { + for (index in items.indices) { + val item = items[index] + key(item.key) { + Box( + modifier = Modifier.fillMaxSize() + ) { + item.content() + } + } + } + } +} + +internal enum class AnimateType { + Create, + Destroy, + Pause, + Resume, +} + +internal data class AnimatedRouteItem( + val key: T, + val animateType: AnimateType, + val content: @Composable () -> Unit +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/DialogTransition.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/DialogTransition.kt new file mode 100644 index 00000000..2d87fd3c --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/DialogTransition.kt @@ -0,0 +1,41 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.transition + +import androidx.compose.ui.graphics.GraphicsLayerScope + +val fadeCreateTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + alpha = factor +} +val fadeDestroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + alpha = factor +} + +data class DialogTransition( + /** + * Transition the scene that about to appear for the first time, similar to activity onCreate, factor from 0.0 to 1.0 + */ + val createTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeCreateTransition, + /** + * Transition the scene that about to disappear forever, similar to activity onDestroy, factor from 1.0 to 0.0 + */ + val destroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeDestroyTransition, +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/NavTransition.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/NavTransition.kt new file mode 100644 index 00000000..028214ec --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/NavTransition.kt @@ -0,0 +1,77 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.transition + +import androidx.compose.ui.graphics.GraphicsLayerScope + +private const val enterScaleFactor: Float = 1.1F +private const val exitScaleFactor: Float = 0.9F + +val fadeScaleCreateTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + (exitScaleFactor + (1F - exitScaleFactor) * factor).let { + scaleX = it + scaleY = it + } + alpha = factor +} +val fadeScaleDestroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + (exitScaleFactor + (1F - exitScaleFactor) * factor).let { + scaleX = it + scaleY = it + } + alpha = factor +} +val fadeScalePauseTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + (enterScaleFactor - (enterScaleFactor - 1F) * factor).let { + scaleX = it + scaleY = it + } + alpha = factor +} +val fadeScaleResumeTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> + (enterScaleFactor - (enterScaleFactor - 1F) * factor).let { + scaleX = it + scaleY = it + } + alpha = factor +} + +/** + * Create a navigation transition + */ +data class NavTransition( + /** + * Transition the scene that about to appear for the first time, similar to activity onCreate, factor from 0.0 to 1.0 + */ + val createTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeScaleCreateTransition, + /** + * Transition the scene that about to disappear forever, similar to activity onDestroy, factor from 1.0 to 0.0 + */ + val destroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeScaleDestroyTransition, + /** + * Transition the scene that will be pushed into back stack, similar to activity onPause, factor from 1.0 to 0.0 + */ + val pauseTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeScalePauseTransition, + /** + * Transition the scene that about to show from the back stack, similar to activity onResume, factor from 0.0 to 1.0 + */ + val resumeTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeScaleResumeTransition, +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt new file mode 100644 index 00000000..295c6c26 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt @@ -0,0 +1,54 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.ui + +import androidx.compose.runtime.compositionLocalOf + +val LocalBackDispatcherOwner = compositionLocalOf { null } + +interface BackDispatcherOwner { + val backDispatcher: BackDispatcher +} + +class BackDispatcher { + private val handlers = arrayListOf() + + fun onBackPress(): Boolean { + for (it in handlers) { + if (it.handleBackPress()) { + return true + } + } + return false + } + + internal fun register(handler: BackHandler) { + handlers.add(0, handler) + } + + internal fun unregister(handler: BackHandler) { + handlers.remove(handler) + } +} + +interface BackHandler { + fun handleBackPress(): Boolean +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/ComposeCompositionLocal.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/ComposeCompositionLocal.kt new file mode 100644 index 00000000..cc0710a7 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/ComposeCompositionLocal.kt @@ -0,0 +1,33 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.ui + +import androidx.compose.runtime.compositionLocalOf +import moe.tlaster.precompose.lifecycle.LifecycleOwner +import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner + +val LocalLifecycleOwner = compositionLocalOf { noLocalProvidedFor("LocalLifecycleOwner") } + +val LocalViewModelStoreOwner = compositionLocalOf { noLocalProvidedFor("ViewModelStoreOwner") } + +private fun noLocalProvidedFor(name: String): Nothing { + error("CompositionLocal $name not present") +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/ViewModelAdapter.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/ViewModelAdapter.kt new file mode 100644 index 00000000..720e8dcf --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/ViewModelAdapter.kt @@ -0,0 +1,70 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner +import kotlin.reflect.KClass + +@Composable +inline fun viewModel( + keys: List = emptyList(), + noinline creator: () -> T, +): T = viewModel(T::class, keys, creator = creator) + +@Composable +fun viewModel( + modelClass: KClass, + keys: List = emptyList(), + creator: () -> T, +): T { + val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "Require LocalViewModelStoreOwner not null for $modelClass" + } + return remember( + modelClass, keys, creator, viewModelStoreOwner + ) { + viewModelStoreOwner.getViewModel(keys, modelClass = modelClass, creator = creator) + } +} + +private fun ViewModelStoreOwner.getViewModel( + keys: List = emptyList(), + modelClass: KClass, + creator: () -> T, +): T { + val key = (keys.map { it.hashCode().toString() } + modelClass.qualifiedName).joinToString() + val existing = viewModelStore[key] + if (existing != null && modelClass.isInstance(existing)) { + @Suppress("UNCHECKED_CAST") + return existing as T + } else { + @Suppress("ControlFlowWithEmptyBody") + if (existing != null) { + // TODO: log a warning. + } + } + val viewModel = creator.invoke() + viewModelStore.put(key, viewModel) + return viewModel +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/CloseableCoroutineScope.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/CloseableCoroutineScope.kt new file mode 100644 index 00000000..6845dfdb --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/CloseableCoroutineScope.kt @@ -0,0 +1,57 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.viewmodel + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import java.io.Closeable +import kotlin.coroutines.CoroutineContext + +private const val JOB_KEY = "moe.tlaster.precompose.viewmodel.ViewModelCoroutineScope.JOB_KEY" + +/** + * [CoroutineScope] tied to this [ViewModel]. + * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called + * + * This scope is bound to + * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] + */ +val ViewModel.viewModelScope: CoroutineScope + get() { + val scope: CoroutineScope? = getTag(JOB_KEY) + if (scope != null) { + return scope + } + return setTagIfAbsent( + JOB_KEY, + CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + ) + } + +internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope { + override val coroutineContext: CoroutineContext = context + + override fun close() { + coroutineContext.cancel() + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModel.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModel.kt new file mode 100644 index 00000000..0c65427f --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModel.kt @@ -0,0 +1,63 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.viewmodel + +import java.io.Closeable + +abstract class ViewModel { + @Volatile + private var disposed = false + private val bagOfTags = hashMapOf() + + protected open fun onCleared() {} + + fun clear() { + disposed = true + bagOfTags.let { + for (value in it.values) { + disposeWithRuntimeException(value) + } + } + onCleared() + } + + open fun setTagIfAbsent(key: String, newValue: T): T { + @Suppress("UNCHECKED_CAST") + return bagOfTags.getOrPut(key) { + newValue as Any + }.also { + if (disposed) { + disposeWithRuntimeException(it) + } + } as T + } + + open fun getTag(key: String): T? { + @Suppress("UNCHECKED_CAST") + return bagOfTags[key] as T? + } + + private fun disposeWithRuntimeException(obj: Any) { + if (obj is Closeable) { + obj.close() + } + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelProvider.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelProvider.kt new file mode 100644 index 00000000..04ccb6f4 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelProvider.kt @@ -0,0 +1,50 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.viewmodel + +import kotlin.reflect.KClass + +inline fun ViewModelStore.getViewModel( + noinline creator: () -> T, +): T { + val key = T::class.qualifiedName.toString() + return getViewModel(key, T::class, creator) +} + +fun ViewModelStore.getViewModel( + key: String, + clazz: KClass, + creator: () -> T, +): T { + val existing = get(key) + if (existing != null && clazz.isInstance(existing)) { + @Suppress("UNCHECKED_CAST") + return existing as T + } else { + @Suppress("ControlFlowWithEmptyBody") + if (existing != null) { + // TODO: log a warning. + } + } + val viewModel = creator.invoke() + put(key, viewModel) + return viewModel +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelStore.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelStore.kt new file mode 100644 index 00000000..d3f26248 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelStore.kt @@ -0,0 +1,45 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.viewmodel + +class ViewModelStore { + private val map = hashMapOf() + + fun put(key: String, viewModel: ViewModel) { + val oldViewModel = map.put(key, viewModel) + oldViewModel?.clear() + } + + operator fun get(key: String): ViewModel? { + return map[key] + } + + fun keys(): Set { + return HashSet(map.keys) + } + + fun clear() { + for (vm in map.values) { + vm.clear() + } + map.clear() + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelStoreOwner.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelStoreOwner.kt new file mode 100644 index 00000000..d4817655 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelStoreOwner.kt @@ -0,0 +1,25 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.viewmodel + +interface ViewModelStoreOwner { + val viewModelStore: ViewModelStore +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/compose/ViewModel.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/compose/ViewModel.kt new file mode 100644 index 00000000..0eeb19cf --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/compose/ViewModel.kt @@ -0,0 +1,42 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.viewmodel.compose + +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.ui.LocalViewModelStoreOwner +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner +import moe.tlaster.precompose.viewmodel.getViewModel + +@Composable +inline fun viewModel( + viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" + }, + key: String? = null, + noinline creator: () -> VM, +): VM = viewModelStoreOwner.viewModelStore.let { + if (key == null) { + it.getViewModel(creator) + } else { + it.getViewModel(key, VM::class, creator) + } +} diff --git a/common/src/commonMain/kotlin/com/dimension/maskbook/common/route/CommonRoute.kt b/common/src/commonMain/route/com/dimension/maskbook/common/route/CommonRoute.kt similarity index 100% rename from common/src/commonMain/kotlin/com/dimension/maskbook/common/route/CommonRoute.kt rename to common/src/commonMain/route/com/dimension/maskbook/common/route/CommonRoute.kt diff --git a/common/src/commonMain/kotlin/com/dimension/maskbook/common/route/Deeplinks.kt b/common/src/commonMain/route/com/dimension/maskbook/common/route/Deeplinks.kt similarity index 100% rename from common/src/commonMain/kotlin/com/dimension/maskbook/common/route/Deeplinks.kt rename to common/src/commonMain/route/com/dimension/maskbook/common/route/Deeplinks.kt diff --git a/common/src/commonMain/kotlin/com/dimension/maskbook/common/route/WebDeepLinks.kt b/common/src/commonMain/route/com/dimension/maskbook/common/route/WebDeepLinks.kt similarity index 100% rename from common/src/commonMain/kotlin/com/dimension/maskbook/common/route/WebDeepLinks.kt rename to common/src/commonMain/route/com/dimension/maskbook/common/route/WebDeepLinks.kt diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt index 751bdc53..4be1aeb8 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt @@ -21,8 +21,6 @@ package com.dimension.maskbook.entry import android.content.Context -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.route.Navigator import com.dimension.maskbook.entry.data.JSMethod @@ -34,11 +32,13 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.RouteBuilder import org.koin.dsl.module import org.koin.mp.KoinPlatformTools object EntrySetup : ModuleSetup { - override fun NavGraphBuilder.route(navController: NavController) { + override fun RouteBuilder.route(navController: NavController) { generatedRoute(navController) } diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/ComposeDebugTool.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/ComposeDebugTool.kt index dd434788..250cfceb 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/ComposeDebugTool.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/ComposeDebugTool.kt @@ -34,8 +34,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.compose.currentBackStackEntryAsState +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.currentBackStackEntryAsState @Composable fun ComposeDebugTool( @@ -57,7 +57,7 @@ fun ComposeDebugTool( if (debugOpen) { Text( modifier = Modifier.background(MaterialTheme.colors.surface), - text = state?.destination?.route ?: "UnKnow route", + text = state?.route?.route ?: "UnKnow route", color = MaterialTheme.colors.primary ) } diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/IntroScene.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/IntroScene.kt index 04df9d94..ad1f1486 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/IntroScene.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/IntroScene.kt @@ -62,7 +62,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.routeProcessor.annotations.NavGraphDestination @@ -76,7 +75,8 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.rememberPagerState -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get +import moe.tlaster.precompose.navigation.NavController private data class IntroData( @DrawableRes val img: Int, diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/MainHost.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/MainHost.kt index 8e9eff2e..c3e524ed 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/MainHost.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/MainHost.kt @@ -51,7 +51,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.dimension.maskbook.common.ext.getAll import com.dimension.maskbook.common.ext.navigateToExtension import com.dimension.maskbook.common.route.CommonRoute @@ -68,6 +67,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.rememberPagerState import kotlinx.coroutines.launch +import moe.tlaster.precompose.navigation.NavController import kotlin.math.max private val tabOrder = listOf( diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt index 3d8f4aaa..455d44f9 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -21,19 +21,10 @@ package com.dimension.maskbook.extension import android.net.Uri -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut import androidx.compose.runtime.getValue -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.navArgument -import androidx.navigation.navDeepLink import com.dimension.maskbook.common.IoScopeName import com.dimension.maskbook.common.ModuleSetup -import com.dimension.maskbook.common.ext.navigate +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.gecko.WebContentController import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks @@ -44,18 +35,20 @@ import com.dimension.maskbook.extension.route.ExtensionRoute import com.dimension.maskbook.extension.ui.WebContentScene import com.dimension.maskbook.extension.utils.BackgroundMessageChannel import com.dimension.maskbook.extension.utils.ContentMessageChannel -import com.google.accompanist.navigation.animation.composable +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.RouteBuilder +import moe.tlaster.precompose.navigation.currentBackStackEntryAsState import org.koin.core.qualifier.named import org.koin.dsl.module import org.koin.mp.KoinPlatformTools object ExtensionSetup : ModuleSetup { - @OptIn(ExperimentalAnimationApi::class) - override fun NavGraphBuilder.route(navController: NavController) { - composable( + + override fun RouteBuilder.route(navController: NavController) { + scene( route = ExtensionRoute.WebContent.path, deepLinks = listOf( - navDeepLink { uriPattern = Deeplinks.WebContent.path } + Deeplinks.WebContent.path ), arguments = listOf( navArgument("site") { type = NavType.StringType; nullable = true } @@ -74,10 +67,10 @@ object ExtensionSetup : ModuleSetup { ) { val backStackEntry by navController.currentBackStackEntryAsState() - val site = it.arguments?.getString("site")?.let { Site.valueOf(it) } + val site = it.query("site")?.let { Site.valueOf(it) } WebContentScene( onPersonaClicked = { - navController.navigate(Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona))) { + navController.navigateUri(Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona))) { launchSingleTop = true popUpTo(ExtensionRoute.WebContent.path) { inclusive = false diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ui/WebContentScene.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ui/WebContentScene.kt index 22b2adc4..67dc6da1 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ui/WebContentScene.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ui/WebContentScene.kt @@ -20,7 +20,6 @@ */ package com.dimension.maskbook.extension.ui -import androidx.activity.compose.BackHandler import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -63,7 +62,8 @@ import com.dimension.maskbook.common.ui.widget.button.clickable import com.dimension.maskbook.extension.export.model.Site import com.dimension.maskbook.extension.ext.site import com.dimension.maskbook.localization.R -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get +import moe.tlaster.precompose.navigation.BackHandler import kotlin.math.roundToInt @Composable diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt index 4fde3462..c98b05fa 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt @@ -21,8 +21,6 @@ package com.dimension.maskbook.labs import android.content.Context -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.IoScopeName import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.ui.tab.TabScreen @@ -38,7 +36,9 @@ import com.dimension.maskbook.labs.ui.tab.LabsTabScreen import com.dimension.maskbook.labs.viewmodel.LabsViewModel import com.dimension.maskbook.labs.viewmodel.LuckDropViewModel import com.dimension.maskbook.labs.viewmodel.PluginSettingsViewModel -import org.koin.androidx.viewmodel.dsl.viewModel +import moe.tlaster.koin.viewModel +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.RouteBuilder import org.koin.core.qualifier.named import org.koin.dsl.bind import org.koin.dsl.module @@ -46,7 +46,7 @@ import org.koin.mp.KoinPlatformTools object LabsSetup : ModuleSetup { - override fun NavGraphBuilder.route(navController: NavController) { + override fun RouteBuilder.route(navController: NavController) { generatedRoute(navController) } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/LabsScene.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/LabsScene.kt index 79b64f38..584dcb20 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/LabsScene.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/LabsScene.kt @@ -32,6 +32,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowForwardIos import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -39,7 +40,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.MaskListItem import com.dimension.maskbook.common.ui.widget.MaskScaffold import com.dimension.maskbook.common.ui.widget.MaskSingleLineTopAppBar @@ -50,7 +50,7 @@ import com.dimension.maskbook.labs.R import com.dimension.maskbook.labs.export.model.AppKey import com.dimension.maskbook.labs.viewmodel.AppDisplayData import com.dimension.maskbook.labs.viewmodel.LabsViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @Composable fun LabsScene( @@ -58,7 +58,7 @@ fun LabsScene( onItemClick: (AppKey) -> Unit, ) { val viewModel: LabsViewModel = getViewModel() - val apps by viewModel.apps.observeAsState(initial = emptyList()) + val apps by viewModel.apps.collectAsState(initial = emptyList()) MaskScaffold( topBar = { MaskSingleLineTopAppBar( diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/LabsTransakScene.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/LabsTransakScene.kt index 656af878..f0d8a063 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/LabsTransakScene.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/LabsTransakScene.kt @@ -30,6 +30,7 @@ import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -39,7 +40,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.routeProcessor.annotations.Back @@ -53,7 +53,7 @@ import com.dimension.maskbook.labs.R import com.dimension.maskbook.labs.export.model.TransakConfig import com.dimension.maskbook.labs.route.LabsRoute import com.dimension.maskbook.wallet.export.WalletServices -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get @NavGraphDestination( route = LabsRoute.LabsTransak, @@ -66,7 +66,7 @@ fun LabsTransakScene( @Back onBack: () -> Unit, ) { val repo = get() - val currentWallet by repo.currentWallet.observeAsState(null) + val currentWallet by repo.currentWallet.collectAsState(null) val transakConfig = remember(currentWallet) { TransakConfig( isStaging = BuildConfig.DEBUG, diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/PluginSettingsScene.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/PluginSettingsScene.kt index 32e36bf2..c6edbf6e 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/PluginSettingsScene.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/PluginSettingsScene.kt @@ -54,7 +54,7 @@ import com.dimension.maskbook.labs.R import com.dimension.maskbook.labs.route.LabsRoute import com.dimension.maskbook.labs.viewmodel.PluginDisplayData import com.dimension.maskbook.labs.viewmodel.PluginSettingsViewModel -import org.koin.androidx.compose.viewModel +import moe.tlaster.koin.compose.getViewModel @NavGraphDestination( route = LabsRoute.PluginSettings, @@ -65,7 +65,7 @@ import org.koin.androidx.compose.viewModel fun PluginSettingsScene( @Back onBack: () -> Unit, ) { - val viewModel by viewModel() + val viewModel = getViewModel() val apps by viewModel.apps.collectAsState() val shouldShowPluginSettingsTipDialog by viewModel.shouldShowPluginSettingsTipDialog.collectAsState() MaskScene { diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/redpacket/LuckyDropModal.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/redpacket/LuckyDropModal.kt index c2e89bc3..38fa936f 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/redpacket/LuckyDropModal.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/redpacket/LuckyDropModal.kt @@ -51,7 +51,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import coil.compose.rememberImagePainter import com.dimension.maskbook.common.bigDecimal.BigDecimal import com.dimension.maskbook.common.ext.eventFlow @@ -75,7 +74,8 @@ import com.dimension.maskbook.labs.route.LabsRoute import com.dimension.maskbook.labs.ui.widget.ClaimLoadingIndicator import com.dimension.maskbook.labs.ui.widget.RedPacketClaimButton import com.dimension.maskbook.labs.viewmodel.LuckDropViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf import kotlin.math.pow diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/tab/LabsTabScreen.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/tab/LabsTabScreen.kt index e3b56110..ba506281 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/tab/LabsTabScreen.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/tab/LabsTabScreen.kt @@ -21,13 +21,13 @@ package com.dimension.maskbook.labs.ui.tab import androidx.compose.runtime.Composable -import androidx.navigation.NavController import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.labs.R import com.dimension.maskbook.labs.export.model.AppKey import com.dimension.maskbook.labs.route.LabsRoute import com.dimension.maskbook.labs.ui.scenes.LabsScene +import moe.tlaster.precompose.navigation.NavController class LabsTabScreen : TabScreen { override val route = CommonRoute.Main.Tabs.Labs diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/LabsViewModel.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/LabsViewModel.kt index 4f81641a..e48ec341 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/LabsViewModel.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/LabsViewModel.kt @@ -22,8 +22,6 @@ package com.dimension.maskbook.labs.viewmodel import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.labs.R import com.dimension.maskbook.labs.export.model.AppKey @@ -32,6 +30,8 @@ import com.dimension.maskbook.wallet.export.WalletServices import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope data class AppDisplayData( val key: AppKey, diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/LuckDropViewModel.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/LuckDropViewModel.kt index b764d18c..df9176de 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/LuckDropViewModel.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/LuckDropViewModel.kt @@ -21,8 +21,6 @@ package com.dimension.maskbook.labs.viewmodel import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.exception.NullTransactionReceiptException import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.ext.decodeJson @@ -49,6 +47,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope import org.web3j.abi.FunctionEncoder import kotlin.time.Duration.Companion.seconds diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/PluginSettingsViewModel.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/PluginSettingsViewModel.kt index d80c6747..c4ecdb15 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/PluginSettingsViewModel.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/viewmodel/PluginSettingsViewModel.kt @@ -22,8 +22,6 @@ package com.dimension.maskbook.labs.viewmodel import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.labs.R import com.dimension.maskbook.labs.export.model.AppKey @@ -33,6 +31,8 @@ import com.dimension.maskbook.wallet.export.WalletServices import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope data class PluginDisplayData( val key: AppKey, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt index 5257fed8..359d3dc8 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt @@ -22,12 +22,11 @@ package com.dimension.maskbook.persona import android.content.Context import androidx.compose.animation.ExperimentalAnimationApi -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder import androidx.room.Room import com.dimension.maskbook.common.IoScopeName import com.dimension.maskbook.common.LocalBackupAccount import com.dimension.maskbook.common.ModuleSetup +import com.dimension.maskbook.common.route.navigation import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.persona.data.JSMethod import com.dimension.maskbook.persona.data.JSMethodV2 @@ -76,10 +75,11 @@ import com.dimension.maskbook.persona.viewmodel.register.PhoneRemoteBackupRecove import com.dimension.maskbook.persona.viewmodel.register.RemoteBackupRecoveryViewModelBase import com.dimension.maskbook.persona.viewmodel.social.DisconnectSocialViewModel import com.dimension.maskbook.persona.viewmodel.social.UserNameModalViewModel -import com.google.accompanist.navigation.animation.navigation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor -import org.koin.androidx.viewmodel.dsl.viewModel +import moe.tlaster.koin.viewModel +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.RouteBuilder import org.koin.core.qualifier.named import org.koin.dsl.bind import org.koin.dsl.binds @@ -89,7 +89,7 @@ import org.koin.mp.KoinPlatformTools object PersonaSetup : ModuleSetup { @OptIn(ExperimentalAnimationApi::class) - override fun NavGraphBuilder.route(navController: NavController) { + override fun RouteBuilder.route(navController: NavController) { generatedRoute(navController) navigation( startDestination = PersonaRoute.Register.CreateIdentity.Backup.path, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/RegisterRoute.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/RegisterRoute.kt index 6226c12d..8112e923 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/RegisterRoute.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/RegisterRoute.kt @@ -27,14 +27,15 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.navigation.NavController -import androidx.navigation.navOptions -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navOptions +import com.dimension.maskbook.common.ext.navigate +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable @@ -60,7 +61,8 @@ import com.dimension.maskbook.persona.ui.scenes.register.recovery.RecoveryHomeSc import com.dimension.maskbook.persona.viewmodel.recovery.IdentityViewModel import com.dimension.maskbook.persona.viewmodel.recovery.PrivateKeyViewModel import kotlinx.coroutines.launch -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -205,8 +207,8 @@ fun RegisterRecoveryIdentity( val viewModel: IdentityViewModel = getViewModel { parametersOf(name) } - val identity by viewModel.identity.observeAsState() - val canConfirm by viewModel.canConfirm.observeAsState() + val identity by viewModel.identity.collectAsState() + val canConfirm by viewModel.canConfirm.collectAsState() val from = stringResource(R.string.scene_identity_mnemonic_import_title) val scope = rememberCoroutineScope() IdentityScene( @@ -256,8 +258,8 @@ fun RegisterRecoveryPrivateKey( @Back onBack: () -> Unit, ) { val viewModel: PrivateKeyViewModel = getViewModel() - val privateKey by viewModel.privateKey.observeAsState() - val canConfirm by viewModel.canConfirm.observeAsState() + val privateKey by viewModel.privateKey.collectAsState() + val canConfirm by viewModel.canConfirm.collectAsState() val scope = rememberCoroutineScope() val from = stringResource(R.string.scene_identity_privatekey_import_title) PrivateKeyScene( @@ -343,7 +345,7 @@ fun RegisterRecoveryComplected( }, buttons = { PrimaryButton(onClick = { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona)), navOptions { popUpTo(PersonaRoute.Register.Init) { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt index 2c014adc..643efa49 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt @@ -29,8 +29,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.navigation.NavController -import androidx.navigation.navOptions import com.dimension.maskbook.common.ext.decodeBase64 import com.dimension.maskbook.common.ext.ifNullOrEmpty import com.dimension.maskbook.common.route.CommonRoute @@ -54,7 +52,8 @@ import com.dimension.maskbook.persona.ui.scenes.register.recovery.PersonaAlready import com.dimension.maskbook.persona.viewmodel.recovery.IdentityViewModel import com.dimension.maskbook.persona.viewmodel.recovery.PrivateKeyViewModel import kotlinx.coroutines.launch -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt index 96c671a8..8598aeaa 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt @@ -21,10 +21,8 @@ package com.dimension.maskbook.persona.ui.scenes import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.navigation.NavController -import androidx.navigation.navOptions -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage @@ -33,7 +31,8 @@ import com.dimension.maskbook.common.routeProcessor.annotations.Path import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.ui.scenes.register.BackUpPasswordModal import com.dimension.maskbook.persona.viewmodel.BackUpPasswordViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController @NavGraphDestination( route = PersonaRoute.BackUpPassword.path, @@ -49,8 +48,8 @@ fun BackUpPassword( @Path("target") target: String, ) { val viewModel = getViewModel() - val password by viewModel.password.observeAsState(initial = "") - val passwordValid by viewModel.passwordValid.observeAsState(initial = false) + val password by viewModel.password.collectAsState(initial = "") + val passwordValid by viewModel.passwordValid.collectAsState(initial = false) BackUpPasswordModal( password = password, onPasswordChanged = { viewModel.setPassword(it) }, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/DownloadQrCodeScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/DownloadQrCodeScene.kt index 857b98fb..5741c051 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/DownloadQrCodeScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/DownloadQrCodeScene.kt @@ -25,11 +25,10 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext -import androidx.navigation.NavController -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ext.onFinished import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage @@ -42,7 +41,8 @@ import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.DownloadQrCodeViewModel import kotlinx.coroutines.launch -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -59,8 +59,8 @@ fun DownloadQrCodeScene( val viewModel = getViewModel { parametersOf(DownloadQrCodeViewModel.IdType.valueOf(idType), idBase64) } - val personaQrCode by viewModel.personaQrCode.observeAsState() - val filePickerLaunched by viewModel.filePickerLaunched.observeAsState() + val personaQrCode by viewModel.personaQrCode.collectAsState() + val filePickerLaunched by viewModel.filePickerLaunched.collectAsState() val context = LocalContext.current val scope = rememberCoroutineScope() val inAppNotification = LocalInAppNotification.current diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/ExportPrivateKeyScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/ExportPrivateKeyScene.kt index fe8c2539..34676eba 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/ExportPrivateKeyScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/ExportPrivateKeyScene.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager @@ -40,7 +41,6 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.routeProcessor.annotations.Back @@ -56,7 +56,7 @@ import com.dimension.maskbook.common.ui.widget.button.SecondaryButton import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.ExportPrivateKeyViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @NavGraphDestination( route = PersonaRoute.ExportPrivateKey, @@ -68,7 +68,7 @@ fun ExportPrivateKeyScene( @Back onBack: () -> Unit, ) { val viewModel = getViewModel() - val text by viewModel.privateKey.observeAsState(initial = "") + val text by viewModel.privateKey.collectAsState(initial = "") val annotatedText = buildAnnotatedString { append(stringResource(R.string.scene_persona_export_private_key_tips)) diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/LogoutDialog.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/LogoutDialog.kt index ed0bb321..194dab81 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/LogoutDialog.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/LogoutDialog.kt @@ -30,8 +30,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.navigationComposeDialog import com.dimension.maskbook.common.route.navigationComposeDialogPackage import com.dimension.maskbook.common.routeProcessor.annotations.Back @@ -42,7 +40,8 @@ import com.dimension.maskbook.common.ui.widget.button.SecondaryButton import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.persona.route.PersonaRoute -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get +import moe.tlaster.precompose.navigation.NavController @NavGraphDestination( route = PersonaRoute.Logout, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaInfoScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaInfoScene.kt index 2dbe67c8..dc7270b5 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaInfoScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaInfoScene.kt @@ -82,8 +82,8 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.calculateCurrentOffsetForPage import com.google.accompanist.pager.rememberPagerState -import org.koin.androidx.compose.get -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.get +import moe.tlaster.koin.compose.getViewModel import kotlin.math.absoluteValue private enum class PersonaInfoData(val title: String) { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaMenuScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaMenuScene.kt index 86854b00..4480b9c6 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaMenuScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaMenuScene.kt @@ -46,7 +46,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.dimension.maskbook.common.ext.encodeBase64 import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable @@ -63,7 +62,8 @@ import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.DownloadQrCodeViewModel import com.dimension.maskbook.persona.viewmodel.PersonaMenuViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController @NavGraphDestination( route = PersonaRoute.PersonaMenu, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaScene.kt index 6bcc6ff9..6b097128 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaScene.kt @@ -44,7 +44,7 @@ import com.dimension.maskbook.persona.export.model.Network import com.dimension.maskbook.persona.export.model.PersonaData import com.dimension.maskbook.persona.export.model.SocialData import com.dimension.maskbook.persona.viewmodel.PersonaViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @ExperimentalAnimationApi @Composable diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/RenamePersonaModal.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/RenamePersonaModal.kt index 44ae9a94..c6448774 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/RenamePersonaModal.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/RenamePersonaModal.kt @@ -31,7 +31,6 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage import com.dimension.maskbook.common.routeProcessor.annotations.NavGraphDestination @@ -42,7 +41,8 @@ import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.RenamePersonaViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/SwitchPersonaModal.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/SwitchPersonaModal.kt index 06f693e9..bebf13df 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/SwitchPersonaModal.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/SwitchPersonaModal.kt @@ -33,14 +33,13 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Add import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage @@ -50,7 +49,8 @@ import com.dimension.maskbook.common.ui.widget.MaskSelection import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.SwitchPersonaViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController @NavGraphDestination( route = PersonaRoute.SwitchPersona, @@ -62,8 +62,8 @@ fun SwitchPersonaModal( navController: NavController, ) { val viewModel = getViewModel() - val currentPersonaData by viewModel.current.observeAsState(initial = null) - val items by viewModel.items.observeAsState(initial = emptyList()) + val currentPersonaData by viewModel.current.collectAsState(initial = null) + val items by viewModel.items.collectAsState(initial = emptyList()) MaskModal( title = { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/avatar/PersonaAvatarModal.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/avatar/PersonaAvatarModal.kt index 9b169632..dd35db61 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/avatar/PersonaAvatarModal.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/avatar/PersonaAvatarModal.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage import com.dimension.maskbook.common.routeProcessor.annotations.Back @@ -37,7 +36,8 @@ import com.dimension.maskbook.common.ui.widget.button.MaskListItemButton import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.persona.route.PersonaRoute -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get +import moe.tlaster.precompose.navigation.NavController @NavGraphDestination( route = PersonaRoute.PersonaAvatarModal, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/avatar/SetAvatarScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/avatar/SetAvatarScene.kt index bad9c9fc..0c03d3fe 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/avatar/SetAvatarScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/avatar/SetAvatarScene.kt @@ -56,7 +56,7 @@ import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.utils.ImagePicker import com.dimension.maskbook.persona.viewmodel.avatar.SetAvatarViewModel import com.google.accompanist.permissions.ExperimentalPermissionsApi -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @OptIn(ExperimentalPermissionsApi::class) @NavGraphDestination( diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/post/PostScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/post/PostScene.kt index 1ecb11fe..8bd22151 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/post/PostScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/post/PostScene.kt @@ -36,22 +36,22 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.Pages import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.viewmodel.post.PostViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @OptIn(ExperimentalMaterialApi::class) @Composable fun PostScene() { val viewModel = getViewModel() - val items by viewModel.items.observeAsState(initial = emptyList()) + val items by viewModel.items.collectAsState(initial = emptyList()) if (!items.any()) { EmptyPostScene() } else { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/createidentity/CreateIdentityHost.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/createidentity/CreateIdentityHost.kt index 96ecdf9a..8f0fdede 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/createidentity/CreateIdentityHost.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/createidentity/CreateIdentityHost.kt @@ -26,15 +26,15 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.navigation.NavController import com.dimension.maskbook.common.ext.encodeBase64 import com.dimension.maskbook.common.ext.getNestedNavigationViewModel import com.dimension.maskbook.common.ext.navigate -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable @@ -50,6 +50,7 @@ import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.DownloadQrCodeViewModel import com.dimension.maskbook.persona.viewmodel.register.CreateIdentityViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf private const val GeneratedRouteName = "createIdentityRoute" @@ -71,8 +72,8 @@ fun BackupRoute( .getNestedNavigationViewModel(PersonaRoute.Register.CreateIdentity.Route) { parametersOf(personaName) } - val words by viewModel.words.observeAsState(emptyList()) - val showNext by viewModel.showNext.observeAsState() + val words by viewModel.words.collectAsState(emptyList()) + val showNext by viewModel.showNext.collectAsState() BackupIdentityScene( words = words.map { it.word }, onRefreshWords = { @@ -127,7 +128,7 @@ fun ConfirmRoute( PrimaryButton( modifier = Modifier.fillMaxWidth(), onClick = { - navController.navigate(Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona))) { + navController.navigateUri(Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona))) { launchSingleTop = true if (isWelcome) { popUpTo(PersonaRoute.Register.Init) { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt index ec92d428..3bc8e6fb 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt @@ -55,7 +55,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.dimension.maskbook.common.ext.getNestedNavigationViewModel import com.dimension.maskbook.common.ext.humanizeFileSize import com.dimension.maskbook.common.ext.humanizeTimestamp @@ -87,6 +86,7 @@ import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.recovery.RecoveryLocalViewModel import kotlinx.coroutines.flow.distinctUntilChanged +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf import java.io.File @@ -456,7 +456,7 @@ fun ImportSuccessScene( .getNestedNavigationViewModel(PersonaRoute.Register.Recovery.LocalBackup.Route) { parametersOf(uri, account) } - val meta by viewModel.meta.observeAsState(initial = null) + val meta by viewModel.meta.collectAsState(initial = null) MaskScene { MaskScaffold( topBar = { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/remote/RemoteBackupRecoveryHost.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/remote/RemoteBackupRecoveryHost.kt index 36499622..018e07c4 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/remote/RemoteBackupRecoveryHost.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/remote/RemoteBackupRecoveryHost.kt @@ -32,6 +32,7 @@ import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -40,8 +41,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage import com.dimension.maskbook.common.route.navigationComposeDialog @@ -59,7 +58,8 @@ import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.register.EmailRemoteBackupRecoveryViewModel import com.dimension.maskbook.persona.viewmodel.register.PhoneRemoteBackupRecoveryViewModel import com.dimension.maskbook.persona.viewmodel.register.RemoteBackupRecoveryViewModelBase -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -130,11 +130,11 @@ fun RegisterRecoveryRemoteBackupRecoveryRemoteBackupRecoveryEmailCode( LaunchedEffect(Unit) { viewModel.startCountDown() } - val canSend by viewModel.canSend.observeAsState(initial = false) - val countDown by viewModel.countdown.observeAsState(initial = 60) - val loading by viewModel.loading.observeAsState(initial = false) - val code by viewModel.code.observeAsState(initial = "") - val codeValid by viewModel.codeValid.observeAsState(initial = true) + val canSend by viewModel.canSend.collectAsState(initial = false) + val countDown by viewModel.countdown.collectAsState(initial = 60) + val loading by viewModel.loading.collectAsState(initial = false) + val code by viewModel.code.collectAsState(initial = "") + val codeValid by viewModel.codeValid.collectAsState(initial = true) EmailCodeInputModal( email = email, code = code, @@ -168,9 +168,9 @@ fun RegisterRecoveryRemoteBackupRecoveryRemoteBackupRecoveryEmail( val viewModel = getViewModel { parametersOf(requestNavigate) } - val email by viewModel.value.observeAsState(initial = "") - val emailValid by viewModel.valueValid.observeAsState(initial = true) - val loading by viewModel.loading.observeAsState(initial = false) + val email by viewModel.value.collectAsState(initial = "") + val emailValid by viewModel.valueValid.collectAsState(initial = true) + val loading by viewModel.loading.collectAsState(initial = false) MaskModal( title = { Text(text = stringResource(R.string.scene_restore_titles_recovery_with_email)) @@ -254,11 +254,11 @@ fun RegisterRecoveryRemoteBackupRecoveryRemoteBackupRecoveryPhoneCode( LaunchedEffect(Unit) { viewModel.startCountDown() } - val canSend by viewModel.canSend.observeAsState(initial = false) - val countDown by viewModel.countdown.observeAsState(initial = 60) - val loading by viewModel.loading.observeAsState(initial = false) - val code by viewModel.code.observeAsState(initial = "") - val codeValid by viewModel.codeValid.observeAsState(initial = true) + val canSend by viewModel.canSend.collectAsState(initial = false) + val countDown by viewModel.countdown.collectAsState(initial = 60) + val loading by viewModel.loading.collectAsState(initial = false) + val code by viewModel.code.collectAsState(initial = "") + val codeValid by viewModel.codeValid.collectAsState(initial = true) MaskModal( title = { Text(text = stringResource(R.string.scene_restore_titles_recovery_with_mobile)) @@ -335,10 +335,10 @@ fun RegisterRecoveryRemoteBackupRecoveryRemoteBackupRecoveryPhone( val viewModel = getViewModel { parametersOf(requestNavigate) } - val regionCode by viewModel.regionCode.observeAsState(initial = "+86") - val phone by viewModel.value.observeAsState(initial = "") - val phoneValid by viewModel.valueValid.observeAsState(initial = true) - val loading by viewModel.loading.observeAsState(initial = false) + val regionCode by viewModel.regionCode.collectAsState(initial = "+86") + val phone by viewModel.value.collectAsState(initial = "") + val phoneValid by viewModel.valueValid.collectAsState(initial = true) + val loading by viewModel.loading.collectAsState(initial = false) MaskModal( title = { Text(text = stringResource(R.string.scene_restore_titles_recovery_with_mobile)) diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/ConnectAccountModal.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/ConnectAccountModal.kt index 95202eba..5a48c578 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/ConnectAccountModal.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/ConnectAccountModal.kt @@ -41,8 +41,8 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import coil.compose.rememberImagePainter +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet @@ -56,7 +56,8 @@ import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.model.SocialProfile import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.social.UserNameModalViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -113,7 +114,7 @@ fun ConnectAccountModal( modifier = Modifier.fillMaxWidth(), onClick = { viewModel.done(personaId, name) - navController.navigate(Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona))) + navController.navigateUri(Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona))) }, ) { Text(text = stringResource(R.string.scene_social_connect_button_title)) diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/ConnectSocial.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/ConnectSocial.kt index ba965182..63fd3e97 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/ConnectSocial.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/ConnectSocial.kt @@ -20,12 +20,12 @@ */ package com.dimension.maskbook.persona.ui.scenes.social -import androidx.navigation.NavController import com.dimension.maskbook.common.ext.navigateToExtension import com.dimension.maskbook.common.ext.toSite import com.dimension.maskbook.persona.export.model.PlatformType import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.persona.route.PersonaRoute +import moe.tlaster.precompose.navigation.NavController fun connectSocial( controller: NavController, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/DisconnectSocialDialog.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/DisconnectSocialDialog.kt index 16384da5..4e937c06 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/DisconnectSocialDialog.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/DisconnectSocialDialog.kt @@ -30,7 +30,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.dimension.maskbook.common.route.navigationComposeDialog import com.dimension.maskbook.common.route.navigationComposeDialogPackage import com.dimension.maskbook.common.routeProcessor.annotations.Back @@ -44,7 +43,8 @@ import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.export.model.PlatformType import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.viewmodel.social.DisconnectSocialViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController @NavGraphDestination( route = PersonaRoute.DisconnectSocial.path, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/SelectPlatformModal.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/SelectPlatformModal.kt index 6d7715e1..0b7c72dd 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/SelectPlatformModal.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/social/SelectPlatformModal.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage import com.dimension.maskbook.common.routeProcessor.annotations.NavGraphDestination @@ -45,7 +44,8 @@ import com.dimension.maskbook.persona.model.platform import com.dimension.maskbook.persona.model.title import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.persona.route.PersonaRoute -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get +import moe.tlaster.precompose.navigation.NavController private val items = listOf( Network.Twitter, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt index d598fbb4..13800123 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt @@ -23,8 +23,7 @@ package com.dimension.maskbook.persona.ui.tab import android.net.Uri import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable -import androidx.navigation.NavController -import androidx.navigation.navOptions +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.ui.tab.TabScreen @@ -35,7 +34,8 @@ import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.persona.ui.scenes.PersonaScene import com.dimension.maskbook.persona.ui.scenes.social.connectSocial -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get +import moe.tlaster.precompose.navigation.NavController class PersonasTabScreen : TabScreen { override val route = CommonRoute.Main.Tabs.Persona @@ -49,10 +49,10 @@ class PersonasTabScreen : TabScreen { PersonaScene( onBack = onBack, onPersonaCreateClick = { - navController.navigate(Uri.parse(Deeplinks.Persona.Register.WelcomeCreatePersona)) + navController.navigateUri(Uri.parse(Deeplinks.Persona.Register.WelcomeCreatePersona)) }, onPersonaRecoveryClick = { - navController.navigate(Uri.parse(Deeplinks.Persona.Recovery)) + navController.navigateUri(Uri.parse(Deeplinks.Persona.Recovery)) }, onPersonaNameClick = { navController.navigate(PersonaRoute.PersonaMenu) @@ -83,7 +83,7 @@ class PersonasTabScreen : TabScreen { onSocialItemClick = { _, social -> social.network.toPlatform()?.let { repository.setPlatform(it) - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.WebContent(null)), navOptions { launchSingleTop = true diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/BackUpPasswordViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/BackUpPasswordViewModel.kt index d1ea3e54..c70575a3 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/BackUpPasswordViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/BackUpPasswordViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.persona.viewmodel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.util.BiometricAuthenticator import com.dimension.maskbook.common.viewmodel.BiometricViewModel import com.dimension.maskbook.setting.export.SettingServices import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.viewModelScope class BackUpPasswordViewModel( settingsRepository: SettingServices, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/DownloadQrCodeViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/DownloadQrCodeViewModel.kt index 2f860515..eb4540f6 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/DownloadQrCodeViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/DownloadQrCodeViewModel.kt @@ -31,8 +31,6 @@ import android.graphics.pdf.PdfDocument import android.graphics.pdf.PdfDocument.PageInfo import android.net.Uri import android.util.Base64 -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.ext.decodeBase64 import com.dimension.maskbook.common.ext.encodeBase64 @@ -45,6 +43,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.withContext +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class DownloadQrCodeViewModel( private val idType: IdType, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/ExportPrivateKeyViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/ExportPrivateKeyViewModel.kt index 245b5b33..ab6f2164 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/ExportPrivateKeyViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/ExportPrivateKeyViewModel.kt @@ -20,12 +20,12 @@ */ package com.dimension.maskbook.persona.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.repository.IPersonaRepository import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class ExportPrivateKeyViewModel( private val repository: IPersonaRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/PersonaMenuViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/PersonaMenuViewModel.kt index 99a197e6..14fc11b3 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/PersonaMenuViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/PersonaMenuViewModel.kt @@ -20,11 +20,11 @@ */ package com.dimension.maskbook.persona.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.setting.export.SettingServices +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class PersonaMenuViewModel( private val repository: IPersonaRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/PersonaViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/PersonaViewModel.kt index 4d300991..380f21aa 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/PersonaViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/PersonaViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.persona.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.export.model.PersonaData import com.dimension.maskbook.persona.repository.IPersonaRepository @@ -30,6 +28,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class PersonaViewModel( private val personaRepository: IPersonaRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/RenamePersonaViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/RenamePersonaViewModel.kt index 40ac2bc8..43ad451e 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/RenamePersonaViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/RenamePersonaViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.persona.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.datasource.DbPersonaDataSource import com.dimension.maskbook.persona.repository.IPersonaRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class RenamePersonaViewModel( private val personaRepository: IPersonaRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/SwitchPersonaViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/SwitchPersonaViewModel.kt index 25c5d699..1e99cacf 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/SwitchPersonaViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/SwitchPersonaViewModel.kt @@ -20,12 +20,12 @@ */ package com.dimension.maskbook.persona.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.datasource.DbPersonaDataSource import com.dimension.maskbook.persona.export.model.PersonaData import com.dimension.maskbook.persona.repository.IPersonaRepository +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class SwitchPersonaViewModel( private val personaRepository: IPersonaRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/avatar/SetAvatarViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/avatar/SetAvatarViewModel.kt index cdaa66a3..798fe699 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/avatar/SetAvatarViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/avatar/SetAvatarViewModel.kt @@ -21,9 +21,9 @@ package com.dimension.maskbook.persona.viewmodel.avatar import android.net.Uri -import androidx.lifecycle.ViewModel import com.dimension.maskbook.persona.repository.IPersonaRepository import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel class SetAvatarViewModel( private val repository: IPersonaRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/contacts/ContactsViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/contacts/ContactsViewModel.kt index db7946f9..ef48f338 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/contacts/ContactsViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/contacts/ContactsViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.persona.viewmodel.contacts -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.repository.IContactsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class ContactsViewModel( repository: IContactsRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/post/PostViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/post/PostViewModel.kt index 47552932..1190eb25 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/post/PostViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/post/PostViewModel.kt @@ -20,14 +20,14 @@ */ package com.dimension.maskbook.persona.viewmodel.post -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.export.model.PersonaData import com.dimension.maskbook.persona.model.PostData import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.persona.repository.IPostRepository import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class PostViewModel( repository: IPostRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/IdentityViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/IdentityViewModel.kt index 24626243..956c9b5d 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/IdentityViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/IdentityViewModel.kt @@ -20,14 +20,14 @@ */ package com.dimension.maskbook.persona.viewmodel.recovery -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.export.PersonaServices import com.dimension.maskbook.wallet.export.WalletServices import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class IdentityViewModel( private val personaServices: PersonaServices, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/PrivateKeyViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/PrivateKeyViewModel.kt index a15d402b..7068742c 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/PrivateKeyViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/PrivateKeyViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.persona.viewmodel.recovery -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.export.PersonaServices import com.dimension.maskbook.wallet.export.WalletServices import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class PrivateKeyViewModel( private val personaServices: PersonaServices, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/RecoveryLocalViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/RecoveryLocalViewModel.kt index 79d1331d..5054bdb7 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/RecoveryLocalViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/recovery/RecoveryLocalViewModel.kt @@ -22,8 +22,6 @@ package com.dimension.maskbook.persona.viewmodel.recovery import android.content.ContentResolver import android.net.Uri -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.BackupServices @@ -33,6 +31,8 @@ import com.dimension.maskbook.setting.export.model.BackupMetaFile import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class RecoveryLocalViewModel( private val backupServices: BackupServices, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/register/CreateIdentityViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/register/CreateIdentityViewModel.kt index c99230fa..5a859451 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/register/CreateIdentityViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/register/CreateIdentityViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.persona.viewmodel.register -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.viewmodel.BaseMnemonicPhraseViewModel import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.wallet.export.WalletServices import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.viewModelScope class CreateIdentityViewModel( private val personaName: String, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/register/RemoteBackupRecoveryViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/register/RemoteBackupRecoveryViewModel.kt index e232d9f4..752fdb40 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/register/RemoteBackupRecoveryViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/register/RemoteBackupRecoveryViewModel.kt @@ -21,14 +21,14 @@ package com.dimension.maskbook.persona.viewmodel.register import android.os.CountDownTimer -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.BackupServices import com.dimension.maskbook.setting.export.model.BackupFileMeta import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class PhoneRemoteBackupRecoveryViewModel( requestNavigate: (NavigateArgs) -> Unit, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/social/DisconnectSocialViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/social/DisconnectSocialViewModel.kt index d5ee172b..0b2815fd 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/social/DisconnectSocialViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/social/DisconnectSocialViewModel.kt @@ -20,8 +20,8 @@ */ package com.dimension.maskbook.persona.viewmodel.social -import androidx.lifecycle.ViewModel import com.dimension.maskbook.persona.repository.IPersonaRepository +import moe.tlaster.precompose.viewmodel.ViewModel class DisconnectSocialViewModel( private val repository: IPersonaRepository, diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/social/UserNameModalViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/social/UserNameModalViewModel.kt index c9ceb8a6..b53d1a47 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/social/UserNameModalViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/social/UserNameModalViewModel.kt @@ -20,12 +20,12 @@ */ package com.dimension.maskbook.persona.viewmodel.social -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.model.SocialProfile import com.dimension.maskbook.persona.repository.IPersonaRepository import kotlinx.coroutines.flow.MutableStateFlow +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class UserNameModalViewModel( private val personaRepository: IPersonaRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt index e57e410b..8d7843bd 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt @@ -21,11 +21,9 @@ package com.dimension.maskbook.setting import android.content.Context -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.navigation import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.retrofit.retrofit +import com.dimension.maskbook.common.route.navigation import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.setting.SettingSetup.route import com.dimension.maskbook.setting.data.JSDataSource @@ -54,13 +52,15 @@ import com.dimension.maskbook.setting.viewmodel.LanguageSettingsViewModel import com.dimension.maskbook.setting.viewmodel.PaymentPasswordSettingsViewModel import com.dimension.maskbook.setting.viewmodel.PhoneBackupViewModel import com.dimension.maskbook.setting.viewmodel.PhoneSetupViewModel -import org.koin.androidx.viewmodel.dsl.viewModel +import moe.tlaster.koin.viewModel +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.RouteBuilder import org.koin.dsl.bind import org.koin.dsl.module import org.koin.mp.KoinPlatformTools object SettingSetup : ModuleSetup { - override fun NavGraphBuilder.route(navController: NavController) { + override fun RouteBuilder.route(navController: NavController) { generatedRoute(navController) navigation( startDestination = SettingRoute.BackupData.BackupLocal.Backup, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/BackupRoute.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/BackupRoute.kt index d4cb2427..9a08c94e 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/BackupRoute.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/BackupRoute.kt @@ -38,6 +38,7 @@ import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -49,8 +50,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navigateWithPopSelf import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage @@ -81,8 +81,9 @@ import com.dimension.maskbook.setting.viewmodel.BackupMergeConfirmViewModel import com.dimension.maskbook.setting.viewmodel.EmailBackupViewModel import com.dimension.maskbook.setting.viewmodel.PhoneBackupViewModel import kotlinx.coroutines.launch -import org.koin.androidx.compose.get -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.get +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -172,13 +173,7 @@ fun BackupDataBackupCould( navController.popBackStack() }, onConfirm = { - navController.navigate(SettingRoute.BackupData.BackupData_BackupCloud_Execute(it, type, value, code)) { - navController.currentBackStackEntry?.let { backStackEntry -> - popUpTo(backStackEntry.destination.id) { - inclusive = true - } - } - } + navController.navigateWithPopSelf(SettingRoute.BackupData.BackupData_BackupCloud_Execute(it, type, value, code)) } ) } @@ -206,21 +201,9 @@ fun BackupDataBackupCouldExecute( withWallet = withWallet, ) if (result) { - navController.navigate(SettingRoute.BackupData.BackupData_Cloud_Success) { - navController.currentBackStackEntry?.let { backStackEntry -> - popUpTo(backStackEntry.destination.id) { - inclusive = true - } - } - } + navController.navigateWithPopSelf(SettingRoute.BackupData.BackupData_Cloud_Success) } else { - navController.navigate(SettingRoute.BackupData.BackupData_Cloud_Failed) { - navController.currentBackStackEntry?.let { backStackEntry -> - popUpTo(backStackEntry.destination.id) { - inclusive = true - } - } - } + navController.navigateWithPopSelf(SettingRoute.BackupData.BackupData_Cloud_Failed) } } @@ -356,9 +339,9 @@ fun BackupDataBackupMergeConfirm( parametersOf(onDone) } - val passwordValid by viewModel.passwordValid.observeAsState(initial = false) - val loading by viewModel.loading.observeAsState(initial = false) - val password by viewModel.backupPassword.observeAsState(initial = "") + val passwordValid by viewModel.passwordValid.collectAsState(initial = false) + val loading by viewModel.loading.collectAsState(initial = false) + val password by viewModel.backupPassword.collectAsState(initial = "") MaskModal( title = { @@ -497,15 +480,15 @@ fun BackupSelectionEmail( val scope = rememberCoroutineScope() val repository = get() - val persona by repository.currentPersona.observeAsState(initial = null) + val persona by repository.currentPersona.collectAsState(initial = null) val phone = persona?.phone val viewModel = getViewModel() - val code by viewModel.code.observeAsState() - val valid by viewModel.codeValid.observeAsState() - val loading by viewModel.loading.observeAsState() - val canSend by viewModel.canSend.observeAsState() - val countDown by viewModel.countdown.observeAsState() + val code by viewModel.code.collectAsState() + val valid by viewModel.codeValid.collectAsState() + val loading by viewModel.loading.collectAsState() + val canSend by viewModel.canSend.collectAsState() + val countDown by viewModel.countdown.collectAsState() LaunchedEffect(Unit) { viewModel.startCountDown() @@ -595,15 +578,15 @@ fun BackupSelectionPhone( val scope = rememberCoroutineScope() val repository = get() - val persona by repository.currentPersona.observeAsState(initial = null) + val persona by repository.currentPersona.collectAsState(initial = null) val email = persona?.email val viewModel = getViewModel() - val code by viewModel.code.observeAsState() - val canSend by viewModel.canSend.observeAsState() - val valid by viewModel.codeValid.observeAsState() - val countDown by viewModel.countdown.observeAsState() - val loading by viewModel.loading.observeAsState() + val code by viewModel.code.collectAsState() + val canSend by viewModel.canSend.collectAsState() + val valid by viewModel.codeValid.collectAsState() + val countDown by viewModel.countdown.collectAsState() + val loading by viewModel.loading.collectAsState() LaunchedEffect(Unit) { viewModel.sendCodeNow(phone) @@ -692,7 +675,7 @@ fun BackupSelection( navController: NavController, ) { val repository = get() - val persona by repository.currentPersona.observeAsState(initial = null) + val persona by repository.currentPersona.collectAsState(initial = null) BackupSelectionModal( onLocal = { navController.navigate(SettingRoute.BackupData.BackupLocal.Backup) @@ -721,7 +704,7 @@ fun BackupDataPassword( navController: NavController, ) { val repository = get() - val currentPassword by repository.backupPassword.observeAsState(initial = "") + val currentPassword by repository.backupPassword.collectAsState(initial = "") var password by remember { mutableStateOf("") } BackupPasswordInputModal( password = password, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/SettingsRoute.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/SettingsRoute.kt index daf6561d..60c65fa2 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/SettingsRoute.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/SettingsRoute.kt @@ -25,14 +25,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.navigation.NavController -import com.dimension.maskbook.common.ext.observeAsState -import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage @@ -55,7 +53,8 @@ import com.dimension.maskbook.setting.ui.scenes.PhoneInputModal import com.dimension.maskbook.setting.viewmodel.EmailSetupViewModel import com.dimension.maskbook.setting.viewmodel.PhoneSetupViewModel import kotlinx.coroutines.launch -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController @NavGraphDestination( route = SettingRoute.SetupPasswordDialog, @@ -266,9 +265,9 @@ fun SettingsChangeEmailSetup( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val email by viewModel.value.observeAsState() - val emailValid by viewModel.valueValid.observeAsState() - val loading by viewModel.loading.observeAsState() + val email by viewModel.value.collectAsState() + val emailValid by viewModel.valueValid.collectAsState() + val loading by viewModel.loading.collectAsState() EmailInputModal( email = email, onEmailChange = { viewModel.setValue(it) }, @@ -300,11 +299,11 @@ fun SettingsChangeEmailSetupCode( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val code by viewModel.code.observeAsState() - val valid by viewModel.codeValid.observeAsState() - val loading by viewModel.loading.observeAsState() - val canSend by viewModel.canSend.observeAsState() - val countDown by viewModel.countdown.observeAsState() + val code by viewModel.code.collectAsState() + val valid by viewModel.codeValid.collectAsState() + val loading by viewModel.loading.collectAsState() + val canSend by viewModel.canSend.collectAsState() + val countDown by viewModel.countdown.collectAsState() LaunchedEffect(Unit) { viewModel.sendCodeNow(email) @@ -385,11 +384,11 @@ fun SettingsChangeEmailChangeCode( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val code by viewModel.code.observeAsState() - val valid by viewModel.codeValid.observeAsState() - val loading by viewModel.loading.observeAsState() - val canSend by viewModel.canSend.observeAsState() - val countDown by viewModel.countdown.observeAsState() + val code by viewModel.code.collectAsState() + val valid by viewModel.codeValid.collectAsState() + val loading by viewModel.loading.collectAsState() + val canSend by viewModel.canSend.collectAsState() + val countDown by viewModel.countdown.collectAsState() EmailCodeInputModal( email = email, @@ -428,9 +427,9 @@ fun SettingsChangeEmailChangeNew( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val email by viewModel.value.observeAsState() - val emailValid by viewModel.valueValid.observeAsState() - val loading by viewModel.loading.observeAsState() + val email by viewModel.value.collectAsState() + val emailValid by viewModel.valueValid.collectAsState() + val loading by viewModel.loading.collectAsState() EmailInputModal( email = email, onEmailChange = { viewModel.setValue(it) }, @@ -462,11 +461,11 @@ fun SettingsChangeEmailChangeNewCode( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val code by viewModel.code.observeAsState() - val valid by viewModel.codeValid.observeAsState() - val loading by viewModel.loading.observeAsState() - val canSend by viewModel.canSend.observeAsState() - val countDown by viewModel.countdown.observeAsState() + val code by viewModel.code.collectAsState() + val valid by viewModel.codeValid.collectAsState() + val loading by viewModel.loading.collectAsState() + val canSend by viewModel.canSend.collectAsState() + val countDown by viewModel.countdown.collectAsState() LaunchedEffect(Unit) { viewModel.sendCodeNow(email) @@ -545,10 +544,10 @@ fun SettingsChangePhoneSetup( ) { val viewModel = getViewModel() - val regionCode by viewModel.regionCode.observeAsState() - val phone by viewModel.value.observeAsState() - val valid by viewModel.valueValid.observeAsState() - val loading by viewModel.loading.observeAsState() + val regionCode by viewModel.regionCode.collectAsState() + val phone by viewModel.value.collectAsState() + val valid by viewModel.valueValid.collectAsState() + val loading by viewModel.loading.collectAsState() PhoneInputModal( regionCode = regionCode, onRegionCodeChange = { viewModel.setRegionCode(it) }, @@ -580,11 +579,11 @@ fun SettingsChangePhoneSetupCode( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val code by viewModel.code.observeAsState() - val canSend by viewModel.canSend.observeAsState() - val valid by viewModel.codeValid.observeAsState() - val countDown by viewModel.countdown.observeAsState() - val loading by viewModel.loading.observeAsState() + val code by viewModel.code.collectAsState() + val canSend by viewModel.canSend.collectAsState() + val valid by viewModel.codeValid.collectAsState() + val countDown by viewModel.countdown.collectAsState() + val loading by viewModel.loading.collectAsState() LaunchedEffect(Unit) { viewModel.sendCodeNow(phone) @@ -661,11 +660,11 @@ fun SettingsChangePhoneChangeCode( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val code by viewModel.code.observeAsState() - val canSend by viewModel.canSend.observeAsState() - val valid by viewModel.codeValid.observeAsState() - val countDown by viewModel.countdown.observeAsState() - val loading by viewModel.loading.observeAsState() + val code by viewModel.code.collectAsState() + val canSend by viewModel.canSend.collectAsState() + val valid by viewModel.codeValid.collectAsState() + val countDown by viewModel.countdown.collectAsState() + val loading by viewModel.loading.collectAsState() PhoneCodeInputModal( phone = phone, @@ -704,10 +703,10 @@ fun SettingsChangePhoneChangeNew( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val regionCode by viewModel.regionCode.observeAsState() - val phone by viewModel.value.observeAsState() - val valid by viewModel.valueValid.observeAsState() - val loading by viewModel.loading.observeAsState() + val regionCode by viewModel.regionCode.collectAsState() + val phone by viewModel.value.collectAsState() + val valid by viewModel.valueValid.collectAsState() + val loading by viewModel.loading.collectAsState() PhoneInputModal( regionCode = regionCode, onRegionCodeChange = { viewModel.setRegionCode(it) }, @@ -744,11 +743,11 @@ fun SettingsChangePhoneChangeCodeNew( val scope = rememberCoroutineScope() val viewModel = getViewModel() - val code by viewModel.code.observeAsState() - val canSend by viewModel.canSend.observeAsState() - val valid by viewModel.codeValid.observeAsState() - val countDown by viewModel.countdown.observeAsState() - val loading by viewModel.loading.observeAsState() + val code by viewModel.code.collectAsState() + val canSend by viewModel.canSend.collectAsState() + val valid by viewModel.codeValid.collectAsState() + val countDown by viewModel.countdown.collectAsState() + val loading by viewModel.loading.collectAsState() LaunchedEffect(Unit) { viewModel.sendCodeNow(phone) diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/AppearanceSettings.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/AppearanceSettings.kt index 98ab1b5e..5c94d66c 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/AppearanceSettings.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/AppearanceSettings.kt @@ -23,15 +23,15 @@ package com.dimension.maskbook.setting.ui.scenes import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.res.stringResource -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.MaskModal import com.dimension.maskbook.common.ui.widget.MaskSelection import com.dimension.maskbook.localization.R import com.dimension.maskbook.setting.export.model.Appearance import com.dimension.maskbook.setting.viewmodel.AppearanceSettingsViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel val appearanceMap = mapOf( Appearance.default to R.string.scene_setting_detail_automatic, @@ -44,7 +44,7 @@ fun AppearanceSettings( onBack: () -> Unit, ) { val viewModel: AppearanceSettingsViewModel = getViewModel() - val appearance by viewModel.appearance.observeAsState(initial = Appearance.default) + val appearance by viewModel.appearance.collectAsState(initial = Appearance.default) MaskModal { Column { appearanceMap.forEach { diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/ChangeBackUpPasswordModal.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/ChangeBackUpPasswordModal.kt index 1a67b2ea..e8dd4899 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/ChangeBackUpPasswordModal.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/ChangeBackUpPasswordModal.kt @@ -31,18 +31,18 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.MaskModal import com.dimension.maskbook.common.ui.widget.MaskPasswordInputField import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.localization.R import com.dimension.maskbook.setting.viewmodel.BackupPasswordSettingsViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @OptIn(ExperimentalAnimationApi::class) @Composable @@ -51,13 +51,13 @@ fun ChangeBackUpPasswordModal( ) { val viewModel: BackupPasswordSettingsViewModel = getViewModel() - val currentPassword by viewModel.currentPassword.observeAsState("") - val isNext by viewModel.isNext.observeAsState(false) - val password by viewModel.password.observeAsState(initial = "") - val newPassword by viewModel.newPassword.observeAsState(initial = "") - val newPasswordConfirm by viewModel.newPasswordConfirm.observeAsState(initial = "") - val confirmPassword by viewModel.confirmPassword.observeAsState(false) - val confirmNewPassword by viewModel.confirmNewPassword.observeAsState(false) + val currentPassword by viewModel.currentPassword.collectAsState("") + val isNext by viewModel.isNext.collectAsState(false) + val password by viewModel.password.collectAsState(initial = "") + val newPassword by viewModel.newPassword.collectAsState(initial = "") + val newPasswordConfirm by viewModel.newPasswordConfirm.collectAsState(initial = "") + val confirmPassword by viewModel.confirmPassword.collectAsState(false) + val confirmNewPassword by viewModel.confirmNewPassword.collectAsState(false) MaskModal( title = { diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/ChangePaymentPasswordModal.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/ChangePaymentPasswordModal.kt index 87547675..7ebeee62 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/ChangePaymentPasswordModal.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/ChangePaymentPasswordModal.kt @@ -31,18 +31,18 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.MaskModal import com.dimension.maskbook.common.ui.widget.MaskPasswordInputField import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.localization.R import com.dimension.maskbook.setting.viewmodel.PaymentPasswordSettingsViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @OptIn(ExperimentalAnimationApi::class) @Composable @@ -51,12 +51,12 @@ fun ChangePaymentPasswordModal( ) { val viewModel: PaymentPasswordSettingsViewModel = getViewModel() - val isNext by viewModel.isNext.observeAsState(false) - val password by viewModel.password.observeAsState(initial = "") - val newPassword by viewModel.newPassword.observeAsState(initial = "") - val newPasswordConfirm by viewModel.newPasswordConfirm.observeAsState(initial = "") - val confirmPassword by viewModel.confirmPassword.observeAsState(false) - val confirmNewPassword by viewModel.confirmNewPassword.observeAsState(false) + val isNext by viewModel.isNext.collectAsState(false) + val password by viewModel.password.collectAsState(initial = "") + val newPassword by viewModel.newPassword.collectAsState(initial = "") + val newPasswordConfirm by viewModel.newPasswordConfirm.collectAsState(initial = "") + val confirmPassword by viewModel.confirmPassword.collectAsState(false) + val confirmNewPassword by viewModel.confirmNewPassword.collectAsState(false) MaskModal( title = { diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/DataSourceSettings.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/DataSourceSettings.kt index 8d65dd4b..6218562c 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/DataSourceSettings.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/DataSourceSettings.kt @@ -23,13 +23,13 @@ package com.dimension.maskbook.setting.ui.scenes import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.MaskModal import com.dimension.maskbook.common.ui.widget.MaskSelection import com.dimension.maskbook.setting.export.model.DataProvider import com.dimension.maskbook.setting.viewmodel.DataSourceSettingsViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel val dataProviderMap = mapOf( DataProvider.COIN_GECKO to "CoinGecko", @@ -42,7 +42,7 @@ fun DataSourceSettings( onBack: () -> Unit, ) { val viewModel: DataSourceSettingsViewModel = getViewModel() - val dataProvider by viewModel.dataProvider.observeAsState(initial = DataProvider.COIN_GECKO) + val dataProvider by viewModel.dataProvider.collectAsState(initial = DataProvider.COIN_GECKO) MaskModal { Column { dataProviderMap.forEach { diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/LanguageSettings.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/LanguageSettings.kt index f9b49c07..c59d6ba4 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/LanguageSettings.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/LanguageSettings.kt @@ -23,13 +23,13 @@ package com.dimension.maskbook.setting.ui.scenes import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.MaskModal import com.dimension.maskbook.common.ui.widget.MaskSelection import com.dimension.maskbook.setting.export.model.Language import com.dimension.maskbook.setting.viewmodel.LanguageSettingsViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel val languageMap = Language.values().map { it to it.value @@ -40,7 +40,7 @@ fun LanguageSettings( onBack: () -> Unit, ) { val viewModel: LanguageSettingsViewModel = getViewModel() - val language by viewModel.language.observeAsState(initial = Language.auto) + val language by viewModel.language.collectAsState(initial = Language.auto) MaskModal { Column { languageMap.forEach { diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/SettingsScene.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/SettingsScene.kt index d87e45c4..32cfdbb8 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/SettingsScene.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/SettingsScene.kt @@ -44,6 +44,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowForwardIos import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -52,8 +53,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.ui.widget.IosSwitch import com.dimension.maskbook.common.ui.widget.MaskCard @@ -70,8 +70,9 @@ import com.dimension.maskbook.setting.export.model.DataProvider import com.dimension.maskbook.setting.export.model.Language import com.dimension.maskbook.setting.repository.ISettingsRepository import com.dimension.maskbook.setting.route.SettingRoute -import org.koin.androidx.compose.get -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.get +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController @OptIn(ExperimentalMaterialApi::class) @Composable @@ -80,14 +81,14 @@ fun SettingsScene( onBack: () -> Unit, ) { val repository = get() - val language by repository.language.observeAsState(initial = Language.auto) - val appearance by repository.appearance.observeAsState(initial = Appearance.default) - val dataProvider by repository.dataProvider.observeAsState(initial = DataProvider.UNISWAP_INFO) - val backupPassword by repository.backupPassword.observeAsState(initial = "") - val paymentPassword by repository.paymentPassword.observeAsState(initial = "") - val biometricEnabled by repository.biometricEnabled.observeAsState(initial = false) + val language by repository.language.collectAsState(initial = Language.auto) + val appearance by repository.appearance.collectAsState(initial = Appearance.default) + val dataProvider by repository.dataProvider.collectAsState(initial = DataProvider.UNISWAP_INFO) + val backupPassword by repository.backupPassword.collectAsState(initial = "") + val paymentPassword by repository.paymentPassword.collectAsState(initial = "") + val biometricEnabled by repository.biometricEnabled.collectAsState(initial = false) val personaRepository = get() - val persona by personaRepository.currentPersona.observeAsState(initial = null) + val persona by personaRepository.currentPersona.collectAsState(initial = null) val biometricEnableViewModel = getViewModel() val context = LocalContext.current MaskScaffold( @@ -192,7 +193,7 @@ fun SettingsScene( title = stringResource(R.string.scene_setting_backup_recovery_restore_data), icon = R.drawable.ic_settings_restore_data, onClick = { - navController.navigate(Uri.parse(Deeplinks.Persona.Recovery)) + navController.navigateUri(Uri.parse(Deeplinks.Persona.Recovery)) } ) SettingsDivider() diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupCloudScene.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupCloudScene.kt index 06393953..6c1cf2e4 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupCloudScene.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupCloudScene.kt @@ -33,13 +33,13 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.Checkbox import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.BackMetaDisplay import com.dimension.maskbook.common.ui.widget.MaskPasswordInputField import com.dimension.maskbook.common.ui.widget.MaskScaffold @@ -52,7 +52,7 @@ import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.common.ui.widget.button.clickable import com.dimension.maskbook.localization.R import com.dimension.maskbook.setting.viewmodel.BackupCloudViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @Composable fun BackupCloudScene( @@ -60,12 +60,12 @@ fun BackupCloudScene( onConfirm: (withWallet: Boolean) -> Unit, ) { val viewModel = getViewModel() - val meta by viewModel.meta.observeAsState(initial = null) - val withWallet by viewModel.withLocalWallet.observeAsState(initial = false) - val backupPassword by viewModel.backupPassword.observeAsState(initial = "") - val backupPasswordValid by viewModel.backupPasswordValid.observeAsState(initial = false) - val paymentPassword by viewModel.paymentPassword.observeAsState(initial = "") - val paymentPasswordValid by viewModel.paymentPasswordValid.observeAsState(initial = false) + val meta by viewModel.meta.collectAsState(initial = null) + val withWallet by viewModel.withLocalWallet.collectAsState(initial = false) + val backupPassword by viewModel.backupPassword.collectAsState(initial = "") + val backupPasswordValid by viewModel.backupPasswordValid.collectAsState(initial = false) + val paymentPassword by viewModel.paymentPassword.collectAsState(initial = "") + val paymentPasswordValid by viewModel.paymentPasswordValid.collectAsState(initial = false) MaskScene { MaskScaffold( topBar = { diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupLocalScene.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupLocalScene.kt index 7b2fd228..8d31ca53 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupLocalScene.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupLocalScene.kt @@ -46,9 +46,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.dimension.maskbook.common.ext.getNestedNavigationViewModel -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.routeProcessor.annotations.Back @@ -68,6 +65,7 @@ import com.dimension.maskbook.setting.route.SettingRoute import com.dimension.maskbook.setting.viewmodel.BackupLocalViewModel import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import moe.tlaster.precompose.navigation.NavController private const val GeneratedRouteName = "backupLocalRoute" @@ -145,12 +143,12 @@ fun BackupLocalScene( navController: NavController, ) { val viewModel: BackupLocalViewModel = navController.getNestedNavigationViewModel(SettingRoute.BackupData.BackupLocal.Route) - val meta by viewModel.meta.observeAsState(initial = null) - val password by viewModel.password.observeAsState(initial = "") - val backupPasswordValid by viewModel.backupPasswordValid.observeAsState(initial = false) - val withWallet by viewModel.withWallet.observeAsState(initial = false) - val paymentPassword by viewModel.paymentPassword.observeAsState(initial = "") - val paymentPasswordValid by viewModel.paymentPasswordValid.observeAsState(initial = false) + val meta by viewModel.meta.collectAsState(initial = null) + val password by viewModel.password.collectAsState(initial = "") + val backupPasswordValid by viewModel.backupPasswordValid.collectAsState(initial = false) + val withWallet by viewModel.withWallet.collectAsState(initial = false) + val paymentPassword by viewModel.paymentPassword.collectAsState(initial = "") + val paymentPasswordValid by viewModel.paymentPasswordValid.collectAsState(initial = false) MaskScene { MaskScaffold( topBar = { diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/tab/SettingsTabScreen.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/tab/SettingsTabScreen.kt index 205f6e28..3c5ac956 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/tab/SettingsTabScreen.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/tab/SettingsTabScreen.kt @@ -21,11 +21,11 @@ package com.dimension.maskbook.setting.ui.tab import androidx.compose.runtime.Composable -import androidx.navigation.NavController import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.setting.R import com.dimension.maskbook.setting.ui.scenes.SettingsScene +import moe.tlaster.precompose.navigation.NavController class SettingsTabScreen : TabScreen { override val route = CommonRoute.Main.Tabs.Setting diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/AppearanceSettingsViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/AppearanceSettingsViewModel.kt index 67a2bd78..559f0332 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/AppearanceSettingsViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/AppearanceSettingsViewModel.kt @@ -20,11 +20,11 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.model.Appearance import com.dimension.maskbook.setting.repository.ISettingsRepository +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class AppearanceSettingsViewModel( private val repository: ISettingsRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupCloudExecuteViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupCloudExecuteViewModel.kt index a292ad32..134f8552 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupCloudExecuteViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupCloudExecuteViewModel.kt @@ -20,12 +20,12 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.ViewModel import com.dimension.maskbook.persona.export.PersonaServices import com.dimension.maskbook.setting.repository.BackupRepository import com.dimension.maskbook.setting.repository.ISettingsRepository import com.dimension.maskbook.setting.services.model.AccountType import kotlinx.coroutines.flow.firstOrNull +import moe.tlaster.precompose.viewmodel.ViewModel class BackupCloudExecuteViewModel( private val settingsRepository: ISettingsRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupCloudViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupCloudViewModel.kt index f9c1f0a7..520f2bf0 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupCloudViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupCloudViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flow +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class BackupCloudViewModel( private val settingsRepository: ISettingsRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupLocalViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupLocalViewModel.kt index 43d2c1ec..8b6d32dc 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupLocalViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupLocalViewModel.kt @@ -21,8 +21,6 @@ package com.dimension.maskbook.setting.viewmodel import android.net.Uri -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.LocalBackupAccount import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.repository.BackupRepository @@ -32,6 +30,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class BackupLocalViewModel( private val repository: ISettingsRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupMergeConfirmViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupMergeConfirmViewModel.kt index 9f871b16..6763cabb 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupMergeConfirmViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupMergeConfirmViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.repository.BackupRepository @@ -29,6 +27,8 @@ import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import java.io.File +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class BackupMergeConfirmViewModel( private val backupRepository: BackupRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupPasswordSettingsViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupPasswordSettingsViewModel.kt index be23a0b2..29b0784a 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupPasswordSettingsViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/BackupPasswordSettingsViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class BackupPasswordSettingsViewModel( private val repository: ISettingsRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/DataSourceSettingsViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/DataSourceSettingsViewModel.kt index 68ef5041..ff781989 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/DataSourceSettingsViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/DataSourceSettingsViewModel.kt @@ -20,11 +20,11 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.model.DataProvider import com.dimension.maskbook.setting.repository.ISettingsRepository +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class DataSourceSettingsViewModel( private val repository: ISettingsRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/EmailBackupViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/EmailBackupViewModel.kt index ae242d32..956c7f7e 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/EmailBackupViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/EmailBackupViewModel.kt @@ -20,7 +20,6 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.defaultRegionCode @@ -31,6 +30,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.viewModelScope import retrofit2.HttpException class EmailBackupViewModel( diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/EmailSetupViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/EmailSetupViewModel.kt index 76c47136..62a6b09b 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/EmailSetupViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/EmailSetupViewModel.kt @@ -20,7 +20,6 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.defaultRegionCode @@ -31,6 +30,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.viewModelScope class EmailSetupViewModel( private val settingsRepository: ISettingsRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/LanguageSettingsViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/LanguageSettingsViewModel.kt index d787629a..aab1b534 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/LanguageSettingsViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/LanguageSettingsViewModel.kt @@ -20,11 +20,11 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.model.Language import com.dimension.maskbook.setting.repository.ISettingsRepository +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class LanguageSettingsViewModel( private val repository: ISettingsRepository diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/PaymentPasswordSettingsViewModel.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/PaymentPasswordSettingsViewModel.kt index f7820b1f..35c32992 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/PaymentPasswordSettingsViewModel.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/PaymentPasswordSettingsViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.setting.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class PaymentPasswordSettingsViewModel( private val repository: ISettingsRepository, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/base/RemoteBackupRecoveryViewModelBase.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/base/RemoteBackupRecoveryViewModelBase.kt index 1dc1af54..3de8815e 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/base/RemoteBackupRecoveryViewModelBase.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/viewmodel/base/RemoteBackupRecoveryViewModelBase.kt @@ -20,14 +20,14 @@ */ package com.dimension.maskbook.setting.viewmodel.base -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.defaultCountDownTime import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope import kotlin.time.Duration.Companion.seconds abstract class RemoteBackupRecoveryViewModelBase : ViewModel() { diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt index c333cc1e..adcab84e 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt @@ -21,11 +21,9 @@ package com.dimension.maskbook.wallet import android.content.Context -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder import androidx.room.Room import com.dimension.maskbook.common.ModuleSetup +import com.dimension.maskbook.common.route.navigation import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.wallet.data.JSMethod import com.dimension.maskbook.wallet.db.AppDatabase @@ -108,12 +106,13 @@ import com.dimension.maskbook.wallet.walletconnect.WalletConnectClientManager import com.dimension.maskbook.wallet.walletconnect.WalletConnectServerManager import com.dimension.maskbook.wallet.walletconnect.v1.client.WalletConnectClientManagerV1 import com.dimension.maskbook.wallet.walletconnect.v1.server.WalletConnectServerManagerV1 -import com.google.accompanist.navigation.animation.navigation import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.launch -import org.koin.androidx.viewmodel.dsl.viewModel +import moe.tlaster.koin.viewModel +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.RouteBuilder import org.koin.core.module.Module import org.koin.dsl.bind import org.koin.dsl.module @@ -122,8 +121,7 @@ import com.dimension.maskbook.wallet.export.WalletServices as ExportWalletServic object WalletSetup : ModuleSetup { - @OptIn(ExperimentalAnimationApi::class) - override fun NavGraphBuilder.route(navController: NavController) { + override fun RouteBuilder.route(navController: NavController) { generatedRoute(navController) navigation( startDestination = WalletRoute.Transfer.SearchAddress.path, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/route/WalletsRoute.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/route/WalletsRoute.kt index f9877939..b993b391 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/route/WalletsRoute.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/route/WalletsRoute.kt @@ -36,10 +36,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString -import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavController -import androidx.navigation.navOptions -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.ext.shareText import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks @@ -94,8 +91,10 @@ import com.dimension.maskbook.wallet.viewmodel.wallets.management.WalletRenameVi import com.dimension.maskbook.wallet.viewmodel.wallets.management.WalletSwitchEditViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.management.WalletSwitchViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.management.WalletTransactionHistoryViewModel -import org.koin.androidx.compose.get -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.get +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -112,8 +111,8 @@ fun CollectibleDetail( val viewModel = getViewModel { parametersOf(id) } - val data by viewModel.data.observeAsState(initial = null) - val transactions by viewModel.transactions.observeAsState() + val data by viewModel.data.collectAsState(initial = null) + val transactions by viewModel.transactions.collectAsState() CollectibleDetailScene( data = data, onBack = onBack, @@ -144,7 +143,7 @@ fun WalletQrcode( @Path("name") name: String, ) { val repository = get() - val currentWallet by repository.currentWallet.observeAsState(initial = null) + val currentWallet by repository.currentWallet.collectAsState(initial = null) val context = LocalContext.current val clipboardManager = LocalClipboardManager.current val inAppNotification = LocalInAppNotification.current @@ -174,10 +173,10 @@ fun TokenDetail( val viewModel = getViewModel { parametersOf(id) } - val token by viewModel.tokenData.observeAsState() - val transactions by viewModel.transactions.observeAsState() - val walletTokenData by viewModel.walletTokenData.observeAsState() - val dWebData by viewModel.dWebData.observeAsState() + val token by viewModel.tokenData.collectAsState() + val transactions by viewModel.transactions.collectAsState() + val walletTokenData by viewModel.walletTokenData.collectAsState() + val dWebData by viewModel.dWebData.collectAsState() TokenDetailScene( onBack = onBack, @@ -248,7 +247,7 @@ fun WalletNetworkSwitch( ) { val target = remember(targetString) { ChainType.valueOf(targetString) } val viewModel = getViewModel() - val currentNetwork by viewModel.network.observeAsState(initial = ChainType.eth) + val currentNetwork by viewModel.network.collectAsState(initial = ChainType.eth) WalletNetworkSwitchWarningDialog( currentNetwork = currentNetwork.name, connectingNetwork = target.name, @@ -270,8 +269,8 @@ fun WalletNetworkSwitchWarningDialog( @Back onBack: () -> Unit, ) { val viewModel = getViewModel() - val currentNetwork by viewModel.network.observeAsState(initial = ChainType.eth) - val wallet by viewModel.currentWallet.observeAsState(initial = null) + val currentNetwork by viewModel.network.collectAsState(initial = ChainType.eth) + val wallet by viewModel.currentWallet.collectAsState(initial = null) LaunchedEffect(wallet) { wallet?.let { wallet -> if (!wallet.fromWalletConnect || wallet.walletConnectChainType == currentNetwork || wallet.walletConnectChainType == null) { @@ -305,9 +304,9 @@ fun SwitchWallet( navController: NavController, ) { val viewModel = getViewModel() - val wallet by viewModel.currentWallet.observeAsState(initial = null) - val wallets by viewModel.wallets.observeAsState(initial = emptyList()) - val chainType by viewModel.network.observeAsState(initial = ChainType.eth) + val wallet by viewModel.currentWallet.collectAsState(initial = null) + val wallets by viewModel.wallets.collectAsState(initial = emptyList()) + val chainType by viewModel.network.collectAsState(initial = ChainType.eth) WalletSwitchSceneModal( selectedWallet = wallet, wallets = wallets, @@ -394,7 +393,7 @@ fun WalletBalancesMenu( @Back onBack: () -> Unit, ) { val viewModel = getViewModel() - val currentWallet by viewModel.currentWallet.observeAsState(initial = null) + val currentWallet by viewModel.currentWallet.collectAsState(initial = null) val wcViewModel = getViewModel() WalletManagementModal( walletData = currentWallet, @@ -438,11 +437,11 @@ fun WalletManagementDeleteDialog( parametersOf(id) } val biometricViewModel = getViewModel() - val wallet by viewModel.wallet.observeAsState(initial = null) - val biometricEnabled by biometricViewModel.biometricEnabled.observeAsState(initial = false) + val wallet by viewModel.wallet.collectAsState(initial = null) + val biometricEnabled by biometricViewModel.biometricEnabled.collectAsState(initial = false) val context = LocalContext.current - val password by viewModel.password.observeAsState(initial = "") - val canConfirm by viewModel.canConfirm.observeAsState(initial = false) + val password by viewModel.password.collectAsState(initial = "") + val canConfirm by viewModel.canConfirm.collectAsState(initial = false) WalletDeleteDialog( walletData = wallet, password = password, @@ -477,8 +476,8 @@ fun WalletManagementBackup( @Back onBack: () -> Unit, ) { val viewModel = getViewModel() - val keyStore by viewModel.keyStore.observeAsState(initial = "") - val privateKey by viewModel.privateKey.observeAsState(initial = "") + val keyStore by viewModel.keyStore.collectAsState(initial = "") + val privateKey by viewModel.privateKey.collectAsState(initial = "") BackupWalletScene( keyStore = keyStore, privateKey = privateKey, @@ -496,7 +495,7 @@ fun WalletManagementTransactionHistory( @Back onBack: () -> Unit, ) { val viewModel = getViewModel() - val transactions by viewModel.transactions.observeAsState() + val transactions by viewModel.transactions.collectAsState() WalletTransactionHistoryScene( onBack = onBack, transactions = transactions, @@ -523,13 +522,13 @@ fun WalletManagementRename( val viewModel = getViewModel { parametersOf(walletId, walletName) } - val name by viewModel.name.observeAsState() + val name by viewModel.name.collectAsState() WalletRenameModal( name = name, onNameChanged = { viewModel.setName(it) }, onDone = { viewModel.confirm() - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Wallet)), navOptions { launchSingleTop = true @@ -550,15 +549,15 @@ fun WalletManagementRename( @Composable fun WalletIntroHostLegal( navController: NavController, - navBackStackEntry: NavBackStackEntry, + navBackStackEntry: BackStackEntry, @Back onBack: () -> Unit, @Path("type") typeString: String, ) { val type = remember(typeString) { CreateType.valueOf(typeString) } val repo = get() - val password by repo.paymentPassword.observeAsState(initial = null) - val enableBiometric by repo.biometricEnabled.observeAsState(initial = false) - val shouldShowLegalScene by repo.shouldShowLegalScene.observeAsState(initial = true) + val password by repo.paymentPassword.collectAsState(initial = null) + val enableBiometric by repo.biometricEnabled.collectAsState(initial = false) + val shouldShowLegalScene by repo.shouldShowLegalScene.collectAsState(initial = true) val biometricEnableViewModel: BiometricEnableViewModel = getViewModel() val context = LocalContext.current val next: () -> Unit = { @@ -609,7 +608,7 @@ fun WalletIntroHostPassword( @Path("type") typeString: String, ) { val type = remember(typeString) { CreateType.valueOf(typeString) } - val enableBiometric by get().biometricEnabled.observeAsState(initial = false) + val enableBiometric by get().biometricEnabled.collectAsState(initial = false) val biometricEnableViewModel: BiometricEnableViewModel = getViewModel() val context = LocalContext.current SetUpPaymentPassword( @@ -792,9 +791,9 @@ fun UnlockWalletDialog( @Path("target") target: String, ) { val viewModel = getViewModel() - val biometricEnable by viewModel.biometricEnabled.observeAsState(initial = false) - val password by viewModel.password.observeAsState(initial = "") - val passwordValid by viewModel.passwordValid.observeAsState(initial = false) + val biometricEnable by viewModel.biometricEnabled.collectAsState(initial = false) + val password by viewModel.password.collectAsState(initial = "") + val passwordValid by viewModel.passwordValid.collectAsState(initial = false) val context = LocalContext.current UnlockWalletDialog( onBack = onBack, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/CreateOrImportWalletScene.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/CreateOrImportWalletScene.kt index 50df97d7..62a0ad95 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/CreateOrImportWalletScene.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/CreateOrImportWalletScene.kt @@ -43,7 +43,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.dimension.maskbook.common.ui.widget.HorizontalScenePadding import com.dimension.maskbook.common.ui.widget.MaskDialog import com.dimension.maskbook.common.ui.widget.MaskInputField @@ -57,6 +56,7 @@ import com.dimension.maskbook.common.ui.widget.button.MaskIconButton import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.wallet.R import com.dimension.maskbook.wallet.route.WalletRoute +import moe.tlaster.precompose.navigation.NavController @Composable fun CreateOrImportWalletScene( diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt index 95aa80c9..f05b6f92 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt @@ -49,7 +49,7 @@ import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.wallet.R import com.dimension.maskbook.wallet.route.WalletRoute import com.dimension.maskbook.wallet.viewmodel.wallets.create.CreateWalletRecoveryKeyViewModel -import org.koin.core.parameter.parametersOf +import moe.tlaster.precompose.navigation.NavController private const val GeneratedRouteName = "createWalletRoute" diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletDerivationPathScene.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletDerivationPathScene.kt index b7cd7601..f627c0d5 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletDerivationPathScene.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletDerivationPathScene.kt @@ -57,9 +57,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.navOptions -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navOptions +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable @@ -86,7 +85,8 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.rememberPagerState import kotlinx.coroutines.launch -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf typealias DerivationPathItem = ImportWalletDerivationPathViewModel.BalanceRow @@ -109,8 +109,8 @@ fun ImportWalletDerivationPathScene( val viewModel = getViewModel { parametersOf(wallet, code) } - val path by viewModel.derivationPath.observeAsState(initial = "") - val checked by viewModel.checked.observeAsState(initial = emptyList()) + val path by viewModel.derivationPath.collectAsState(initial = "") + val checked by viewModel.checked.collectAsState(initial = emptyList()) var showDialog by remember { mutableStateOf(false) } var result by remember { mutableStateOf(null) } @@ -217,7 +217,7 @@ fun ImportWalletDerivationPathScene( it.Dialog(onDismissRequest = { showDialog = false if (it.type == WalletCreateOrImportResult.Type.SUCCESS) { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Wallet)), navOptions = navOptions { launchSingleTop = true diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletKeystoreScene.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletKeystoreScene.kt index 1afbbeaa..694126f6 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletKeystoreScene.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletKeystoreScene.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -38,9 +39,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.navOptions -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navOptions +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable @@ -61,7 +61,8 @@ import com.dimension.maskbook.wallet.repository.WalletCreateOrImportResult import com.dimension.maskbook.wallet.route.WalletRoute import com.dimension.maskbook.wallet.ui.scenes.wallets.common.Dialog import com.dimension.maskbook.wallet.viewmodel.wallets.import.ImportWalletKeystoreViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -91,9 +92,9 @@ fun ImportWalletKeyStoreScene( val viewModel = getViewModel { parametersOf(wallet) } - val keystore by viewModel.keystore.observeAsState(initial = "") - val password by viewModel.password.observeAsState(initial = "") - val canConfirm by viewModel.canConfirm.observeAsState(initial = false) + val keystore by viewModel.keystore.collectAsState(initial = "") + val password by viewModel.password.collectAsState(initial = "") + val canConfirm by viewModel.canConfirm.collectAsState(initial = false) var showDialog by remember { mutableStateOf(false) } @@ -133,7 +134,7 @@ fun ImportWalletKeyStoreScene( onClick = { viewModel.confirm { if (it.type == WalletCreateOrImportResult.Type.SUCCESS) { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Wallet)), navOptions = navOptions { launchSingleTop = true diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletMnemonicScene.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletMnemonicScene.kt index 0362ba7c..045c85d3 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletMnemonicScene.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletMnemonicScene.kt @@ -37,6 +37,7 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -44,8 +45,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.routeProcessor.annotations.Back @@ -63,7 +62,8 @@ import com.dimension.maskbook.wallet.repository.WalletCreateOrImportResult import com.dimension.maskbook.wallet.route.WalletRoute import com.dimension.maskbook.wallet.ui.scenes.wallets.common.Dialog import com.dimension.maskbook.wallet.viewmodel.wallets.import.ImportWalletMnemonicViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -94,9 +94,9 @@ fun ImportWalletMnemonicScene( val viewModel = getViewModel { parametersOf(walletAddress) } - val words by viewModel.words.observeAsState(initial = "") - val canConfirm by viewModel.canConfirm.observeAsState(initial = false) - val hintWords by viewModel.hintWords.observeAsState(initial = emptyList()) + val words by viewModel.words.collectAsState(initial = "") + val canConfirm by viewModel.canConfirm.collectAsState(initial = false) + val hintWords by viewModel.hintWords.collectAsState(initial = emptyList()) var showDialog by remember { mutableStateOf(false) } diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletPrivateKeyScene.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletPrivateKeyScene.kt index 00dbfee8..ecaf97fe 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletPrivateKeyScene.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/import/ImportWalletPrivateKeyScene.kt @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -37,9 +38,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.navOptions -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navOptions +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable @@ -59,7 +59,8 @@ import com.dimension.maskbook.wallet.repository.WalletCreateOrImportResult import com.dimension.maskbook.wallet.route.WalletRoute import com.dimension.maskbook.wallet.ui.scenes.wallets.common.Dialog import com.dimension.maskbook.wallet.viewmodel.wallets.import.ImportWalletPrivateKeyViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf @NavGraphDestination( @@ -89,8 +90,8 @@ fun ImportWalletPrivateKeyScene( val viewModel = getViewModel { parametersOf(wallet) } - val privateKey by viewModel.privateKey.observeAsState(initial = "") - val canConfirm by viewModel.canConfirm.observeAsState(initial = false) + val privateKey by viewModel.privateKey.collectAsState(initial = "") + val canConfirm by viewModel.canConfirm.collectAsState(initial = false) var showDialog by remember { mutableStateOf(false) } @@ -119,7 +120,7 @@ fun ImportWalletPrivateKeyScene( onClick = { viewModel.confirm { if (it.type == WalletCreateOrImportResult.Type.SUCCESS) { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Wallet)), navOptions = navOptions { launchSingleTop = true diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/WalletIntroHost.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/WalletIntroHost.kt index 59fe053f..da5e47d2 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/WalletIntroHost.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/WalletIntroHost.kt @@ -21,34 +21,32 @@ package com.dimension.maskbook.wallet.ui.scenes.wallets.intro import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.buildAnnotatedString -import androidx.navigation.NavController import androidx.paging.compose.collectAsLazyPagingItems -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.wallet.route.WalletRoute import com.dimension.maskbook.wallet.ui.scenes.wallets.create.CreateType import com.dimension.maskbook.wallet.ui.scenes.wallets.management.WalletBalancesScene import com.dimension.maskbook.wallet.viewmodel.wallets.WalletBalancesViewModel -import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi import com.google.accompanist.swiperefresh.rememberSwipeRefreshState -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController -@ExperimentalMaterialNavigationApi @Composable fun WalletIntroHost(navController: NavController) { val clipboardManager = LocalClipboardManager.current val viewModel = getViewModel() val collectible = viewModel.collectible.collectAsLazyPagingItems() - val dWebData by viewModel.dWebData.observeAsState() - val sceneType by viewModel.sceneType.observeAsState() - val wallet by viewModel.currentWallet.observeAsState() - val wallets by viewModel.wallets.observeAsState() - val displayChainType by viewModel.displayChainType.observeAsState() - val showTokens by viewModel.showTokens.observeAsState() - val showTokensLess by viewModel.showTokensLess.observeAsState() - val showTokensLessAmount by viewModel.showTokensLessAmount.observeAsState() + val dWebData by viewModel.dWebData.collectAsState() + val sceneType by viewModel.sceneType.collectAsState() + val wallet by viewModel.currentWallet.collectAsState() + val wallets by viewModel.wallets.collectAsState() + val displayChainType by viewModel.displayChainType.collectAsState() + val showTokens by viewModel.showTokens.collectAsState() + val showTokensLess by viewModel.showTokensLess.collectAsState() + val showTokensLessAmount by viewModel.showTokensLessAmount.collectAsState() val currentWallet = wallet val currentDWebData = dWebData @@ -67,7 +65,7 @@ fun WalletIntroHost(navController: NavController) { ) } else if (currentDWebData != null) { val swipeRefreshState = rememberSwipeRefreshState(false) - val refreshing by viewModel.refreshingWallet.observeAsState() + val refreshing by viewModel.refreshingWallet.collectAsState() swipeRefreshState.isRefreshing = refreshing WalletBalancesScene( wallets = wallets, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/password/BiometricsEnableScene.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/password/BiometricsEnableScene.kt index 1b2c85e4..c61a3d33 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/password/BiometricsEnableScene.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/password/BiometricsEnableScene.kt @@ -46,7 +46,7 @@ import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.common.ui.widget.button.SecondaryButton import com.dimension.maskbook.common.viewmodel.BiometricEnableViewModel import com.dimension.maskbook.wallet.R -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @Composable fun BiometricsEnableScene( diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/password/TouchIdEnableScene.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/password/TouchIdEnableScene.kt index af5185eb..3842c70c 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/password/TouchIdEnableScene.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/intro/password/TouchIdEnableScene.kt @@ -45,7 +45,7 @@ import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.common.ui.widget.button.SecondaryButton import com.dimension.maskbook.wallet.R import com.dimension.maskbook.wallet.viewmodel.wallets.TouchIdEnableViewModel -import org.koin.androidx.compose.get +import moe.tlaster.koin.compose.get @Composable fun TouchIdEnableScene( diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/SendTokenConfirmModal.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/SendTokenConfirmModal.kt index 2c109483..940ca11e 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/SendTokenConfirmModal.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/SendTokenConfirmModal.kt @@ -21,22 +21,16 @@ package com.dimension.maskbook.wallet.ui.scenes.wallets.send import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext -import androidx.navigation.NavController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.dialog -import androidx.navigation.compose.rememberNavController import com.dimension.maskbook.common.ext.decodeJson import com.dimension.maskbook.common.ext.fromHexString import com.dimension.maskbook.common.ext.humanizeDollar import com.dimension.maskbook.common.ext.humanizeToken -import com.dimension.maskbook.common.ext.observeAsState -import com.dimension.maskbook.common.ext.sendEvent -import com.dimension.maskbook.common.model.ResultEvent import com.dimension.maskbook.common.route.Deeplinks +import com.dimension.maskbook.common.route.composable import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage import com.dimension.maskbook.common.routeProcessor.annotations.Back @@ -52,7 +46,10 @@ import com.dimension.maskbook.wallet.ui.scenes.wallets.UnlockWalletDialog import com.dimension.maskbook.wallet.viewmodel.wallets.UnlockWalletViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.send.GasFeeViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.send.Web3TransactionConfirmViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.NavHost +import moe.tlaster.precompose.navigation.rememberNavController import org.koin.core.parameter.parametersOf import java.math.BigDecimal @@ -78,29 +75,29 @@ fun SendTokenConfirmModal( val viewModel = getViewModel { parametersOf(data, request) } - val token by viewModel.tokenData.observeAsState(null) - val address by viewModel.addressData.observeAsState(null) - val amount by viewModel.amount.observeAsState(BigDecimal.ZERO) + val token by viewModel.tokenData.collectAsState(null) + val address by viewModel.addressData.collectAsState(null) + val amount by viewModel.amount.collectAsState(BigDecimal.ZERO) address?.let { addressData -> token?.let { tokenData -> val gasFeeViewModel = getViewModel { parametersOf(data.gasLimit?.fromHexString()?.toDouble() ?: 21000.0) } - val gasLimit by gasFeeViewModel.gasLimit.observeAsState() - val maxPriorityFee by gasFeeViewModel.maxPriorityFeePerGas.observeAsState() - val maxFee by gasFeeViewModel.maxFeePerGas.observeAsState() - val arrives by gasFeeViewModel.arrives.observeAsState() - val gasUsdTotal by gasFeeViewModel.gasUsdTotal.observeAsState() - val gasTotal by gasFeeViewModel.gasTotal.observeAsState() - val loadingState by gasFeeViewModel.loadingState.observeAsState() - val gasFeeUnit by gasFeeViewModel.gasFeeUnit.observeAsState() + val gasLimit by gasFeeViewModel.gasLimit.collectAsState() + val maxPriorityFee by gasFeeViewModel.maxPriorityFeePerGas.collectAsState() + val maxFee by gasFeeViewModel.maxFeePerGas.collectAsState() + val arrives by gasFeeViewModel.arrives.collectAsState() + val gasUsdTotal by gasFeeViewModel.gasUsdTotal.collectAsState() + val gasTotal by gasFeeViewModel.gasTotal.collectAsState() + val loadingState by gasFeeViewModel.loadingState.collectAsState() + val gasFeeUnit by gasFeeViewModel.gasFeeUnit.collectAsState() NavHost( navController, - startDestination = "SendConfirm" + initialRoute = "SendConfirm" ) { composable("SendConfirm") { - val sending by viewModel.loadingState.observeAsState() + val sending by viewModel.loadingState.collectAsState() SendConfirmSheet( addressData = addressData, tokenData = WalletTokenData( @@ -127,8 +124,8 @@ fun SendTokenConfirmModal( ) } composable("EditGasFee") { - val mode by gasFeeViewModel.gasPriceEditMode.observeAsState(initial = GasPriceEditMode.MEDIUM) - val loading by gasFeeViewModel.loadingState.observeAsState() + val mode by gasFeeViewModel.gasPriceEditMode.collectAsState(initial = GasPriceEditMode.MEDIUM) + val loading by gasFeeViewModel.loadingState.collectAsState() EditGasPriceSheet( price = gasUsdTotal.humanizeDollar(), costFee = gasTotal.humanizeToken(), @@ -170,9 +167,9 @@ fun SendTokenConfirmModal( "UnlockWalletDialog", ) { val unlockViewModel = getViewModel() - val biometricEnable by unlockViewModel.biometricEnabled.observeAsState(initial = false) - val password by unlockViewModel.password.observeAsState(initial = "") - val passwordValid by unlockViewModel.passwordValid.observeAsState(initial = false) + val biometricEnable by unlockViewModel.biometricEnabled.collectAsState(initial = false) + val password by unlockViewModel.password.collectAsState(initial = "") + val passwordValid by unlockViewModel.passwordValid.collectAsState(initial = false) val context = LocalContext.current val onSuccess: () -> Unit = { navController.popBackStack() diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/TranserHost.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/TranserHost.kt index d2062c1c..7bcf21e3 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/TranserHost.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/TranserHost.kt @@ -30,13 +30,11 @@ import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.buildAnnotatedString -import androidx.navigation.NavController import androidx.paging.compose.collectAsLazyPagingItems import com.dimension.maskbook.common.bigDecimal.BigDecimal import com.dimension.maskbook.common.ext.getNestedNavigationViewModel import com.dimension.maskbook.common.ext.humanizeDollar import com.dimension.maskbook.common.ext.humanizeToken -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.route.navigationComposeBottomSheet @@ -60,7 +58,8 @@ import com.dimension.maskbook.wallet.viewmodel.wallets.send.SearchAddressViewMod import com.dimension.maskbook.wallet.viewmodel.wallets.send.SearchTradableViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.send.SendConfirmViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.send.TransferDetailViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf private const val GeneratedRouteName = "transferRoute" @@ -80,12 +79,12 @@ fun SearchAddressRoute( val searchAddressViewModel = navController .getNestedNavigationViewModel(WalletRoute.Transfer.Route) - val input by searchAddressViewModel.input.observeAsState() - val contacts by searchAddressViewModel.contacts.observeAsState() - val recent by searchAddressViewModel.recent.observeAsState() - val ensData by searchAddressViewModel.ensData.observeAsState() - val selectEnsData by searchAddressViewModel.selectEnsData.observeAsState() - val canConfirm by searchAddressViewModel.canConfirm.observeAsState() + val input by searchAddressViewModel.input.collectAsState() + val contacts by searchAddressViewModel.contacts.collectAsState() + val recent by searchAddressViewModel.recent.collectAsState() + val ensData by searchAddressViewModel.ensData.collectAsState() + val selectEnsData by searchAddressViewModel.selectEnsData.collectAsState() + val canConfirm by searchAddressViewModel.canConfirm.collectAsState() val clipboardManager = LocalClipboardManager.current val inAppNotification = LocalInAppNotification.current @@ -176,9 +175,9 @@ fun SendRoute( parametersOf(tradableId) } - val arrives by gasFeeViewModel.arrives.observeAsState(initial = "") - val gasUsdTotal by gasFeeViewModel.gasUsdTotal.observeAsState(initial = BigDecimal.ZERO) - val gasTotal by gasFeeViewModel.gasTotal.observeAsState(initial = BigDecimal.ZERO) + val arrives by gasFeeViewModel.arrives.collectAsState(initial = "") + val gasUsdTotal by gasFeeViewModel.gasUsdTotal.collectAsState(initial = BigDecimal.ZERO) + val gasTotal by gasFeeViewModel.gasTotal.collectAsState(initial = BigDecimal.ZERO) val selectTradable by transferDetailViewModel.selectedTradable.collectAsState(null) @@ -186,13 +185,13 @@ fun SendRoute( transferDetailViewModel.setAddress(address) } val biometricViewModel = getViewModel() - val biometricEnabled by biometricViewModel.biometricEnabled.observeAsState() - val addressData by transferDetailViewModel.addressData.observeAsState() - val amount by transferDetailViewModel.amount.observeAsState() - val password by transferDetailViewModel.password.observeAsState() - val canConfirm by transferDetailViewModel.canConfirm.observeAsState() - val balance by transferDetailViewModel.balance.observeAsState() - val maxAmount by transferDetailViewModel.maxAmount.observeAsState() + val biometricEnabled by biometricViewModel.biometricEnabled.collectAsState() + val addressData by transferDetailViewModel.addressData.collectAsState() + val amount by transferDetailViewModel.amount.collectAsState() + val password by transferDetailViewModel.password.collectAsState() + val canConfirm by transferDetailViewModel.canConfirm.collectAsState() + val balance by transferDetailViewModel.balance.collectAsState() + val maxAmount by transferDetailViewModel.maxAmount.collectAsState() transferDetailViewModel.setGasTotal(gasTotal = gasTotal) TransferDetailScene( @@ -270,8 +269,8 @@ fun SearchTokenRoute( } val viewModel = getViewModel() - val walletTokens by viewModel.walletTokens.observeAsState(emptyList()) - val query by viewModel.query.observeAsState() + val walletTokens by viewModel.walletTokens.collectAsState(emptyList()) + val query by viewModel.query.collectAsState() SearchTokenScene( onBack = { onBack.invoke() @@ -308,7 +307,7 @@ fun SearchCollectiblesRoute( val viewModel = getViewModel() val walletCollectibleCollections = viewModel.walletCollectibleCollections.collectAsLazyPagingItems() - val query by viewModel.query.observeAsState() + val query by viewModel.query.collectAsState() SearchCollectibleScene( onBack = { onBack.invoke() @@ -340,15 +339,15 @@ fun EditGasFeeRoute( parametersOf(21000.0) } - val gasLimit by gasFeeViewModel.gasLimit.observeAsState(initial = -1.0) - val maxPriorityFee by gasFeeViewModel.maxPriorityFeePerGas.observeAsState(initial = -1.0) - val maxFee by gasFeeViewModel.maxFeePerGas.observeAsState(initial = -1.0) - val arrives by gasFeeViewModel.arrives.observeAsState(initial = "") - val gasTotal by gasFeeViewModel.gasTotal.observeAsState(initial = BigDecimal.ZERO) - val gasUsdTotal by gasFeeViewModel.gasUsdTotal.observeAsState(initial = BigDecimal.ZERO) + val gasLimit by gasFeeViewModel.gasLimit.collectAsState(initial = -1.0) + val maxPriorityFee by gasFeeViewModel.maxPriorityFeePerGas.collectAsState(initial = -1.0) + val maxFee by gasFeeViewModel.maxFeePerGas.collectAsState(initial = -1.0) + val arrives by gasFeeViewModel.arrives.collectAsState(initial = "") + val gasTotal by gasFeeViewModel.gasTotal.collectAsState(initial = BigDecimal.ZERO) + val gasUsdTotal by gasFeeViewModel.gasUsdTotal.collectAsState(initial = BigDecimal.ZERO) val mode by gasFeeViewModel.gasPriceEditMode.collectAsState() - val loading by gasFeeViewModel.loadingState.observeAsState() - val gasFeeUnit by gasFeeViewModel.gasFeeUnit.observeAsState() + val loading by gasFeeViewModel.loadingState.collectAsState() + val gasFeeUnit by gasFeeViewModel.gasFeeUnit.collectAsState() EditGasPriceSheet( price = gasUsdTotal.humanizeDollar(), costFee = gasTotal.humanizeToken(), @@ -399,7 +398,7 @@ fun AddContactSheetRoute( @Back onBack: () -> Unit, ) { val viewModel = getViewModel() - val name by viewModel.name.observeAsState(initial = "") + val name by viewModel.name.collectAsState(initial = "") AddContactSheet( avatarLabel = name, address = address, @@ -437,10 +436,10 @@ fun SendConfirmRoute( parametersOf(tradableId) } - val gasLimit by gasFeeViewModel.gasLimit.observeAsState(initial = -1.0) - val maxPriorityFee by gasFeeViewModel.maxPriorityFeePerGas.observeAsState(initial = -1.0) - val maxFee by gasFeeViewModel.maxFeePerGas.observeAsState(initial = -1.0) - val gasUsdTotal by gasFeeViewModel.gasUsdTotal.observeAsState(initial = BigDecimal.ZERO) + val gasLimit by gasFeeViewModel.gasLimit.collectAsState(initial = -1.0) + val maxPriorityFee by gasFeeViewModel.maxPriorityFeePerGas.collectAsState(initial = -1.0) + val maxFee by gasFeeViewModel.maxFeePerGas.collectAsState(initial = -1.0) + val gasUsdTotal by gasFeeViewModel.gasUsdTotal.collectAsState(initial = BigDecimal.ZERO) val selectTradable by transferDetailViewModel.selectedTradable.collectAsState(null) val amount = remember(amountString) { BigDecimal(amountString) } @@ -448,9 +447,9 @@ fun SendConfirmRoute( val viewModel = getViewModel { parametersOf(address) } - val deeplink by viewModel.deepLink.observeAsState(initial = "") - val addressData by viewModel.addressData.observeAsState(initial = null) - val loading by viewModel.loadingState.observeAsState() + val deeplink by viewModel.deepLink.collectAsState(initial = "") + val addressData by viewModel.addressData.collectAsState(initial = null) + val loading by viewModel.loadingState.collectAsState() val totalPrice = selectTradable?.let { when (it) { is WalletTokenData -> (amount * it.tokenData.price + gasUsdTotal).humanizeDollar() diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/walletconnect/WalletConnectModal.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/walletconnect/WalletConnectModal.kt index 99948976..f54e2056 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/walletconnect/WalletConnectModal.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/walletconnect/WalletConnectModal.kt @@ -55,6 +55,7 @@ import androidx.compose.material.TabRowDefaults import androidx.compose.material.TabRowDefaults.tabIndicatorOffset import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -72,13 +73,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import androidx.navigation.NavType -import androidx.navigation.compose.dialog -import androidx.navigation.navArgument -import androidx.navigation.navOptions import coil.compose.rememberImagePainter -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.route.AnimatedNavHost +import com.dimension.maskbook.common.route.composable import com.dimension.maskbook.common.ui.barcode.rememberBarcodeBitmap import com.dimension.maskbook.common.ui.notification.StringResNotificationEvent.Companion.show import com.dimension.maskbook.common.ui.widget.HorizontalScenePadding @@ -96,12 +93,11 @@ import com.dimension.maskbook.wallet.route.WalletRoute import com.dimension.maskbook.wallet.ui.scenes.wallets.management.supportedChainType import com.dimension.maskbook.wallet.viewmodel.wallets.walletconnect.WalletConnectResult import com.dimension.maskbook.wallet.viewmodel.wallets.walletconnect.WalletConnectViewModel -import com.google.accompanist.navigation.animation.AnimatedNavHost -import com.google.accompanist.navigation.animation.composable -import com.google.accompanist.navigation.animation.rememberAnimatedNavController import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.rememberNavController import org.koin.core.parameter.parametersOf enum class WalletConnectType { @@ -112,7 +108,7 @@ enum class WalletConnectType { @OptIn(ExperimentalAnimationApi::class) @Composable fun WalletConnectModal(rootNavController: NavController) { - val navController = rememberAnimatedNavController() + val navController = rememberNavController() val scope = rememberCoroutineScope() val onResult: (WalletConnectResult) -> Unit = { result -> scope.launch(Dispatchers.Main) { @@ -135,9 +131,9 @@ fun WalletConnectModal(rootNavController: NavController) { val viewModel = getViewModel { parametersOf(onResult) } - val wcUrl by viewModel.wcUrl.observeAsState(initial = "") + val wcUrl by viewModel.wcUrl.collectAsState(initial = "") val context = LocalContext.current - val currentSupportedWallets by viewModel.currentSupportedWallets.observeAsState(initial = emptyList()) + val currentSupportedWallets by viewModel.currentSupportedWallets.collectAsState(initial = emptyList()) MaskModal { val clipboardManager = LocalClipboardManager.current val inAppNotification = LocalInAppNotification.current @@ -153,7 +149,7 @@ fun WalletConnectModal(rootNavController: NavController) { AnimatedNavHost( navController = navController, - startDestination = "WalletConnectTypeSelect" + initialRoute = "WalletConnectTypeSelect" ) { composable("WalletConnectTypeSelect") { TypeSelectScene( diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/tab/WalletTabScreen.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/tab/WalletTabScreen.kt index 51ff7187..ae2c0685 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/tab/WalletTabScreen.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/tab/WalletTabScreen.kt @@ -21,19 +21,17 @@ package com.dimension.maskbook.wallet.ui.tab import androidx.compose.runtime.Composable -import androidx.navigation.NavController import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.wallet.R import com.dimension.maskbook.wallet.ui.scenes.wallets.intro.WalletIntroHost -import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi +import moe.tlaster.precompose.navigation.NavController class WalletTabScreen : TabScreen { override val route = CommonRoute.Main.Tabs.Wallet override val title: Int = R.string.tab_wallet override val icon: Int = R.drawable.ic_wallet - @OptIn(ExperimentalMaterialNavigationApi::class) @Composable override fun Content(navController: NavController, onBack: () -> Unit) { WalletIntroHost(navController) diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/widget/CollectibleCollectionCard.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/widget/CollectibleCollectionCard.kt index d0b5f2bc..9ad7667d 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/widget/CollectibleCollectionCard.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/widget/CollectibleCollectionCard.kt @@ -59,7 +59,7 @@ import com.dimension.maskbook.wallet.R import com.dimension.maskbook.wallet.export.model.WalletCollectibleCollectionData import com.dimension.maskbook.wallet.export.model.WalletCollectibleData import com.dimension.maskbook.wallet.viewmodel.wallets.collectible.CollectiblesViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @OptIn(ExperimentalMaterialApi::class) @Composable diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/WelcomeViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/WelcomeViewModel.kt index a8d4ba07..20c8e642 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/WelcomeViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/WelcomeViewModel.kt @@ -20,11 +20,11 @@ */ package com.dimension.maskbook.wallet.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.persona.export.PersonaServices import kotlinx.coroutines.flow.MutableStateFlow +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class WelcomeViewModel( private val personaServices: PersonaServices, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/TokenDetailViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/TokenDetailViewModel.kt index 9060f632..2fc41b3b 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/TokenDetailViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/TokenDetailViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.util.DateUtils import com.dimension.maskbook.wallet.repository.ITokenRepository @@ -30,6 +28,8 @@ import com.dimension.maskbook.wallet.repository.IWalletRepository import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class TokenDetailViewModel( private val id: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/TouchIdEnableViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/TouchIdEnableViewModel.kt index 1ef59f6a..9dcac7c8 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/TouchIdEnableViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/TouchIdEnableViewModel.kt @@ -20,7 +20,7 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets -import androidx.lifecycle.ViewModel +import moe.tlaster.precompose.viewmodel.ViewModel class TouchIdEnableViewModel : ViewModel() { fun enable(onEnable: () -> Unit) { diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/UnlockWalletViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/UnlockWalletViewModel.kt index dfdd2ce9..4fd6240b 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/UnlockWalletViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/UnlockWalletViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.util.BiometricAuthenticator import com.dimension.maskbook.common.viewmodel.BiometricViewModel import com.dimension.maskbook.setting.export.SettingServices import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.viewModelScope class UnlockWalletViewModel( settingsRepository: SettingServices, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletBalancesViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletBalancesViewModel.kt index 22e70778..5cc9ed87 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletBalancesViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletBalancesViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.bigDecimal.BigDecimal import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.ext.humanizeDollar @@ -46,6 +44,8 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class WalletBalancesViewModel( private val repository: IWalletRepository, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletConnectManagementViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletConnectManagementViewModel.kt index be0f9305..1c2eafef 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletConnectManagementViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletConnectManagementViewModel.kt @@ -20,12 +20,12 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.wallet.export.model.WalletData import com.dimension.maskbook.wallet.repository.IWalletRepository import com.dimension.maskbook.wallet.walletconnect.WalletConnectClientManager import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class WalletConnectManagementViewModel( private val manager: WalletConnectClientManager, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletManagementModalViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletManagementModalViewModel.kt index 50e926b9..4bb419e5 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletManagementModalViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/WalletManagementModalViewModel.kt @@ -20,10 +20,10 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.repository.IWalletRepository +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class WalletManagementModalViewModel( private val repository: IWalletRepository, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/collectible/CollectibleDetailViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/collectible/CollectibleDetailViewModel.kt index 2959e962..9008b8af 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/collectible/CollectibleDetailViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/collectible/CollectibleDetailViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.collectible -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.util.DateUtils import com.dimension.maskbook.wallet.repository.ICollectibleRepository @@ -29,6 +27,8 @@ import com.dimension.maskbook.wallet.repository.ITransactionRepository import com.dimension.maskbook.wallet.repository.IWalletRepository import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class CollectibleDetailViewModel( private val id: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/collectible/CollectiblesViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/collectible/CollectiblesViewModel.kt index 5ffdaa30..0af6c869 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/collectible/CollectiblesViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/collectible/CollectiblesViewModel.kt @@ -20,7 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.collectible -import androidx.lifecycle.ViewModel import androidx.paging.PagingData import com.dimension.maskbook.wallet.export.model.WalletCollectibleData import com.dimension.maskbook.wallet.repository.ICollectibleRepository @@ -29,6 +28,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.mapNotNull +import moe.tlaster.precompose.viewmodel.ViewModel class CollectiblesViewModel( private val repository: ICollectibleRepository, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/create/CreateWalletRecoveryKeyViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/create/CreateWalletRecoveryKeyViewModel.kt index 1ecf5d20..1f17ba50 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/create/CreateWalletRecoveryKeyViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/create/CreateWalletRecoveryKeyViewModel.kt @@ -20,7 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.create -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.viewmodel.BaseMnemonicPhraseViewModel import com.dimension.maskbook.wallet.db.model.CoinPlatformType @@ -29,6 +28,7 @@ import com.dimension.maskbook.wallet.repository.WalletCreateOrImportResult import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.viewModelScope import java.util.UUID class CreateWalletRecoveryKeyViewModel( diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletDerivationPathViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletDerivationPathViewModel.kt index c96f1d7b..4deff364 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletDerivationPathViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletDerivationPathViewModel.kt @@ -21,8 +21,6 @@ package com.dimension.maskbook.wallet.viewmodel.wallets.import import androidx.compose.runtime.snapshots.SnapshotStateMap -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.db.model.CoinPlatformType import com.dimension.maskbook.wallet.repository.IWalletRepository @@ -37,6 +35,8 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class ImportWalletDerivationPathViewModel( private val wallet: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletKeystoreViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletKeystoreViewModel.kt index 8286c73f..6be9cc01 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletKeystoreViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletKeystoreViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.import -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.db.model.CoinPlatformType import com.dimension.maskbook.wallet.repository.IWalletRepository @@ -33,6 +31,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class ImportWalletKeystoreViewModel( private val wallet: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletMnemonicViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletMnemonicViewModel.kt index 3ee6d8d7..37f34174 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletMnemonicViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletMnemonicViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.import -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.export.model.WalletData import com.dimension.maskbook.wallet.repository.IWalletRepository @@ -30,6 +28,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope import java.util.UUID class ImportWalletMnemonicViewModel( diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletPrivateKeyViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletPrivateKeyViewModel.kt index 8388eacc..5571fdae 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletPrivateKeyViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/import/ImportWalletPrivateKeyViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.import -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.db.model.CoinPlatformType import com.dimension.maskbook.wallet.repository.IWalletRepository @@ -30,6 +28,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class ImportWalletPrivateKeyViewModel( private val wallet: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletBackupViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletBackupViewModel.kt index 0f2d3069..1c662224 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletBackupViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletBackupViewModel.kt @@ -20,11 +20,11 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.management -import androidx.lifecycle.ViewModel import com.dimension.maskbook.setting.export.SettingServices import com.dimension.maskbook.wallet.repository.IWalletRepository import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.mapNotNull +import moe.tlaster.precompose.viewmodel.ViewModel class WalletBackupViewModel( private val repository: IWalletRepository, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletDeleteViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletDeleteViewModel.kt index c6e5d770..6609043e 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletDeleteViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletDeleteViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.management -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.SettingServices import com.dimension.maskbook.wallet.repository.IWalletRepository @@ -29,6 +27,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class WalletDeleteViewModel( private val id: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletRenameViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletRenameViewModel.kt index 91a822de..ca2f3331 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletRenameViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletRenameViewModel.kt @@ -20,11 +20,11 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.management -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.repository.IWalletRepository import kotlinx.coroutines.flow.MutableStateFlow +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class WalletRenameViewModel( private val walletId: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletSwitchEditViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletSwitchEditViewModel.kt index 535e0a25..11abb16b 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletSwitchEditViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletSwitchEditViewModel.kt @@ -20,10 +20,10 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.management -import androidx.lifecycle.ViewModel import com.dimension.maskbook.wallet.repository.IWalletRepository import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel class WalletSwitchEditViewModel( private val id: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletSwitchViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletSwitchViewModel.kt index 9ba12703..281dc7d2 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletSwitchViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletSwitchViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.management -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.export.model.ChainType import com.dimension.maskbook.wallet.export.model.WalletData @@ -29,6 +27,8 @@ import com.dimension.maskbook.wallet.repository.IWalletRepository import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class WalletSwitchViewModel( private val walletRepository: IWalletRepository diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletTransactionHistoryViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletTransactionHistoryViewModel.kt index 711ca4c9..8f865108 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletTransactionHistoryViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/management/WalletTransactionHistoryViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.management -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.util.DateUtils import com.dimension.maskbook.wallet.repository.ITransactionRepository @@ -30,6 +28,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class WalletTransactionHistoryViewModel( private val repository: IWalletRepository, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/AddContactViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/AddContactViewModel.kt index 4606116c..0ccfcd90 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/AddContactViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/AddContactViewModel.kt @@ -20,13 +20,13 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.send -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.ext.onFinished import com.dimension.maskbook.wallet.usecase.AddContactUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class AddContactViewModel( private val addContact: AddContactUseCase, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/GasFeeViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/GasFeeViewModel.kt index c5e1d9c3..c3babc70 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/GasFeeViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/GasFeeViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.send -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.ext.humanizeMinutes import com.dimension.maskbook.common.ext.onFinished @@ -42,6 +40,8 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope import java.math.BigDecimal class GasFeeViewModel( diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SearchAddressViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SearchAddressViewModel.kt index f3d6d9e2..cf1f618a 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SearchAddressViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SearchAddressViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.send -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.Validator import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.export.model.ChainType @@ -37,6 +35,8 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope sealed class EnsData { object Loading : EnsData() diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SearchTradableViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SearchTradableViewModel.kt index 3b861e1b..75769a5d 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SearchTradableViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SearchTradableViewModel.kt @@ -20,14 +20,14 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.send -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.usecase.GetWalletCollectibleCollectionsUseCase import com.dimension.maskbook.wallet.usecase.GetWalletTokensUseCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class SearchTradableViewModel( getWalletTokens: GetWalletTokensUseCase, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SendConfirmViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SendConfirmViewModel.kt index 253fa0c6..c621c1cd 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SendConfirmViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/SendConfirmViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.send -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.bigDecimal.BigDecimal import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.ext.onFinished @@ -35,6 +33,8 @@ import com.dimension.maskbook.wallet.usecase.SendWalletCollectibleUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class SendConfirmViewModel( private val toAddress: String, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/TransferDetailViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/TransferDetailViewModel.kt index a339179a..85cc563b 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/TransferDetailViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/TransferDetailViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.send -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.export.model.TradableData import com.dimension.maskbook.wallet.export.model.WalletCollectibleData @@ -38,6 +36,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope import java.math.BigDecimal class TransferDetailViewModel( diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/Web3TransactionConfirmViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/Web3TransactionConfirmViewModel.kt index 576bfcfa..fa35c409 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/Web3TransactionConfirmViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/send/Web3TransactionConfirmViewModel.kt @@ -20,8 +20,6 @@ */ package com.dimension.maskbook.wallet.viewmodel.wallets.send -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.bigDecimal.BigDecimal import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.common.ext.onFinished @@ -43,6 +41,8 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class Web3TransactionConfirmViewModel( private val data: SendTransactionData, diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/walletconnect/WalletConnectViewModel.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/walletconnect/WalletConnectViewModel.kt index 2e84409e..c580c6f4 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/walletconnect/WalletConnectViewModel.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/wallets/walletconnect/WalletConnectViewModel.kt @@ -26,8 +26,6 @@ import android.content.pm.PackageManager import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY import android.content.pm.ResolveInfo import android.net.Uri -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.wallet.db.model.CoinPlatformType import com.dimension.maskbook.wallet.export.model.ChainType @@ -41,6 +39,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope sealed class WalletConnectResult { data class Success(val switchNetwork: Boolean = false) : WalletConnectResult() From 32c6e543427e5ea9867a31582f4cdd416680c490 Mon Sep 17 00:00:00 2001 From: seiko Date: Fri, 8 Apr 2022 19:32:47 +0800 Subject: [PATCH 02/18] move okhttp & retrofit to common --- common/okhttp/build.gradle.kts | 25 ----------------- common/okhttp/consumer-rules.pro | 0 common/okhttp/proguard-rules.pro | 21 -------------- .../src/androidMain/AndroidManifest.xml | 4 --- common/retrofit/build.gradle.kts | 28 ------------------- common/retrofit/consumer-rules.pro | 0 common/retrofit/proguard-rules.pro | 21 -------------- .../src/androidMain/AndroidManifest.xml | 4 --- .../com/dimension/maskbook/common/IsDebug.kt | 23 +++++++++++++++ .../com/dimension/maskbook/common/IsDebug.kt | 23 +++++++++++++++ .../maskbook/common/okhttp/Okhttp.kt | 9 +++--- .../maskbook/common/retrofit/Retrofit.kt | 0 settings.gradle.kts | 2 -- 13 files changed, 51 insertions(+), 109 deletions(-) delete mode 100644 common/okhttp/build.gradle.kts delete mode 100644 common/okhttp/consumer-rules.pro delete mode 100644 common/okhttp/proguard-rules.pro delete mode 100644 common/okhttp/src/androidMain/AndroidManifest.xml delete mode 100644 common/retrofit/build.gradle.kts delete mode 100644 common/retrofit/consumer-rules.pro delete mode 100644 common/retrofit/proguard-rules.pro delete mode 100644 common/retrofit/src/androidMain/AndroidManifest.xml create mode 100644 common/src/androidMain/kotlin/com/dimension/maskbook/common/IsDebug.kt create mode 100644 common/src/commonMain/kotlin/com/dimension/maskbook/common/IsDebug.kt rename common/{okhttp/src/androidMain => src/commonMain}/kotlin/com/dimension/maskbook/common/okhttp/Okhttp.kt (92%) rename common/{retrofit/src/androidMain => src/commonMain}/kotlin/com/dimension/maskbook/common/retrofit/Retrofit.kt (100%) diff --git a/common/okhttp/build.gradle.kts b/common/okhttp/build.gradle.kts deleted file mode 100644 index ba145bbe..00000000 --- a/common/okhttp/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - kotlin("multiplatform") - id("com.android.library") -} - -kotlin { - android() - sourceSets { - val androidMain by getting { - dependencies { - api("com.squareup.okhttp3:okhttp:${Versions.okhttp}") - api("com.squareup.okhttp3:logging-interceptor:${Versions.okhttp}") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.Kotlin.coroutines}") - } - } - val androidTest by getting { - dependencies { - } - } - } -} - -android { - setupLibrary() -} diff --git a/common/okhttp/consumer-rules.pro b/common/okhttp/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/common/okhttp/proguard-rules.pro b/common/okhttp/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/common/okhttp/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/okhttp/src/androidMain/AndroidManifest.xml b/common/okhttp/src/androidMain/AndroidManifest.xml deleted file mode 100644 index 2b9f24e6..00000000 --- a/common/okhttp/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/common/retrofit/build.gradle.kts b/common/retrofit/build.gradle.kts deleted file mode 100644 index 4c4f0a3c..00000000 --- a/common/retrofit/build.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - kotlin("multiplatform") - id("com.android.library") - kotlin("plugin.serialization") -} - -kotlin { - android() - sourceSets { - val androidMain by getting { - dependencies { - api(projects.common.okhttp) - api("com.squareup.retrofit2:retrofit:${Versions.retrofit}") - api("com.squareup.retrofit2:converter-scalars:${Versions.retrofit}") - api("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:${Versions.retrofitSerialization}") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") - } - } - val androidTest by getting { - dependencies { - } - } - } -} - -android { - setupLibrary() -} diff --git a/common/retrofit/consumer-rules.pro b/common/retrofit/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/common/retrofit/proguard-rules.pro b/common/retrofit/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/common/retrofit/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/retrofit/src/androidMain/AndroidManifest.xml b/common/retrofit/src/androidMain/AndroidManifest.xml deleted file mode 100644 index 0dafc6e7..00000000 --- a/common/retrofit/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/IsDebug.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/IsDebug.kt new file mode 100644 index 00000000..a7583cbc --- /dev/null +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/IsDebug.kt @@ -0,0 +1,23 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package com.dimension.maskbook.common + +actual val isDebug: Boolean = BuildConfig.DEBUG diff --git a/common/src/commonMain/kotlin/com/dimension/maskbook/common/IsDebug.kt b/common/src/commonMain/kotlin/com/dimension/maskbook/common/IsDebug.kt new file mode 100644 index 00000000..95b56823 --- /dev/null +++ b/common/src/commonMain/kotlin/com/dimension/maskbook/common/IsDebug.kt @@ -0,0 +1,23 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package com.dimension.maskbook.common + +expect val isDebug: Boolean diff --git a/common/okhttp/src/androidMain/kotlin/com/dimension/maskbook/common/okhttp/Okhttp.kt b/common/src/commonMain/kotlin/com/dimension/maskbook/common/okhttp/Okhttp.kt similarity index 92% rename from common/okhttp/src/androidMain/kotlin/com/dimension/maskbook/common/okhttp/Okhttp.kt rename to common/src/commonMain/kotlin/com/dimension/maskbook/common/okhttp/Okhttp.kt index b55cc50e..601600f6 100644 --- a/common/okhttp/src/androidMain/kotlin/com/dimension/maskbook/common/okhttp/Okhttp.kt +++ b/common/src/commonMain/kotlin/com/dimension/maskbook/common/okhttp/Okhttp.kt @@ -20,7 +20,7 @@ */ package com.dimension.maskbook.common.okhttp -import android.util.Log +import com.dimension.maskbook.common.isDebug import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.Call import okhttp3.Callback @@ -31,10 +31,10 @@ import java.io.IOException import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -val okHttpClient by lazy { +val okHttpClient: OkHttpClient by lazy { OkHttpClient.Builder() .apply { - if (BuildConfig.DEBUG) { + if (isDebug) { addInterceptor( HttpLoggingInterceptor(HttpLogger()).apply { setLevel(HttpLoggingInterceptor.Level.BODY) @@ -47,7 +47,8 @@ val okHttpClient by lazy { class HttpLogger : HttpLoggingInterceptor.Logger { override fun log(message: String) { - Log.i("HttpLogger", message) + // TODO + // Log.i("HttpLogger", message) } } diff --git a/common/retrofit/src/androidMain/kotlin/com/dimension/maskbook/common/retrofit/Retrofit.kt b/common/src/commonMain/kotlin/com/dimension/maskbook/common/retrofit/Retrofit.kt similarity index 100% rename from common/retrofit/src/androidMain/kotlin/com/dimension/maskbook/common/retrofit/Retrofit.kt rename to common/src/commonMain/kotlin/com/dimension/maskbook/common/retrofit/Retrofit.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 610a851d..f6dc9d55 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,8 +25,6 @@ include( ":common", ":common:gecko", ":common:gecko:sample", - ":common:okhttp", - ":common:retrofit", ":common:routeProcessor", ":common:routeProcessor:annotations", ":common:bigDecimal", From ea5e1356bb49b64ab69576eb7667d09119c78cb2 Mon Sep 17 00:00:00 2001 From: seiko Date: Fri, 8 Apr 2022 19:36:03 +0800 Subject: [PATCH 03/18] compileOnly dependencies for export --- extension/export/build.gradle.kts | 4 ++-- persona/export/build.gradle.kts | 4 ++-- setting/export/build.gradle.kts | 4 ++-- wallet/export/build.gradle.kts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extension/export/build.gradle.kts b/extension/export/build.gradle.kts index c6a4f35b..1b9c5d43 100644 --- a/extension/export/build.gradle.kts +++ b/extension/export/build.gradle.kts @@ -8,8 +8,8 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") + compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") } } val commonTest by getting { diff --git a/persona/export/build.gradle.kts b/persona/export/build.gradle.kts index c6a4f35b..1b9c5d43 100644 --- a/persona/export/build.gradle.kts +++ b/persona/export/build.gradle.kts @@ -8,8 +8,8 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") + compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") } } val commonTest by getting { diff --git a/setting/export/build.gradle.kts b/setting/export/build.gradle.kts index c6a4f35b..1b9c5d43 100644 --- a/setting/export/build.gradle.kts +++ b/setting/export/build.gradle.kts @@ -8,8 +8,8 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") + compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") } } val commonTest by getting { diff --git a/wallet/export/build.gradle.kts b/wallet/export/build.gradle.kts index 43a10bc9..0f3ce071 100644 --- a/wallet/export/build.gradle.kts +++ b/wallet/export/build.gradle.kts @@ -8,9 +8,9 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation(projects.common.bigDecimal) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") + compileOnly(projects.common.bigDecimal) + compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") + compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.Kotlin.serialization}") } } val commonTest by getting { From c1009701a1d8a6578dbb748719d3f013f5b943a4 Mon Sep 17 00:00:00 2001 From: seiko Date: Fri, 8 Apr 2022 21:51:25 +0800 Subject: [PATCH 04/18] [wip]migrate precompose --- .../dimension/maskbook/entry/ui/scene/IntroScene.kt | 1 + .../labs/ui/scenes/redpacket/LuckyDropModal.kt | 5 +++-- .../maskbook/persona/route/RegisterRoute.kt | 2 +- .../maskbook/persona/route/SynchronizationRoute.kt | 8 +++++--- .../persona/ui/scenes/BackupPasswordScene.kt | 1 + .../maskbook/persona/ui/scenes/LogoutDialog.kt | 1 + .../maskbook/persona/ui/scenes/PersonaMenuScene.kt | 11 ++++++----- .../maskbook/persona/ui/scenes/SwitchPersonaModal.kt | 5 ++--- .../register/recovery/local/RecoveryLocalHost.kt | 1 + .../recovery/remote/RemoteBackupRecoveryHost.kt | 1 + .../maskbook/persona/ui/tab/PersonasTabScreen.kt | 1 + .../dimension/maskbook/setting/route/BackupRoute.kt | 1 + .../maskbook/setting/route/SettingsRoute.kt | 2 ++ .../setting/ui/scenes/backup/BackupLocalScene.kt | 2 ++ .../dimension/maskbook/wallet/route/WalletsRoute.kt | 4 +++- .../scenes/wallets/create/create/CreateWalletHost.kt | 12 ++++++------ .../ui/scenes/wallets/send/SendTokenConfirmModal.kt | 2 ++ 17 files changed, 39 insertions(+), 21 deletions(-) diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/IntroScene.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/IntroScene.kt index ad1f1486..bcd94bdb 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/IntroScene.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/scene/IntroScene.kt @@ -62,6 +62,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.dimension.maskbook.common.ext.navigate import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.routeProcessor.annotations.NavGraphDestination diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/redpacket/LuckyDropModal.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/redpacket/LuckyDropModal.kt index 38fa936f..ca0280ea 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/redpacket/LuckyDropModal.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/ui/scenes/redpacket/LuckyDropModal.kt @@ -20,6 +20,7 @@ */ package com.dimension.maskbook.labs.ui.scenes.redpacket +import android.net.Uri import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -138,7 +139,7 @@ fun LuckDropModal( WalletTokenCard( wallet = stateData.wallet, onClick = { - navController.navigateUri(Deeplinks.Wallet.SwitchWallet) + navController.navigateUri(Uri.parse(Deeplinks.Wallet.SwitchWallet)) } ) Spacer(Modifier.height(24.dp)) @@ -146,7 +147,7 @@ fun LuckDropModal( enabled = stateData.buttonEnabled && !loading, onClick = { viewModel.getSendTransactionData(stateData)?.let { data -> - navController.navigateUri(Deeplinks.Wallet.SendTokenConfirm(data)) + navController.navigateUri(Uri.parse(Deeplinks.Wallet.SendTokenConfirm(data))) } }, modifier = Modifier.fillMaxWidth(), diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/RegisterRoute.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/RegisterRoute.kt index 8112e923..c064e9c0 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/RegisterRoute.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/RegisterRoute.kt @@ -312,7 +312,7 @@ fun RegisterRecoveryAlreadyExists( PersonaAlreadyExitsDialog( onBack = onBack, onConfirm = { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona)), navOptions { popUpTo(PersonaRoute.Register.Init) { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt index 643efa49..5d4f0c99 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt @@ -31,6 +31,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import com.dimension.maskbook.common.ext.decodeBase64 import com.dimension.maskbook.common.ext.ifNullOrEmpty +import com.dimension.maskbook.common.ext.navOptions +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.Persona @@ -70,7 +72,7 @@ fun SynchronizationScan( onBack = onBack, onResult = { try { - navController.navigate( + navController.navigateUri( Uri.parse(it), navOptions { popUpTo(PersonaRoute.Synchronization.Scan) { @@ -112,7 +114,7 @@ fun SynchronizationSuccess( }, buttons = { PrimaryButton(onClick = { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona)), navOptions { launchSingleTop = true @@ -169,7 +171,7 @@ fun SynchronizationPersonaAlreadyExists( PersonaAlreadyExitsDialog( onBack = onBack, onConfirm = { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona)), navOptions { launchSingleTop = true diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt index 8598aeaa..1342dc86 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt @@ -23,6 +23,7 @@ package com.dimension.maskbook.persona.ui.scenes import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import com.dimension.maskbook.common.ext.navOptions import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/LogoutDialog.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/LogoutDialog.kt index 194dab81..1d6c58da 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/LogoutDialog.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/LogoutDialog.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.navigationComposeDialog import com.dimension.maskbook.common.route.navigationComposeDialogPackage import com.dimension.maskbook.common.routeProcessor.annotations.Back diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaMenuScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaMenuScene.kt index 4480b9c6..e1a67f1f 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaMenuScene.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/PersonaMenuScene.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.dimension.maskbook.common.ext.encodeBase64 +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage @@ -151,9 +152,9 @@ fun PersonaMenuScene( onClick = { // first check if it has backup password if (backupPassword.isEmpty()) { - navController.navigate(Uri.parse(Deeplinks.Setting.SetupPasswordDialog)) + navController.navigateUri(Uri.parse(Deeplinks.Setting.SetupPasswordDialog)) } else { - navController.navigate(Uri.parse(Deeplinks.Persona.BackUpPassword(PersonaRoute.ExportPrivateKey))) + navController.navigateUri(Uri.parse(Deeplinks.Persona.BackUpPassword(PersonaRoute.ExportPrivateKey))) } } ) { @@ -176,10 +177,10 @@ fun PersonaMenuScene( onClick = { // first check if it has backup password if (backupPassword.isEmpty()) { - navController.navigate(Uri.parse(Deeplinks.Setting.SetupPasswordDialog)) + navController.navigateUri(Uri.parse(Deeplinks.Setting.SetupPasswordDialog)) } else { currentPersona?.let { - navController.navigate( + navController.navigateUri( Uri.parse( Deeplinks.Persona.BackUpPassword( PersonaRoute.DownloadQrCode( @@ -210,7 +211,7 @@ fun PersonaMenuScene( modifier = Modifier.fillMaxWidth(), elevation = 0.dp, onClick = { - navController.navigate( + navController.navigateUri( Uri.parse(if (backupPassword.isEmpty() || paymentPassword.isEmpty()) Deeplinks.Setting.SetupPasswordDialog else Deeplinks.Setting.BackupData.BackupSelection) ) } diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/SwitchPersonaModal.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/SwitchPersonaModal.kt index bebf13df..64c9e39c 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/SwitchPersonaModal.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/SwitchPersonaModal.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage @@ -98,9 +99,7 @@ fun SwitchPersonaModal( MaskSelection( selected = false, onClicked = { - navController.navigate( - Uri.parse(Deeplinks.Persona.Register.CreatePersona) - ) + navController.navigateUri(Uri.parse(Deeplinks.Persona.Register.CreatePersona)) }, content = { Text( diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt index 3bc8e6fb..bcf7c0ff 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt @@ -58,6 +58,7 @@ import androidx.compose.ui.unit.dp import com.dimension.maskbook.common.ext.getNestedNavigationViewModel import com.dimension.maskbook.common.ext.humanizeFileSize import com.dimension.maskbook.common.ext.humanizeTimestamp +import com.dimension.maskbook.common.ext.navigate import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/remote/RemoteBackupRecoveryHost.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/remote/RemoteBackupRecoveryHost.kt index 018e07c4..7bd15b78 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/remote/RemoteBackupRecoveryHost.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/remote/RemoteBackupRecoveryHost.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import com.dimension.maskbook.common.ext.navigate import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage import com.dimension.maskbook.common.route.navigationComposeDialog diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt index 13800123..76b21ed9 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt @@ -23,6 +23,7 @@ package com.dimension.maskbook.persona.ui.tab import android.net.Uri import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable +import com.dimension.maskbook.common.ext.navOptions import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/BackupRoute.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/BackupRoute.kt index 9a08c94e..24827e6a 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/BackupRoute.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/BackupRoute.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import com.dimension.maskbook.common.ext.navigate import com.dimension.maskbook.common.ext.navigateWithPopSelf import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/SettingsRoute.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/SettingsRoute.kt index 60c65fa2..4e7cf30a 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/SettingsRoute.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/route/SettingsRoute.kt @@ -31,6 +31,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import com.dimension.maskbook.common.ext.navigate +import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupLocalScene.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupLocalScene.kt index 8d31ca53..0898974f 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupLocalScene.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/ui/scenes/backup/BackupLocalScene.kt @@ -46,6 +46,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp +import com.dimension.maskbook.common.ext.getNestedNavigationViewModel +import com.dimension.maskbook.common.ext.navigate import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.routeProcessor.annotations.Back diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/route/WalletsRoute.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/route/WalletsRoute.kt index b993b391..1c5e881e 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/route/WalletsRoute.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/route/WalletsRoute.kt @@ -36,6 +36,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.buildAnnotatedString +import com.dimension.maskbook.common.ext.navOptions +import com.dimension.maskbook.common.ext.navigate import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.ext.shareText import com.dimension.maskbook.common.route.CommonRoute @@ -571,7 +573,7 @@ fun WalletIntroHostLegal( navController.navigate( route, navOptions { - popUpTo(id = navBackStackEntry.destination.id) { + popUpTo(route = navBackStackEntry.route.route) { inclusive = true } launchSingleTop = true diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt index f05b6f92..d034b21b 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt @@ -25,16 +25,15 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.navigation.NavController -import androidx.navigation.navOptions import com.dimension.maskbook.common.ext.getNestedNavigationViewModel -import com.dimension.maskbook.common.ext.observeAsState +import com.dimension.maskbook.common.ext.navOptions +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable @@ -50,6 +49,7 @@ import com.dimension.maskbook.wallet.R import com.dimension.maskbook.wallet.route.WalletRoute import com.dimension.maskbook.wallet.viewmodel.wallets.create.CreateWalletRecoveryKeyViewModel import moe.tlaster.precompose.navigation.NavController +import org.koin.core.parameter.parametersOf private const val GeneratedRouteName = "createWalletRoute" @@ -69,7 +69,7 @@ fun PharseRoute( .getNestedNavigationViewModel(WalletRoute.CreateWallet.Route) { parametersOf(wallet) } - val words by viewModel.words.observeAsState(initial = emptyList()) + val words by viewModel.words.collectAsState(initial = emptyList()) MnemonicPhraseScene( words = words.map { it.word }, onRefreshWords = { @@ -92,7 +92,7 @@ fun ConfirmRoute( ) { val onDone = remember { { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Wallet)), navOptions = navOptions { launchSingleTop = true diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/SendTokenConfirmModal.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/SendTokenConfirmModal.kt index 940ca11e..6a0b88f5 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/SendTokenConfirmModal.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/send/SendTokenConfirmModal.kt @@ -29,6 +29,8 @@ import com.dimension.maskbook.common.ext.decodeJson import com.dimension.maskbook.common.ext.fromHexString import com.dimension.maskbook.common.ext.humanizeDollar import com.dimension.maskbook.common.ext.humanizeToken +import com.dimension.maskbook.common.ext.sendEvent +import com.dimension.maskbook.common.model.ResultEvent import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.composable import com.dimension.maskbook.common.route.navigationComposeBottomSheet From 7b331fef5198d96ee0143f227e9847bc34068ba1 Mon Sep 17 00:00:00 2001 From: seiko Date: Sat, 9 Apr 2022 08:42:54 +0800 Subject: [PATCH 05/18] update parse of navigation path & query --- .../precompose/navigation/BackStackEntry.kt | 17 +++++++++++------ .../precompose/navigation/QueryString.kt | 14 +------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt index 3d19a513..ce509009 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt @@ -66,22 +66,27 @@ class BackStackEntry internal constructor( } } - inline fun path(path: String, default: T? = null): T? { - val value = pathMap[path] ?: return default + inline fun path(path: String): T { + val value = requireNotNull(pathMap[path]) return convertValue(value) } - inline fun query(name: String, default: T? = null): T? { - return queryString?.query(name, default) + inline fun query(name: String): T? { + return query(name, null) } - inline fun queryList(name: String): List { + inline fun query(name: String, default: T): T { + val value = queryString?.map?.get(name)?.firstOrNull() ?: return default + return convertValue(value) + } + + inline fun queryList(name: String): List { val value = queryString?.map?.get(name) ?: return emptyList() return value.map { convertValue(it) } } } -inline fun convertValue(value: String): T? { +inline fun convertValue(value: String): T { return when (T::class) { Int::class -> value.toIntOrNull() Long::class -> value.toLongOrNull() diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.kt index e4b1cd2b..c0ca78d1 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.kt @@ -27,9 +27,7 @@ data class QueryString( rawInput .split("?") .lastOrNull() - .let { - it ?: "" - } + .let { it ?: "" } .split("&") .asSequence() .map { it.split("=") } @@ -42,13 +40,3 @@ data class QueryString( .toMap() } } - -inline fun QueryString.query(name: String, default: T? = null): T? { - val value = map[name]?.firstOrNull() ?: return default - return convertValue(value) -} - -inline fun QueryString.queryList(name: String): List { - val value = map[name] ?: return emptyList() - return value.map { convertValue(it) } -} From 2908ddc2bf2e2490502fcd893d939131dd4ebe8f Mon Sep 17 00:00:00 2001 From: seiko Date: Mon, 11 Apr 2022 15:30:03 +0800 Subject: [PATCH 06/18] compose jb to 1.1.1 --- buildSrc/src/main/kotlin/Config.kt | 10 ---------- buildSrc/src/main/kotlin/Versions.kt | 3 +-- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 9d29b109..08389e06 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -35,16 +35,6 @@ fun com.android.build.api.dsl.LibraryExtension.setupLibrary() { sourceSets["debug"].java.srcDir("build/generated/ksp/android/androidDebug/kotlin") } - -fun com.android.build.gradle.LibraryExtension.withCompose() { - buildFeatures { - compose = true - } - composeOptions { - kotlinCompilerExtensionVersion = Versions.compose - } -} - fun Project.kspAndroid(dependencyNotation: Any) { project.dependencies.add("kspAndroid", dependencyNotation) } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 758323aa..b30f84f8 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -22,8 +22,7 @@ object Versions { const val ksp = "${Kotlin.lang}-1.0.4" const val spotless = "6.3.0" const val ktlint = "0.43.2" - const val compose_jb = "1.1.0" - const val compose = "1.1.0" + const val compose_jb = "1.1.1" const val accompanist = "0.23.0" const val navigation = "2.4.1" const val lifecycle = "2.4.1" From f5fa3fb8576224e5981b88e9d4e2d30a13f70ed1 Mon Sep 17 00:00:00 2001 From: seiko Date: Mon, 11 Apr 2022 16:53:35 +0800 Subject: [PATCH 07/18] [wip]migrate precompose navigation --- .../common/routeProcessor/RouteDefinition.kt | 8 +- .../routeProcessor/RouteGraphProcessor.kt | 51 ++------ .../maskbook/common/route/AnimatedNavHost.kt | 19 --- .../dimension/maskbook/common/route/Consts.kt | 76 ------------ .../maskbook/common/route/RouteBuilder.kt | 100 +++++++++++++++ .../ui/widget/MaskBottomSheetNavigator.kt | 86 ++++++------- .../maskbook/common/ui/widget/RouteHost.kt | 116 ++++++++---------- .../precompose/navigation/Navigator.kt | 13 +- .../precompose/navigation/RouteBuilder.kt | 2 + .../navigation/RouteStackManager.kt | 101 ++++++++++----- .../navigation/route/ComposeRoute.kt | 7 +- .../navigation/route/DialogRoute.kt | 1 + .../precompose/navigation/route/Route.kt | 2 - .../precompose/navigation/route/SceneRoute.kt | 11 +- .../com/dimension/maskbook/entry/ui/Router.kt | 17 +-- .../maskbook/extension/ExtensionSetup.kt | 28 ++--- .../walletconnect/WalletConnectModal.kt | 8 +- 17 files changed, 318 insertions(+), 328 deletions(-) delete mode 100644 common/src/androidMain/kotlin/com/dimension/maskbook/common/route/AnimatedNavHost.kt create mode 100644 common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt diff --git a/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteDefinition.kt b/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteDefinition.kt index 5269fed7..2d4103f8 100644 --- a/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteDefinition.kt +++ b/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteDefinition.kt @@ -85,19 +85,19 @@ internal data class ParameterRouteDefinition( childRoute.forEach { if (it is FunctionRouteDefinition) { val pathParams = it.parameters.filter { !it.parameter.type.resolve().isMarkedNullable } - val queryParams = it.parameters.filter { it.parameter.type.resolve().isMarkedNullable } + // val queryParams = it.parameters.filter { it.parameter.type.resolve().isMarkedNullable } addProperty( PropertySpec.builder("path", String::class) .addModifiers(KModifier.CONST) .initializer( - "%S + %S + %S + %S + %S + %S + %S", + "%S + %S + %S + %S + %S", parentPath, RouteDivider, name, if (pathParams.any()) RouteDivider else "", pathParams.joinToString(RouteDivider) { "{${it.name}}" }, - if (queryParams.any()) "?" else "", - queryParams.joinToString("&") { "${it.name}={${it.name}}" } + // if (queryParams.any()) "?" else "", + // queryParams.joinToString("&") { "${it.name}={${it.name}}" } ) .build() ) diff --git a/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteGraphProcessor.kt b/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteGraphProcessor.kt index 07ff9630..13e33636 100644 --- a/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteGraphProcessor.kt +++ b/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteGraphProcessor.kt @@ -42,13 +42,12 @@ import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview -import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.toTypeName import com.squareup.kotlinpoet.ksp.writeTo import com.squareup.kotlinpoet.withIndent -private val navControllerType = ClassName("androidx.navigation", "NavController") -private val navBackStackEntryType = ClassName("androidx.navigation", "NavBackStackEntry") +private val navControllerType = ClassName("moe.tlaster.precompose.navigation", "NavController") +private val navBackStackEntryType = ClassName("moe.tlaster.precompose.navigation", "BackStackEntry") private const val navControllerName = "controller" @OptIn(KotlinPoetKspPreview::class, KspExperimental::class) @@ -93,14 +92,11 @@ internal class RouteGraphProcessor( ) val packageName = data.first().packageName FileSpec.builder(packageName.asString(), "RouteGraph") - .addImport("androidx.navigation", "NavType") - .addImport("androidx.navigation", "navDeepLink") - .addImport("androidx.navigation", "navArgument") .also { fileBuilder -> fileBuilder.addFunction( FunSpec.builder(generatedFunctionName) .addModifiers(KModifier.INTERNAL) - .receiver(ClassName("androidx.navigation", "NavGraphBuilder")) + .receiver(ClassName("moe.tlaster.precompose.navigation", "RouteBuilder")) .addParameter( navControllerName, navControllerType, @@ -133,42 +129,11 @@ internal class RouteGraphProcessor( "route = %S,", annotation.route, ) - val parameters = ksFunctionDeclaration.parameters.filter { - it.isAnnotationPresent( - Query::class - ) || it.isAnnotationPresent(Path::class) - } - if (parameters.isNotEmpty()) { - addStatement("arguments = listOf(") - withIndent { - parameters.forEach { - val type = it.type.resolve() - val typeName = type.toClassName() - - val argumentName = when { - it.isAnnotationPresent(Path::class) -> it.getAnnotationsByType(Path::class).first().name - it.isAnnotationPresent(Query::class) -> it.getAnnotationsByType(Query::class).first().name - else -> it.name?.asString().orEmpty() - } - - addStatement( - "navArgument(%S) { type = NavType.%NType; nullable = %L },", - argumentName, - if (typeName.isBoolean) "Bool" else type.declaration.simpleName.asString(), - it.isAnnotationPresent(Query::class) && !typeName.isLong - ) - } - } - addStatement("),") - } if (annotation.deeplink.isNotEmpty()) { addStatement("deepLinks = listOf(") withIndent { annotation.deeplink.forEach { - addStatement( - "navDeepLink { uriPattern = %S }", - it - ) + add("%S", it) } } addStatement("),") @@ -187,17 +152,17 @@ internal class RouteGraphProcessor( if (it.isAnnotationPresent(Path::class)) { val path = it.getAnnotationsByType(Path::class).first() builder.addStatement( - "val ${it.name?.asString()} = it.arguments!!.get(%S) as %T", + "val ${it.name?.asString()}: %T = it.path(%S)!!", + it.type.toTypeName(), path.name, - it.type.toTypeName() ) } else if (it.isAnnotationPresent(Query::class)) { val query = it.getAnnotationsByType(Query::class).first() builder.addStatement( - "val ${it.name?.asString()} = it.arguments?.get(%S) as? %T", + "val ${it.name?.asString()}: %T? = it.query(%S)", + it.type.toTypeName(), query.name, - it.type.toTypeName() ) } } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/AnimatedNavHost.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/AnimatedNavHost.kt deleted file mode 100644 index 4ab656df..00000000 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/AnimatedNavHost.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.dimension.maskbook.common.route - -import androidx.compose.runtime.Composable -import moe.tlaster.precompose.navigation.NavController -import moe.tlaster.precompose.navigation.NavHost -import moe.tlaster.precompose.navigation.RouteBuilder - -@Composable -fun AnimatedNavHost( - navController: NavController, - initialRoute: String, - builder: RouteBuilder.() -> Unit, -) { - NavHost( - navController = navController, - initialRoute = initialRoute, - builder = builder, - ) -} \ No newline at end of file diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt index a38fe1c0..ca425919 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt @@ -20,10 +20,6 @@ */ package com.dimension.maskbook.common.route -import androidx.compose.runtime.Composable -import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.RouteBuilder - const val navigationComposeDialogPackage = "com.dimension.maskbook.common.route" const val navigationComposeDialog = "dialog" @@ -35,75 +31,3 @@ const val navigationComposeModalComposable = "modalComposable" const val navigationComposeBottomSheetPackage = "com.dimension.maskbook.common.route" const val navigationComposeBottomSheet = "bottomSheet" - -fun RouteBuilder.composable( - route: String, - // arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable (BackStackEntry) -> Unit -) { - scene( - route = route, - // arguments = arguments, - deepLinks = deepLinks, - content = content, - ) -} - -fun RouteBuilder.modalComposable( - route: String, - // arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable (BackStackEntry) -> Unit -) { - scene( - route = route, - // arguments = arguments, - deepLinks = deepLinks, - content = content, - // enterTransition = { - // slideInVertically { it } - // }, - // exitTransition = null, - // popEnterTransition = null, - // popExitTransition = { - // slideOutVertically { it } - // }, - ) -} - -fun RouteBuilder.bottomSheet( - route: String, - // arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable (BackStackEntry) -> Unit -) { - scene( - route = route, - // arguments = arguments, - deepLinks = deepLinks, - content = content - ) -} - -fun RouteBuilder.dialog( - route: String, - // arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable (BackStackEntry) -> Unit -) { - dialog( - route = route, - // arguments = arguments, - // deepLinks = deepLinks, - content = content, - ) -} - -fun RouteBuilder.navigation( - route: String, - startDestination: String, - content: @Composable RouteBuilder.(BackStackEntry) -> Unit -) { - // TODO -} diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt new file mode 100644 index 00000000..62ea05f3 --- /dev/null +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt @@ -0,0 +1,100 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package com.dimension.maskbook.common.route + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.RouteBuilder + +@OptIn(ExperimentalAnimationApi::class) +fun RouteBuilder.composable( + route: String, + // arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + scene( + route = route, + // arguments = arguments, + deepLinks = deepLinks, + content = content, + ) +} + +@OptIn(ExperimentalAnimationApi::class) +fun RouteBuilder.modalComposable( + route: String, + // arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + scene( + route = route, + // arguments = arguments, + deepLinks = deepLinks, + content = content, + // enterTransition = { + // slideInVertically { it } + // }, + // exitTransition = null, + // popEnterTransition = null, + // popExitTransition = { + // slideOutVertically { it } + // }, + ) +} + +fun RouteBuilder.bottomSheet( + route: String, + // arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + // bottomSheet( + // route = route, + // // arguments = arguments, + // deepLinks = deepLinks, + // content = content + // ) +} + +fun RouteBuilder.dialog( + route: String, + // arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + dialog( + route = route, + // arguments = arguments, + deepLinks = deepLinks, + content = content, + ) +} + +fun RouteBuilder.navigation( + route: String, + startDestination: String, + content: @Composable RouteBuilder.(BackStackEntry) -> Unit +) { + // TODO +} diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt index 5f25f379..e89f7b64 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt @@ -20,46 +20,46 @@ */ package com.dimension.maskbook.common.ui.widget -import androidx.compose.animation.core.AnimationSpec -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.SwipeableDefaults -import androidx.compose.material.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshotFlow -import com.google.accompanist.navigation.material.BottomSheetNavigator -import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi - -@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterialNavigationApi::class,) -@Composable -fun rememberMaskBottomSheetNavigator( - animationSpec: AnimationSpec = SwipeableDefaults.AnimationSpec, - skipHalfExpanded: Boolean = true, -): BottomSheetNavigator { - val sheetState = rememberModalBottomSheetState( - ModalBottomSheetValue.Hidden, - animationSpec - ) - - if (skipHalfExpanded) { - LaunchedEffect(sheetState) { - snapshotFlow { sheetState.isAnimationRunning } - .collect { - with(sheetState) { - val isOpening = currentValue == ModalBottomSheetValue.Hidden && targetValue == ModalBottomSheetValue.HalfExpanded - val isClosing = currentValue == ModalBottomSheetValue.Expanded && targetValue == ModalBottomSheetValue.HalfExpanded - when { - isOpening -> animateTo(ModalBottomSheetValue.Expanded) - isClosing -> animateTo(ModalBottomSheetValue.Hidden) - } - } - } - } - } - - return remember(sheetState) { - BottomSheetNavigator(sheetState = sheetState) - } -} +// import androidx.compose.animation.core.AnimationSpec +// import androidx.compose.material.ExperimentalMaterialApi +// import androidx.compose.material.ModalBottomSheetValue +// import androidx.compose.material.SwipeableDefaults +// import androidx.compose.material.rememberModalBottomSheetState +// import androidx.compose.runtime.Composable +// import androidx.compose.runtime.LaunchedEffect +// import androidx.compose.runtime.remember +// import androidx.compose.runtime.snapshotFlow +// import com.google.accompanist.navigation.material.BottomSheetNavigator +// import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi +// +// @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterialNavigationApi::class,) +// @Composable +// fun rememberMaskBottomSheetNavigator( +// animationSpec: AnimationSpec = SwipeableDefaults.AnimationSpec, +// skipHalfExpanded: Boolean = true, +// ): BottomSheetNavigator { +// val sheetState = rememberModalBottomSheetState( +// ModalBottomSheetValue.Hidden, +// animationSpec +// ) +// +// if (skipHalfExpanded) { +// LaunchedEffect(sheetState) { +// snapshotFlow { sheetState.isAnimationRunning } +// .collect { +// with(sheetState) { +// val isOpening = currentValue == ModalBottomSheetValue.Hidden && targetValue == ModalBottomSheetValue.HalfExpanded +// val isClosing = currentValue == ModalBottomSheetValue.Expanded && targetValue == ModalBottomSheetValue.HalfExpanded +// when { +// isOpening -> animateTo(ModalBottomSheetValue.Expanded) +// isClosing -> animateTo(ModalBottomSheetValue.Hidden) +// } +// } +// } +// } +// } +// +// return remember(sheetState) { +// BottomSheetNavigator(sheetState = sheetState) +// } +// } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt index e4abd950..4c223ca7 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt @@ -24,75 +24,67 @@ import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.tween import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.unit.dp -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController -import com.dimension.maskbook.common.ui.theme.modalScrimColor -import com.google.accompanist.navigation.animation.AnimatedNavHost -import com.google.accompanist.navigation.animation.rememberAnimatedNavController -import com.google.accompanist.navigation.material.BottomSheetNavigator -import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi -import com.google.accompanist.navigation.material.ModalBottomSheetLayout +import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.NavHost +import moe.tlaster.precompose.navigation.RouteBuilder +import moe.tlaster.precompose.navigation.rememberNavController private const val navHostAnimationDurationMillis = 320 @ExperimentalAnimationApi -@ExperimentalMaterialNavigationApi @Composable fun RouteHost( - bottomSheetNavigator: BottomSheetNavigator = rememberMaskBottomSheetNavigator(), - navController: NavHostController = rememberAnimatedNavController(bottomSheetNavigator), + // bottomSheetNavigator: BottomSheetNavigator = rememberMaskBottomSheetNavigator(), + navController: NavController = rememberNavController(), startDestination: String, - builder: NavGraphBuilder.() -> Unit + builder: RouteBuilder.() -> Unit ) { - ModalBottomSheetLayout( - bottomSheetNavigator, - sheetBackgroundColor = MaterialTheme.colors.background, - sheetShape = MaterialTheme.shapes.large.copy( - bottomStart = CornerSize(0.dp), - bottomEnd = CornerSize(0.dp), - ), - scrimColor = MaterialTheme.colors.modalScrimColor, - ) { - AnimatedNavHost( - navController = navController, - startDestination = startDestination, - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = tween( - navHostAnimationDurationMillis - ) - ) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { -it }, - animationSpec = tween( - navHostAnimationDurationMillis - ) - ) - }, - popEnterTransition = { - slideInHorizontally( - initialOffsetX = { -it }, - animationSpec = tween( - navHostAnimationDurationMillis - ) - ) - }, - popExitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = tween( - navHostAnimationDurationMillis - ) - ) - }, - builder = builder, - ) - } + // ModalBottomSheetLayout( + // // bottomSheetNavigator, + // sheetBackgroundColor = MaterialTheme.colors.background, + // sheetShape = MaterialTheme.shapes.large.copy( + // bottomStart = CornerSize(0.dp), + // bottomEnd = CornerSize(0.dp), + // ), + // scrimColor = MaterialTheme.colors.modalScrimColor, + // ) { + NavHost( + navController = navController, + initialRoute = startDestination, + // enterTransition = { + // slideInHorizontally( + // initialOffsetX = { it }, + // animationSpec = tween( + // navHostAnimationDurationMillis + // ) + // ) + // }, + // exitTransition = { + // slideOutHorizontally( + // targetOffsetX = { -it }, + // animationSpec = tween( + // navHostAnimationDurationMillis + // ) + // ) + // }, + // popEnterTransition = { + // slideInHorizontally( + // initialOffsetX = { -it }, + // animationSpec = tween( + // navHostAnimationDurationMillis + // ) + // ) + // }, + // popExitTransition = { + // slideOutHorizontally( + // targetOffsetX = { it }, + // animationSpec = tween( + // navHostAnimationDurationMillis + // ) + // ) + // }, + builder = builder, + ) + // } } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt index 9a5514b2..4650a312 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -84,12 +84,15 @@ class NavController { * user presses the "Up" button marked with a left (or start)-facing arrow in the upper left * (or starting) corner of the app UI. */ - fun goBack() { - stackManager?.goBack() + fun goBack(route: String? = null, inclusive: Boolean = false) { + stackManager?.goBack( + route = route, + inclusive = inclusive, + ) } fun goBackWith(result: Any? = null) { - stackManager?.goBack(result) + stackManager?.goBack(result = result) } /** @@ -99,6 +102,10 @@ class NavController { goBack() } + fun popBackStack(route: String, inclusive: Boolean = false) { + goBack(route, inclusive) + } + /** * Check if navigator can navigate up */ diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt index 39d605f4..b72ee5c7 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt @@ -57,10 +57,12 @@ class RouteBuilder( */ fun dialog( route: String, + deepLinks: List = emptyList(), content: @Composable (BackStackEntry) -> Unit, ) { this.route += DialogRoute( route = route, + deepLinks = deepLinks, content = content ) } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt index a688adf0..f7831e0c 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt @@ -29,6 +29,7 @@ import moe.tlaster.precompose.lifecycle.LifecycleOwner import moe.tlaster.precompose.navigation.route.ComposeRoute import moe.tlaster.precompose.navigation.route.DialogRoute import moe.tlaster.precompose.navigation.route.SceneRoute +import moe.tlaster.precompose.navigation.transition.NavTransition import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackHandler import moe.tlaster.precompose.viewmodel.ViewModelStore @@ -71,11 +72,11 @@ internal class RouteStackManager( get() = currentStack?.currentEntry val canGoBack: Boolean - get() = currentStack?.canGoBack != false || _backStacks.size > 1 + get() = currentStack?.canGoBack == true || _backStacks.size > 1 private val routeParser: RouteParser by lazy { RouteParser().apply { - routeGraph.routes + routeGraph.routes.asSequence() .map { route -> RouteParser.expandOptionalVariables(route.route).let { if (route is SceneRoute) { @@ -87,12 +88,16 @@ internal class RouteStackManager( } } to route } - .flatMap { it.first.map { route -> route to it.second } }.forEach { - insert(it.first, it.second) - } + .flatMap { it.first.map { route -> route to it.second } } + .forEach { insert(it.first, it.second) } } } + private fun findRoute(route: String): RouteMatchResult? { + val routePath = route.substringBefore('?') + return routeParser.find(path = routePath) + } + internal fun getBackStackEntry(route: String): BackStackEntry? { return _backStacks.find { it.hasRoute(route) }?.currentEntry } @@ -103,23 +108,19 @@ internal class RouteStackManager( } } - fun navigate(path: String, options: NavOptions? = null) { + fun navigate(route: String, options: NavOptions? = null) { val vm = viewModel ?: run { - pendingNavigation = path + pendingNavigation = route return } - val query = path.substringAfter('?', "") - val routePath = path.substringBefore('?') - val matchResult = routeParser.find(path = routePath) - checkNotNull(matchResult) { "RouteStackManager: navigate target $path not found" } - require(matchResult.route is ComposeRoute) { "RouteStackManager: navigate target $path is not ComposeRoute" } - if (options != null && matchResult.route is SceneRoute && options.launchSingleTop) { - _backStacks.firstOrNull { it.hasRoute(matchResult.route.route) }?.let { - _backStacks.remove(it) - _backStacks.add(it) - } - } else { - val entry = BackStackEntry( + val query = route.substringAfter('?', "") + + val matchResult = findRoute(route) + checkNotNull(matchResult) { "RouteStackManager: navigate target $route not found" } + require(matchResult.route is ComposeRoute) { "RouteStackManager: navigate target $route is not ComposeRoute" } + + fun newEntry(): BackStackEntry { + return BackStackEntry( id = stackEntryId++, route = matchResult.route, pathMap = matchResult.pathMap, @@ -128,14 +129,32 @@ internal class RouteStackManager( }, viewModel = vm, ) + } + + fun newTask(entry: BackStackEntry, navTransition: NavTransition? = null): RouteStack { + return RouteStack( + id = routeStackId++, + stacks = mutableStateListOf(entry), + navTransition = navTransition, + ) + } + + var launchSingleTopSuccess = false + if (options?.launchSingleTop == true && matchResult.route is SceneRoute) { + _backStacks.firstOrNull { it.hasRoute(matchResult.route.route) } + ?.let { + _backStacks.remove(it) + _backStacks.add(it) + launchSingleTopSuccess = true + } + } + + if (!launchSingleTopSuccess) { + val entry = newEntry() when (matchResult.route) { is SceneRoute -> { _backStacks.add( - RouteStack( - id = routeStackId++, - stacks = mutableStateListOf(entry), - navTransition = matchResult.route.navTransition, - ) + newTask(entry, matchResult.route.navTransition) ) } is DialogRoute -> { @@ -149,7 +168,7 @@ internal class RouteStackManager( if (index != -1 && index != _backStacks.lastIndex) { _backStacks.removeRange( if (options.popUpTo.inclusive) index else index + 1, - _backStacks.lastIndex + _backStacks.lastIndex, ) } else if (options.popUpTo.route.isEmpty()) { _backStacks.removeRange(0, _backStacks.lastIndex) @@ -157,11 +176,29 @@ internal class RouteStackManager( } } - fun goBack(result: Any? = null) { + fun goBack( + route: String? = null, + inclusive: Boolean = false, + result: Any? = null, + ): Boolean { if (!canGoBack) { - backDispatcher?.onBackPress() - return + return false } + + if (!route.isNullOrEmpty()) { + val matchResult = findRoute(route) + if (matchResult != null) { + val index = _backStacks.indexOfLast { it.hasRoute(matchResult.route.route) } + if (index != -1) { + _backStacks.removeRange( + if (inclusive) index else index + 1, + _backStacks.lastIndex, + ) + return true + } + } + } + when { currentStack?.canGoBack == true -> { currentStack?.goBack() @@ -181,6 +218,7 @@ internal class RouteStackManager( }?.let { _suspendResult.remove(it)?.resume(result) } + return true } suspend fun waitingForResult(entry: BackStackEntry): Any? = suspendCoroutine { @@ -206,12 +244,7 @@ internal class RouteStackManager( } override fun handleBackPress(): Boolean { - return if (canGoBack) { - goBack() - true - } else { - false - } + return goBack() } fun navigateInitial(initialRoute: String) { diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt index a21c8e8f..2a7612b7 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt @@ -22,13 +22,8 @@ package moe.tlaster.precompose.navigation.route import androidx.compose.runtime.Composable import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.RouteParser abstract class ComposeRoute( override val route: String, val content: @Composable (BackStackEntry) -> Unit -) : Route { - override val pathKeys by lazy { - RouteParser.pathKeys(pattern = route) - } -} +) : Route diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt index c924989a..dee55e9d 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt @@ -25,5 +25,6 @@ import moe.tlaster.precompose.navigation.BackStackEntry internal class DialogRoute( route: String, + val deepLinks: List, content: @Composable (BackStackEntry) -> Unit ) : ComposeRoute(route, content) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt index d2a2ed7e..30e44008 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt @@ -22,6 +22,4 @@ package moe.tlaster.precompose.navigation.route interface Route { val route: String - @Deprecated("store path key in route node in order to match different links in one route") - val pathKeys: List } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt index 6de56897..3932e7eb 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt @@ -22,7 +22,6 @@ package moe.tlaster.precompose.navigation.route import androidx.compose.runtime.Composable import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.RouteParser import moe.tlaster.precompose.navigation.transition.NavTransition internal class SceneRoute( @@ -30,12 +29,4 @@ internal class SceneRoute( val navTransition: NavTransition?, val deepLinks: List, content: @Composable (BackStackEntry) -> Unit, -) : ComposeRoute(route, content) { - override val pathKeys by lazy { - ( - deepLinks.flatMap { - RouteParser.pathKeys(pattern = it) - } + RouteParser.pathKeys(pattern = route) - ).distinct() - } -} +) : ComposeRoute(route, content) diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt index 4f5db8ff..b956beb7 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt @@ -25,12 +25,13 @@ import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import com.dimension.maskbook.common.CommonSetup +import com.dimension.maskbook.common.ext.navigate +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.route import com.dimension.maskbook.common.route.DeeplinkNavigateArgs import com.dimension.maskbook.common.route.Navigator import com.dimension.maskbook.common.route.RouteNavigateArgs import com.dimension.maskbook.common.ui.widget.RouteHost -import com.dimension.maskbook.common.ui.widget.rememberMaskBottomSheetNavigator import com.dimension.maskbook.entry.BuildConfig import com.dimension.maskbook.entry.EntrySetup import com.dimension.maskbook.entry.repository.EntryRepository @@ -43,18 +44,18 @@ import com.dimension.maskbook.persona.export.PersonaServices import com.dimension.maskbook.persona.route.PersonaRoute import com.dimension.maskbook.setting.SettingSetup import com.dimension.maskbook.wallet.WalletSetup -import com.google.accompanist.navigation.animation.rememberAnimatedNavController -import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi import kotlinx.coroutines.flow.firstOrNull +import moe.tlaster.precompose.navigation.rememberNavController import org.koin.mp.KoinPlatformTools -@OptIn(ExperimentalMaterialNavigationApi::class, ExperimentalAnimationApi::class) +@OptIn(ExperimentalAnimationApi::class) @Composable fun Router( startDestination: String, ) { - val bottomSheetNavigator = rememberMaskBottomSheetNavigator() - val navController = rememberAnimatedNavController(bottomSheetNavigator) + // val bottomSheetNavigator = rememberMaskBottomSheetNavigator() + // val navController = rememberAnimatedNavController(bottomSheetNavigator) + val navController = rememberNavController() LaunchedEffect(Unit) { val initialRoute = getInitialRoute() navController.navigate(initialRoute) { @@ -67,14 +68,14 @@ fun Router( Navigator.navigateEvent.collect { it.getContentIfNotHandled()?.let { it1 -> when (it1) { - is DeeplinkNavigateArgs -> navController.navigate(Uri.parse(it1.url)) + is DeeplinkNavigateArgs -> navController.navigateUri(Uri.parse(it1.url)) is RouteNavigateArgs -> navController.navigate(it1.route) } } } } RouteHost( - bottomSheetNavigator = bottomSheetNavigator, + // bottomSheetNavigator = bottomSheetNavigator, navController = navController, startDestination = startDestination, ) { diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt index 455d44f9..9d0d3558 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -50,20 +50,20 @@ object ExtensionSetup : ModuleSetup { deepLinks = listOf( Deeplinks.WebContent.path ), - arguments = listOf( - navArgument("site") { type = NavType.StringType; nullable = true } - ), - exitTransition = { - scaleOut( - targetScale = 0.9f, - ) - }, - popExitTransition = null, - popEnterTransition = { - scaleIn( - initialScale = 0.9f, - ) - } + // arguments = listOf( + // navArgument("site") { type = NavType.StringType; nullable = true } + // ), + // exitTransition = { + // scaleOut( + // targetScale = 0.9f, + // ) + // }, + // popExitTransition = null, + // popEnterTransition = { + // scaleIn( + // initialScale = 0.9f, + // ) + // } ) { val backStackEntry by navController.currentBackStackEntryAsState() diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/walletconnect/WalletConnectModal.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/walletconnect/WalletConnectModal.kt index f54e2056..2f69f4dd 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/walletconnect/WalletConnectModal.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/walletconnect/WalletConnectModal.kt @@ -74,7 +74,7 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import coil.compose.rememberImagePainter -import com.dimension.maskbook.common.route.AnimatedNavHost +import com.dimension.maskbook.common.ext.navOptions import com.dimension.maskbook.common.route.composable import com.dimension.maskbook.common.ui.barcode.rememberBarcodeBitmap import com.dimension.maskbook.common.ui.notification.StringResNotificationEvent.Companion.show @@ -97,6 +97,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import moe.tlaster.koin.compose.getViewModel import moe.tlaster.precompose.navigation.NavController +import moe.tlaster.precompose.navigation.NavHost import moe.tlaster.precompose.navigation.rememberNavController import org.koin.core.parameter.parametersOf @@ -147,7 +148,7 @@ fun WalletConnectModal(rootNavController: NavController) { textAlign = TextAlign.Center, ) - AnimatedNavHost( + NavHost( navController = navController, initialRoute = "WalletConnectTypeSelect" ) { @@ -197,9 +198,8 @@ fun WalletConnectModal(rootNavController: NavController) { dialog( "WalletConnectUnsupportedNetwork/{network}", - listOf(navArgument("network") { type = NavType.StringType }) ) { - val network = it.arguments?.getString("network") ?: "unKnown" + val network: String = it.path("network") WalletConnectUnsupportedNetwork( onBack = { viewModel.retry() From 84d20147e4e0e2e7c5b922ac4ecea248fd589b47 Mon Sep 17 00:00:00 2001 From: seiko Date: Mon, 11 Apr 2022 19:52:49 +0800 Subject: [PATCH 08/18] fix merge --- .../dimension/maskbook/persona/route/SynchronizationRoute.kt | 4 ++-- .../ui/scenes/register/recovery/local/RecoveryLocalHost.kt | 3 +-- .../register/recovery/local/VerifyPaymentPasswordModal.kt | 2 +- .../persona/viewmodel/VerifyPaymentPasswordViewModel.kt | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt index 5d4f0c99..720de4a3 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/route/SynchronizationRoute.kt @@ -237,7 +237,7 @@ private fun NavController.handleResult(result: Result) { PersonaRoute.Synchronization.Success, navOptions { currentBackStackEntry?.let { backStackEntry -> - popUpTo(backStackEntry.destination.id) { + popUpTo(backStackEntry.route.route) { inclusive = true } } @@ -251,7 +251,7 @@ private fun NavController.handleResult(result: Result) { PersonaRoute.Synchronization.Failed, navOptions { currentBackStackEntry?.let { backStackEntry -> - popUpTo(backStackEntry.destination.id) { + popUpTo(backStackEntry.route.route) { inclusive = true } } diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt index bcf7c0ff..508d045f 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/RecoveryLocalHost.kt @@ -59,7 +59,6 @@ import com.dimension.maskbook.common.ext.getNestedNavigationViewModel import com.dimension.maskbook.common.ext.humanizeFileSize import com.dimension.maskbook.common.ext.humanizeTimestamp import com.dimension.maskbook.common.ext.navigate -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.route.navigationComposeBottomSheet @@ -385,7 +384,7 @@ fun ImportWalletScene( parametersOf(uri, account) } val paymentPassword by viewModel.paymentPassword.collectAsState(initial = null) - val file by viewModel.file.observeAsState(initial = null) + val file by viewModel.file.collectAsState(initial = null) MaskModal( title = { Text(text = "Wallets for recovery") }, ) { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/VerifyPaymentPasswordModal.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/VerifyPaymentPasswordModal.kt index 125ca984..376f1ea8 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/VerifyPaymentPasswordModal.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/register/recovery/local/VerifyPaymentPasswordModal.kt @@ -37,7 +37,7 @@ import com.dimension.maskbook.common.ui.widget.MaskPasswordInputField import com.dimension.maskbook.common.ui.widget.button.PrimaryButton import com.dimension.maskbook.persona.R import com.dimension.maskbook.persona.viewmodel.VerifyPaymentPasswordViewModel -import org.koin.androidx.compose.getViewModel +import moe.tlaster.koin.compose.getViewModel @Composable fun VerifyPaymentPasswordModal( diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/VerifyPaymentPasswordViewModel.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/VerifyPaymentPasswordViewModel.kt index a1c95699..b06b9cf5 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/VerifyPaymentPasswordViewModel.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/viewmodel/VerifyPaymentPasswordViewModel.kt @@ -20,12 +20,12 @@ */ package com.dimension.maskbook.persona.viewmodel -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.SettingServices import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import moe.tlaster.precompose.viewmodel.ViewModel +import moe.tlaster.precompose.viewmodel.viewModelScope class VerifyPaymentPasswordViewModel( settingsRepository: SettingServices, From 41dcf1d827b55f5e935a61845bbaa220cad8c217 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 10:58:24 +0800 Subject: [PATCH 09/18] [wip]migrate precompose navigation navTransition --- .../maskbook/common/ext/NavControllerExt.kt | 4 +- .../maskbook/common/route/RouteBuilder.kt | 28 ++-- .../common/ui/scene/SetupPaymentPassword.kt | 1 - .../maskbook/common/ui/widget/RouteHost.kt | 67 ++++----- .../tlaster/precompose/navigation/NavHost.kt | 102 ++++++++++---- .../precompose/navigation/NavTransition.kt | 57 ++++++++ .../precompose/navigation/Navigator.kt | 2 +- .../precompose/navigation/RouteBuilder.kt | 6 +- .../precompose/navigation/RouteStack.kt | 28 ++-- .../navigation/RouteStackManager.kt | 12 +- .../navigation/route/ComposeRoute.kt | 3 + .../navigation/route/DialogRoute.kt | 11 +- .../precompose/navigation/route/SceneRoute.kt | 13 +- .../transition/AnimatedDialogRoute.kt | 129 ----------------- .../navigation/transition/AnimatedRoute.kt | 132 ------------------ .../navigation/transition/DialogTransition.kt | 41 ------ .../navigation/transition/NavTransition.kt | 77 ---------- .../maskbook/extension/ExtensionSetup.kt | 33 +++-- 18 files changed, 245 insertions(+), 501 deletions(-) create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavTransition.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedDialogRoute.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedRoute.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/DialogTransition.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/NavTransition.kt diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt index f022ffe5..749e8cde 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt @@ -75,7 +75,7 @@ fun NavController.navigateUri(uri: Uri, navOptions: NavOptions) { fun NavController.navigateWithPopSelf(route: String) { navigate(route) { - currentDestination?.route?.let { popRoute -> + currentBackStackEntry?.route?.let { popRoute -> popUpTo(popRoute.route) { inclusive = true } } } @@ -83,7 +83,7 @@ fun NavController.navigateWithPopSelf(route: String) { fun NavController.navigateUriWithPopSelf(uri: Uri) { navigateUri(uri) { - currentDestination?.route?.let { popRoute -> + currentBackStackEntry?.route?.let { popRoute -> popUpTo(popRoute.route) { inclusive = true } } } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt index 62ea05f3..02cf621e 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt @@ -21,20 +21,20 @@ package com.dimension.maskbook.common.route import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.runtime.Composable import moe.tlaster.precompose.navigation.BackStackEntry import moe.tlaster.precompose.navigation.RouteBuilder +import moe.tlaster.precompose.navigation.NavTransition -@OptIn(ExperimentalAnimationApi::class) fun RouteBuilder.composable( route: String, - // arguments: List = emptyList(), deepLinks: List = emptyList(), content: @Composable (BackStackEntry) -> Unit ) { scene( route = route, - // arguments = arguments, deepLinks = deepLinks, content = content, ) @@ -49,23 +49,23 @@ fun RouteBuilder.modalComposable( ) { scene( route = route, - // arguments = arguments, deepLinks = deepLinks, content = content, - // enterTransition = { - // slideInVertically { it } - // }, - // exitTransition = null, - // popEnterTransition = null, - // popExitTransition = { - // slideOutVertically { it } - // }, + navTransition = NavTransition( + enterTransition = { + slideInVertically { it } + }, + exitTransition = NavTransition.NoneExit, + popEnterTransition = NavTransition.NoneEnter, + popExitTransition = { + slideOutVertically { it } + }, + ) ) } fun RouteBuilder.bottomSheet( route: String, - // arguments: List = emptyList(), deepLinks: List = emptyList(), content: @Composable (BackStackEntry) -> Unit ) { @@ -79,13 +79,11 @@ fun RouteBuilder.bottomSheet( fun RouteBuilder.dialog( route: String, - // arguments: List = emptyList(), deepLinks: List = emptyList(), content: @Composable (BackStackEntry) -> Unit ) { dialog( route = route, - // arguments = arguments, deepLinks = deepLinks, content = content, ) diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/SetupPaymentPassword.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/SetupPaymentPassword.kt index d095e010..b83f159d 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/SetupPaymentPassword.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/SetupPaymentPassword.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import com.dimension.maskbook.common.R -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ui.widget.MaskModal import com.dimension.maskbook.common.ui.widget.MaskPasswordInputField import com.dimension.maskbook.common.ui.widget.button.PrimaryButton diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt index 4c223ca7..98d11d99 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt @@ -29,6 +29,7 @@ import moe.tlaster.precompose.navigation.NavController import moe.tlaster.precompose.navigation.NavHost import moe.tlaster.precompose.navigation.RouteBuilder import moe.tlaster.precompose.navigation.rememberNavController +import moe.tlaster.precompose.navigation.NavTransition private const val navHostAnimationDurationMillis = 320 @@ -52,38 +53,40 @@ fun RouteHost( NavHost( navController = navController, initialRoute = startDestination, - // enterTransition = { - // slideInHorizontally( - // initialOffsetX = { it }, - // animationSpec = tween( - // navHostAnimationDurationMillis - // ) - // ) - // }, - // exitTransition = { - // slideOutHorizontally( - // targetOffsetX = { -it }, - // animationSpec = tween( - // navHostAnimationDurationMillis - // ) - // ) - // }, - // popEnterTransition = { - // slideInHorizontally( - // initialOffsetX = { -it }, - // animationSpec = tween( - // navHostAnimationDurationMillis - // ) - // ) - // }, - // popExitTransition = { - // slideOutHorizontally( - // targetOffsetX = { it }, - // animationSpec = tween( - // navHostAnimationDurationMillis - // ) - // ) - // }, + navTransition = NavTransition( + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = tween( + navHostAnimationDurationMillis + ) + ) + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { -it }, + animationSpec = tween( + navHostAnimationDurationMillis + ) + ) + }, + popEnterTransition = { + slideInHorizontally( + initialOffsetX = { -it }, + animationSpec = tween( + navHostAnimationDurationMillis + ) + ) + }, + popExitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = tween( + navHostAnimationDurationMillis + ) + ) + }, + ), builder = builder, ) // } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt index 353a9356..ffc81888 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt @@ -20,16 +20,18 @@ */ package moe.tlaster.precompose.navigation +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.with import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveableStateHolder -import moe.tlaster.precompose.navigation.transition.AnimatedDialogRoute -import moe.tlaster.precompose.navigation.transition.AnimatedRoute -import moe.tlaster.precompose.navigation.transition.DialogTransition -import moe.tlaster.precompose.navigation.transition.NavTransition import moe.tlaster.precompose.ui.LocalBackDispatcherOwner import moe.tlaster.precompose.ui.LocalLifecycleOwner import moe.tlaster.precompose.ui.LocalViewModelStoreOwner @@ -45,15 +47,14 @@ import moe.tlaster.precompose.ui.LocalViewModelStoreOwner * * @param navController the Navigator for this host * @param initialRoute the route for the start destination - * @param navTransition navigation transition for the scenes in this [NavHost] * @param builder the builder used to construct the graph */ +@OptIn(ExperimentalAnimationApi::class) @Composable fun NavHost( navController: NavController, initialRoute: String, navTransition: NavTransition = remember { NavTransition() }, - dialogTransition: DialogTransition = remember { DialogTransition() }, builder: RouteBuilder.() -> Unit, ) { val stateHolder = rememberSaveableStateHolder() @@ -83,45 +84,84 @@ fun NavHost( LaunchedEffect(manager, initialRoute) { manager.navigateInitial(initialRoute) } + val currentStack = manager.currentStack if (currentStack != null) { - AnimatedRoute( - currentStack, - navTransition = navTransition, - manager = manager, - ) { routeStack -> - LaunchedEffect(routeStack) { - routeStack.onActive() + + val finalStackEnter: AnimatedContentScope.() -> EnterTransition = { + if (manager.isPop.value) { + (targetState.navTransition ?: navTransition).popEnterTransition.invoke(this) + } else { + (targetState.navTransition ?: navTransition).enterTransition.invoke(this) } - DisposableEffect(routeStack) { + } + val finalStackExit: AnimatedContentScope.() -> ExitTransition = { + if (manager.isPop.value) { + (targetState.navTransition ?: navTransition).popExitTransition.invoke(this) + } else { + (targetState.navTransition ?: navTransition).exitTransition.invoke(this) + } + } + + val finalEntryEnter: AnimatedContentScope.() -> EnterTransition = { + if (manager.isPop.value) { + (targetState.route.navTransition ?: navTransition).popEnterTransition.invoke(this) + } else { + (targetState.route.navTransition ?: navTransition).enterTransition.invoke(this) + } + } + val finalEntryExit: AnimatedContentScope.() -> ExitTransition = { + if (manager.isPop.value) { + (targetState.route.navTransition ?: navTransition).popExitTransition.invoke(this) + } else { + (targetState.route.navTransition ?: navTransition).exitTransition.invoke(this) + } + } + + AnimatedContent( + currentStack, + transitionSpec = { + finalStackEnter(this) with finalStackExit(this) + }, + ) { stack -> + DisposableEffect(stack) { + stack.onActive() onDispose { - routeStack.onInActive() + stack.onInActive() } } - val currentEntry = routeStack.currentEntry - if (currentEntry != null) { - LaunchedEffect(currentEntry) { - currentEntry.active() - } - DisposableEffect(currentEntry) { + + @Composable + fun initEntry(entry: BackStackEntry) { + DisposableEffect(entry) { + entry.active() onDispose { - currentEntry.inActive() + entry.inActive() } } - } - AnimatedDialogRoute( - stack = routeStack, - dialogTransition = dialogTransition, - ) { - stateHolder.SaveableStateProvider(it.id) { + + stateHolder.SaveableStateProvider(entry.id) { CompositionLocalProvider( - LocalViewModelStoreOwner provides it, - LocalLifecycleOwner provides it, + LocalViewModelStoreOwner provides entry, + LocalLifecycleOwner provides entry, ) { - it.route.content.invoke(it) + entry.route.content(entry) } } } + + initEntry(stack.topEntry) + + if (stack.currentEntry != stack.topEntry) { + AnimatedContent( + stack.currentEntry, + transitionSpec = { + finalEntryEnter(this) with finalEntryExit(this) + }, + ) { entry -> + initEntry(entry) + } + } } } } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavTransition.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavTransition.kt new file mode 100644 index 00000000..fcd7bcb6 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavTransition.kt @@ -0,0 +1,57 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut + +/** + * Create a navigation transition + */ +@OptIn(ExperimentalAnimationApi::class) +data class NavTransition( + /** + * Transition the scene that about to appear for the first time, similar to activity onCreate, factor from 0.0 to 1.0 + */ + val enterTransition: (AnimatedContentScope<*>.() -> EnterTransition) = { fadeIn(animationSpec = tween(700)) }, + /** + * Transition the scene that about to disappear forever, similar to activity onDestroy, factor from 1.0 to 0.0 + */ + val exitTransition: (AnimatedContentScope<*>.() -> ExitTransition) = { fadeOut(animationSpec = tween(700)) }, + /** + * Transition the scene that will be pushed into back stack, similar to activity onPause, factor from 1.0 to 0.0 + */ + val popEnterTransition: (AnimatedContentScope<*>.() -> EnterTransition) = enterTransition, + /** + * Transition the scene that about to show from the back stack, similar to activity onResume, factor from 0.0 to 1.0 + */ + val popExitTransition: (AnimatedContentScope<*>.() -> ExitTransition) = exitTransition, +) { + companion object { + val NoneEnter: (AnimatedContentScope<*>.() -> EnterTransition) = { EnterTransition.None } + val NoneExit: (AnimatedContentScope<*>.() -> ExitTransition) = { ExitTransition.None } + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt index 4650a312..a65c4b5e 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -51,7 +51,7 @@ class NavController { val backQueue: List get() = stackManager?.backStacks?.mapNotNull { it.currentEntry } ?: emptyList() - val currentDestination: BackStackEntry? + val currentBackStackEntry: BackStackEntry? get() = stackManager?.currentEntry fun getBackStackEntry(route: String): BackStackEntry? { diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt index b72ee5c7..620829e1 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.Composable import moe.tlaster.precompose.navigation.route.DialogRoute import moe.tlaster.precompose.navigation.route.Route import moe.tlaster.precompose.navigation.route.SceneRoute -import moe.tlaster.precompose.navigation.transition.NavTransition class RouteBuilder( private val initialRoute: String, @@ -45,11 +44,12 @@ class RouteBuilder( ) { this.route += SceneRoute( route = route, - navTransition = navTransition, deepLinks = deepLinks, + navTransition = navTransition, content = content, ) } + /** * Add the scene [Composable] to the [RouteBuilder], which will show over the scene * @param route route for the destination @@ -58,11 +58,13 @@ class RouteBuilder( fun dialog( route: String, deepLinks: List = emptyList(), + navTransition: NavTransition? = null, content: @Composable (BackStackEntry) -> Unit, ) { this.route += DialogRoute( route = route, deepLinks = deepLinks, + navTransition = navTransition, content = content ) } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt index 70cee6b0..2278b611 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt @@ -23,33 +23,34 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList -import moe.tlaster.precompose.navigation.transition.NavTransition @Stable -internal class RouteStack( +class RouteStack internal constructor( val id: Long, - val stacks: SnapshotStateList = mutableStateListOf(), + val topEntry: BackStackEntry, + val entries: SnapshotStateList = mutableStateListOf(), val navTransition: NavTransition? = null, ) { private var destroyAfterTransition = false - val currentEntry: BackStackEntry? - get() = stacks.lastOrNull() + + val currentEntry: BackStackEntry + get() = entries.lastOrNull() ?: topEntry val canGoBack: Boolean - get() = stacks.size > 1 + get() = entries.isNotEmpty() fun goBack(): BackStackEntry { - return stacks.removeLast().also { + return entries.removeLast().also { it.destroy() } } fun onActive() { - currentEntry?.active() + currentEntry.active() } fun onInActive() { - currentEntry?.inActive() + currentEntry.inActive() if (destroyAfterTransition) { onDestroyed() } @@ -60,13 +61,12 @@ internal class RouteStack( } fun onDestroyed() { - stacks.forEach { - it.destroy() - } - stacks.clear() + topEntry.destroy() + entries.forEach { it.destroy() } + entries.clear() } fun hasRoute(route: String): Boolean { - return stacks.any { it.route.route == route } + return topEntry.route.route == route || entries.any { it.route.route == route } } } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt index f7831e0c..4f1ebcf6 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt @@ -22,6 +22,7 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.SaveableStateHolder import moe.tlaster.precompose.lifecycle.Lifecycle import moe.tlaster.precompose.lifecycle.LifecycleObserver @@ -29,7 +30,6 @@ import moe.tlaster.precompose.lifecycle.LifecycleOwner import moe.tlaster.precompose.navigation.route.ComposeRoute import moe.tlaster.precompose.navigation.route.DialogRoute import moe.tlaster.precompose.navigation.route.SceneRoute -import moe.tlaster.precompose.navigation.transition.NavTransition import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackHandler import moe.tlaster.precompose.viewmodel.ViewModelStore @@ -74,6 +74,8 @@ internal class RouteStackManager( val canGoBack: Boolean get() = currentStack?.canGoBack == true || _backStacks.size > 1 + val isPop = mutableStateOf(false) + private val routeParser: RouteParser by lazy { RouteParser().apply { routeGraph.routes.asSequence() @@ -113,12 +115,13 @@ internal class RouteStackManager( pendingNavigation = route return } - val query = route.substringAfter('?', "") + isPop.value = false val matchResult = findRoute(route) checkNotNull(matchResult) { "RouteStackManager: navigate target $route not found" } require(matchResult.route is ComposeRoute) { "RouteStackManager: navigate target $route is not ComposeRoute" } + val query = route.substringAfter('?', "") fun newEntry(): BackStackEntry { return BackStackEntry( id = stackEntryId++, @@ -134,7 +137,7 @@ internal class RouteStackManager( fun newTask(entry: BackStackEntry, navTransition: NavTransition? = null): RouteStack { return RouteStack( id = routeStackId++, - stacks = mutableStateListOf(entry), + topEntry = entry, navTransition = navTransition, ) } @@ -158,7 +161,7 @@ internal class RouteStackManager( ) } is DialogRoute -> { - currentStack?.stacks?.add(entry) + currentStack?.entries?.add(entry) } } } @@ -184,6 +187,7 @@ internal class RouteStackManager( if (!canGoBack) { return false } + isPop.value = true if (!route.isNullOrEmpty()) { val matchResult = findRoute(route) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt index 2a7612b7..92f22ec6 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt @@ -22,8 +22,11 @@ package moe.tlaster.precompose.navigation.route import androidx.compose.runtime.Composable import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.NavTransition abstract class ComposeRoute( override val route: String, + val deepLinks: List, + val navTransition: NavTransition?, val content: @Composable (BackStackEntry) -> Unit ) : Route diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt index dee55e9d..9f54b1d9 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt @@ -22,9 +22,16 @@ package moe.tlaster.precompose.navigation.route import androidx.compose.runtime.Composable import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.NavTransition internal class DialogRoute( route: String, - val deepLinks: List, + deepLinks: List, + navTransition: NavTransition?, content: @Composable (BackStackEntry) -> Unit -) : ComposeRoute(route, content) +) : ComposeRoute( + route = route, + deepLinks = deepLinks, + navTransition = navTransition, + content = content +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt index 3932e7eb..8ec1e00c 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt @@ -22,11 +22,16 @@ package moe.tlaster.precompose.navigation.route import androidx.compose.runtime.Composable import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.transition.NavTransition +import moe.tlaster.precompose.navigation.NavTransition internal class SceneRoute( route: String, - val navTransition: NavTransition?, - val deepLinks: List, + deepLinks: List, + navTransition: NavTransition?, content: @Composable (BackStackEntry) -> Unit, -) : ComposeRoute(route, content) +) : ComposeRoute( + route = route, + deepLinks = deepLinks, + navTransition = navTransition, + content = content +) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedDialogRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedDialogRoute.kt deleted file mode 100644 index 2612a694..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedDialogRoute.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.transition - -import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.tween -import androidx.compose.animation.core.updateTransition -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.RouteStack - -@Composable -internal fun AnimatedDialogRoute( - stack: RouteStack, - modifier: Modifier = Modifier, - animationSpec: FiniteAnimationSpec = tween(), - dialogTransition: DialogTransition = remember { DialogTransition() }, - content: @Composable (BackStackEntry) -> Unit -) { - - val items = remember { mutableStateListOf>() } - val stacks = stack.stacks - val targetState = remember(stack.stacks.size) { - stack.currentEntry - } - val transitionState = remember { MutableTransitionState(targetState) } - val targetChanged = (targetState != transitionState.targetState) - val previousState = transitionState.targetState - transitionState.targetState = targetState - val transition = updateTransition(transitionState, label = "AnimatedDialogRouteTransition") - - if (targetChanged || items.isEmpty()) { - val indexOfNew = stacks.indexOf(targetState).takeIf { it >= 0 } ?: Int.MAX_VALUE - val indexOfOld = stacks.indexOf(previousState) - .takeIf { - it >= 0 || - // Workaround for navOptions - targetState?.lifecycle?.currentState == Lifecycle.State.Initialized && - previousState?.lifecycle?.currentState == Lifecycle.State.Active - } ?: Int.MAX_VALUE - // Only manipulate the list when the state is changed, or in the first run. - val keys = items.map { - val type = if (indexOfNew >= indexOfOld) AnimateType.Pause else AnimateType.Destroy - it.key to type - }.toMap().run { - toMutableMap().also { - val type = if (indexOfNew >= indexOfOld) { - AnimateType.Create - } else { - AnimateType.Resume - } - if (targetState != null) { - it[targetState] = type - } - } - } - items.clear() - keys.mapTo(items) { (key, value) -> - AnimatedRouteItem(key, value) { - val factor by transition.animateFloat( - transitionSpec = { animationSpec } - ) { if (it == key) 1f else 0f } - Box( - Modifier.graphicsLayer { - when (value) { - AnimateType.Create -> dialogTransition.createTransition.invoke( - this, - factor - ) - AnimateType.Destroy -> dialogTransition.destroyTransition.invoke( - this, - factor - ) - else -> Unit - } - } - ) { - content(key) - } - } - }.sortByDescending { it.animateType } - } else if (transitionState.currentState == transitionState.targetState) { - // Remove all the intermediate items from the list once the animation is finished. - items.removeAll { it.animateType == AnimateType.Destroy } - } - - Box(modifier) { - for (index in items.indices) { - val item = items[index] - key(item.key) { - Box( - modifier = Modifier - .fillMaxSize() - ) { - item.content.invoke() - } - } - } - } -} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedRoute.kt deleted file mode 100644 index f2c19f98..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/AnimatedRoute.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.transition - -import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.tween -import androidx.compose.animation.core.updateTransition -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.navigation.RouteStack -import moe.tlaster.precompose.navigation.RouteStackManager - -@Composable -internal fun AnimatedRoute( - targetState: RouteStack, - modifier: Modifier = Modifier, - manager: RouteStackManager, - animationSpec: FiniteAnimationSpec = tween(), - navTransition: NavTransition = remember { NavTransition() }, - content: @Composable (RouteStack) -> Unit -) { - val items = remember { mutableStateListOf>() } - val transitionState = remember { MutableTransitionState(targetState) } - val targetChanged = (targetState != transitionState.targetState) - val previousState = transitionState.targetState - transitionState.targetState = targetState - val transition = updateTransition(transitionState) - if (targetChanged || items.isEmpty()) { - val indexOfNew = manager.indexOf(targetState).takeIf { it >= 0 } ?: Int.MAX_VALUE - val indexOfOld = manager.indexOf(previousState) - .takeIf { - it >= 0 || - // Workaround for navOptions - targetState.currentEntry?.lifecycle?.currentState == Lifecycle.State.Initialized && - previousState.currentEntry?.lifecycle?.currentState == Lifecycle.State.Active - } ?: Int.MAX_VALUE - val actualNavTransition = run { - if (indexOfNew >= indexOfOld) targetState else previousState - }.navTransition ?: navTransition - // Only manipulate the list when the state is changed, or in the first run. - val keys = items.map { - val type = if (indexOfNew >= indexOfOld) AnimateType.Pause else AnimateType.Destroy - it.key to type - }.toMap().run { - if (!containsKey(targetState)) { - toMutableMap().also { - val type = if (indexOfNew >= indexOfOld) AnimateType.Create else AnimateType.Resume - it[targetState] = type - } - } else { - this - } - } - items.clear() - keys.mapTo(items) { (key, value) -> - AnimatedRouteItem(key, value) { - val factor by transition.animateFloat( - transitionSpec = { animationSpec } - ) { if (it == key) 1f else 0f } - Box( - Modifier.graphicsLayer { - when (value) { - AnimateType.Create -> actualNavTransition.createTransition.invoke(this, factor) - AnimateType.Destroy -> actualNavTransition.destroyTransition.invoke(this, factor) - AnimateType.Pause -> actualNavTransition.pauseTransition.invoke(this, factor) - AnimateType.Resume -> actualNavTransition.resumeTransition.invoke(this, factor) - } - } - ) { - content(key) - } - } - }.sortByDescending { it.animateType } - } else if (transitionState.currentState == transitionState.targetState) { - // Remove all the intermediate items from the list once the animation is finished. - items.removeAll { it.key != transitionState.targetState } - } - - Box(modifier) { - for (index in items.indices) { - val item = items[index] - key(item.key) { - Box( - modifier = Modifier.fillMaxSize() - ) { - item.content() - } - } - } - } -} - -internal enum class AnimateType { - Create, - Destroy, - Pause, - Resume, -} - -internal data class AnimatedRouteItem( - val key: T, - val animateType: AnimateType, - val content: @Composable () -> Unit -) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/DialogTransition.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/DialogTransition.kt deleted file mode 100644 index 2d87fd3c..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/DialogTransition.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.transition - -import androidx.compose.ui.graphics.GraphicsLayerScope - -val fadeCreateTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> - alpha = factor -} -val fadeDestroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> - alpha = factor -} - -data class DialogTransition( - /** - * Transition the scene that about to appear for the first time, similar to activity onCreate, factor from 0.0 to 1.0 - */ - val createTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeCreateTransition, - /** - * Transition the scene that about to disappear forever, similar to activity onDestroy, factor from 1.0 to 0.0 - */ - val destroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeDestroyTransition, -) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/NavTransition.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/NavTransition.kt deleted file mode 100644 index 028214ec..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/transition/NavTransition.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.transition - -import androidx.compose.ui.graphics.GraphicsLayerScope - -private const val enterScaleFactor: Float = 1.1F -private const val exitScaleFactor: Float = 0.9F - -val fadeScaleCreateTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> - (exitScaleFactor + (1F - exitScaleFactor) * factor).let { - scaleX = it - scaleY = it - } - alpha = factor -} -val fadeScaleDestroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> - (exitScaleFactor + (1F - exitScaleFactor) * factor).let { - scaleX = it - scaleY = it - } - alpha = factor -} -val fadeScalePauseTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> - (enterScaleFactor - (enterScaleFactor - 1F) * factor).let { - scaleX = it - scaleY = it - } - alpha = factor -} -val fadeScaleResumeTransition: GraphicsLayerScope.(factor: Float) -> Unit = { factor -> - (enterScaleFactor - (enterScaleFactor - 1F) * factor).let { - scaleX = it - scaleY = it - } - alpha = factor -} - -/** - * Create a navigation transition - */ -data class NavTransition( - /** - * Transition the scene that about to appear for the first time, similar to activity onCreate, factor from 0.0 to 1.0 - */ - val createTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeScaleCreateTransition, - /** - * Transition the scene that about to disappear forever, similar to activity onDestroy, factor from 1.0 to 0.0 - */ - val destroyTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeScaleDestroyTransition, - /** - * Transition the scene that will be pushed into back stack, similar to activity onPause, factor from 1.0 to 0.0 - */ - val pauseTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeScalePauseTransition, - /** - * Transition the scene that about to show from the back stack, similar to activity onResume, factor from 0.0 to 1.0 - */ - val resumeTransition: GraphicsLayerScope.(factor: Float) -> Unit = fadeScaleResumeTransition, -) diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt index 9d0d3558..db1d0dd2 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -21,6 +21,9 @@ package com.dimension.maskbook.extension import android.net.Uri +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.runtime.getValue import com.dimension.maskbook.common.IoScopeName import com.dimension.maskbook.common.ModuleSetup @@ -38,32 +41,34 @@ import com.dimension.maskbook.extension.utils.ContentMessageChannel import moe.tlaster.precompose.navigation.NavController import moe.tlaster.precompose.navigation.RouteBuilder import moe.tlaster.precompose.navigation.currentBackStackEntryAsState +import moe.tlaster.precompose.navigation.NavTransition import org.koin.core.qualifier.named import org.koin.dsl.module import org.koin.mp.KoinPlatformTools object ExtensionSetup : ModuleSetup { + @OptIn(ExperimentalAnimationApi::class) override fun RouteBuilder.route(navController: NavController) { scene( route = ExtensionRoute.WebContent.path, deepLinks = listOf( Deeplinks.WebContent.path ), - // arguments = listOf( - // navArgument("site") { type = NavType.StringType; nullable = true } - // ), - // exitTransition = { - // scaleOut( - // targetScale = 0.9f, - // ) - // }, - // popExitTransition = null, - // popEnterTransition = { - // scaleIn( - // initialScale = 0.9f, - // ) - // } + navTransition = NavTransition( + enterTransition = NavTransition.NoneEnter, + exitTransition = { + scaleOut( + targetScale = 0.9f, + ) + }, + popExitTransition = NavTransition.NoneExit, + popEnterTransition = { + scaleIn( + initialScale = 0.9f, + ) + } + ), ) { val backStackEntry by navController.currentBackStackEntryAsState() From e8ca76e360282bb2ac952c677c31ff231c83f24f Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 11:00:48 +0800 Subject: [PATCH 10/18] cleanup common dependencies --- common/build.gradle.kts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index f7648794..6d77028c 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -59,18 +59,6 @@ kotlin { dependencies { api(projects.localization) - // Koin - // api("io.insert-koin:koin-android:${Versions.koin}") - // api("io.insert-koin:koin-androidx-compose:${Versions.koin}") - - // Lifecycle - // api("androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}") - // api("androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}") - // api("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}") - // api("androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.lifecycle}") - // api("androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.lifecycle}") - // api("androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycle}") - // Coil api("io.coil-kt:coil-compose:${Versions.coil}") api("io.coil-kt:coil-svg:${Versions.coil}") @@ -79,13 +67,10 @@ kotlin { api("com.google.accompanist:accompanist-pager:${Versions.accompanist}") api("com.google.accompanist:accompanist-pager-indicators:${Versions.accompanist}") api("com.google.accompanist:accompanist-swiperefresh:${Versions.accompanist}") - // api("com.google.accompanist:accompanist-navigation-animation:${Versions.accompanist}") - // api("com.google.accompanist:accompanist-navigation-material:${Versions.accompanist}") api("com.google.accompanist:accompanist-permissions:${Versions.accompanist}") api("com.google.accompanist:accompanist-insets:${Versions.accompanist}") // coroutines - // api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.Kotlin.coroutines}") api("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.Kotlin.coroutines}") // Androidx @@ -95,8 +80,6 @@ kotlin { api("androidx.activity:activity-compose:${Versions.Androidx.activity}") api("androidx.fragment:fragment-ktx:${Versions.Androidx.fragment}") api("androidx.datastore:datastore-preferences:${Versions.datastore}") - // api("androidx.navigation:navigation-ui-ktx:${Versions.navigation}") - // api("androidx.navigation:navigation-compose:${Versions.navigation}") implementation("androidx.biometric:biometric-ktx:${Versions.Androidx.biometric}") // sqlite From 8e6670598a8a47017d0cd15078c13494dcd78ccc Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 12:31:45 +0800 Subject: [PATCH 11/18] fix BackHandler --- .../common/ui/scene/VerifyMnemonicWordsScene.kt | 2 +- .../precompose/{navigation => ui}/BackHandler.kt | 16 ++++++++-------- .../tlaster/precompose/ui/BackPressAdapter.kt | 13 +++++++++++++ .../maskbook/extension/ui/WebContentScene.kt | 2 +- 4 files changed, 23 insertions(+), 10 deletions(-) rename common/src/commonMain/kotlin/moe/tlaster/precompose/{navigation => ui}/BackHandler.kt (85%) diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/VerifyMnemonicWordsScene.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/VerifyMnemonicWordsScene.kt index be9c695a..8f82d5fa 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/VerifyMnemonicWordsScene.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/scene/VerifyMnemonicWordsScene.kt @@ -71,7 +71,7 @@ import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.calculateCurrentOffsetForPage import com.google.accompanist.pager.rememberPagerState -import moe.tlaster.precompose.navigation.BackHandler +import moe.tlaster.precompose.ui.BackHandler import kotlin.math.absoluteValue @OptIn(ExperimentalPagerApi::class, ExperimentalMaterialApi::class) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackHandler.kt similarity index 85% rename from common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt rename to common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackHandler.kt index cf4bcf6d..f779552f 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackHandler.kt @@ -18,16 +18,14 @@ * You should have received a copy of the GNU Affero General Public License * along with Mask-Android. If not, see . */ -package moe.tlaster.precompose.navigation +package moe.tlaster.precompose.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState -import moe.tlaster.precompose.ui.BackHandler -import moe.tlaster.precompose.ui.LocalBackDispatcherOwner -import moe.tlaster.precompose.ui.LocalLifecycleOwner @Composable fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) { @@ -35,15 +33,17 @@ fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) { val currentOnBack by rememberUpdatedState(onBack) // Remember in Composition a back callback that calls the `onBack` lambda val backCallback = remember { - object : BackHandler { - override fun handleBackPress(): Boolean { - if (!enabled) return false + object : OnBackPressedCallback(enabled) { + override fun handleOnBackPressed() { currentOnBack.invoke() - return true } } } + SideEffect { + backCallback.isEnabled = enabled + } + val backDispatcher = checkNotNull(LocalBackDispatcherOwner.current) { "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" }.backDispatcher diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt index 295c6c26..fb7c9519 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt @@ -49,6 +49,19 @@ class BackDispatcher { } } +abstract class OnBackPressedCallback(enabled: Boolean) : BackHandler { + + var isEnabled: Boolean = enabled + internal set + + abstract fun handleOnBackPressed() + + override fun handleBackPress(): Boolean { + if (isEnabled) handleOnBackPressed() + return isEnabled + } +} + interface BackHandler { fun handleBackPress(): Boolean } diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ui/WebContentScene.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ui/WebContentScene.kt index 67dc6da1..aa41dac6 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ui/WebContentScene.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ui/WebContentScene.kt @@ -63,7 +63,7 @@ import com.dimension.maskbook.extension.export.model.Site import com.dimension.maskbook.extension.ext.site import com.dimension.maskbook.localization.R import moe.tlaster.koin.compose.get -import moe.tlaster.precompose.navigation.BackHandler +import moe.tlaster.precompose.ui.BackHandler import kotlin.math.roundToInt @Composable From a150d8ebcf06dca7781165dfd4aa5b082e62e300 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 13:55:01 +0800 Subject: [PATCH 12/18] support navigate bottomSheet --- .../maskbook/common/route/RouteBuilder.kt | 12 +- .../ui/widget/MaskBottomSheetNavigator.kt | 82 +++++---- .../maskbook/common/ui/widget/RouteHost.kt | 109 +++++++----- .../tlaster/precompose/navigation/NavHost.kt | 35 +++- .../precompose/navigation/Navigator.kt | 8 +- .../precompose/navigation/RouteBuilder.kt | 17 +- .../precompose/navigation/RouteStack.kt | 8 + .../navigation/RouteStackManager.kt | 33 +++- .../bottomsheet/SheetContentHost.kt | 165 ++++++++++++++++++ .../navigation/route/BottomSheetRoute.kt | 37 ++++ .../com/dimension/maskbook/entry/ui/Router.kt | 6 +- 11 files changed, 403 insertions(+), 109 deletions(-) create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/bottomsheet/SheetContentHost.kt create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/BottomSheetRoute.kt diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt index 02cf621e..ce6c4896 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt @@ -43,7 +43,6 @@ fun RouteBuilder.composable( @OptIn(ExperimentalAnimationApi::class) fun RouteBuilder.modalComposable( route: String, - // arguments: List = emptyList(), deepLinks: List = emptyList(), content: @Composable (BackStackEntry) -> Unit ) { @@ -69,12 +68,11 @@ fun RouteBuilder.bottomSheet( deepLinks: List = emptyList(), content: @Composable (BackStackEntry) -> Unit ) { - // bottomSheet( - // route = route, - // // arguments = arguments, - // deepLinks = deepLinks, - // content = content - // ) + bottomSheet( + route = route, + deepLinks = deepLinks, + content = content + ) } fun RouteBuilder.dialog( diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt index e89f7b64..9ffdbd82 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt @@ -20,46 +20,42 @@ */ package com.dimension.maskbook.common.ui.widget -// import androidx.compose.animation.core.AnimationSpec -// import androidx.compose.material.ExperimentalMaterialApi -// import androidx.compose.material.ModalBottomSheetValue -// import androidx.compose.material.SwipeableDefaults -// import androidx.compose.material.rememberModalBottomSheetState -// import androidx.compose.runtime.Composable -// import androidx.compose.runtime.LaunchedEffect -// import androidx.compose.runtime.remember -// import androidx.compose.runtime.snapshotFlow -// import com.google.accompanist.navigation.material.BottomSheetNavigator -// import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi -// -// @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterialNavigationApi::class,) -// @Composable -// fun rememberMaskBottomSheetNavigator( -// animationSpec: AnimationSpec = SwipeableDefaults.AnimationSpec, -// skipHalfExpanded: Boolean = true, -// ): BottomSheetNavigator { -// val sheetState = rememberModalBottomSheetState( -// ModalBottomSheetValue.Hidden, -// animationSpec -// ) -// -// if (skipHalfExpanded) { -// LaunchedEffect(sheetState) { -// snapshotFlow { sheetState.isAnimationRunning } -// .collect { -// with(sheetState) { -// val isOpening = currentValue == ModalBottomSheetValue.Hidden && targetValue == ModalBottomSheetValue.HalfExpanded -// val isClosing = currentValue == ModalBottomSheetValue.Expanded && targetValue == ModalBottomSheetValue.HalfExpanded -// when { -// isOpening -> animateTo(ModalBottomSheetValue.Expanded) -// isClosing -> animateTo(ModalBottomSheetValue.Hidden) -// } -// } -// } -// } -// } -// -// return remember(sheetState) { -// BottomSheetNavigator(sheetState = sheetState) -// } -// } +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.SwipeableDefaults +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.snapshotFlow + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun rememberMaskBottomSheetNavigator( + animationSpec: AnimationSpec = SwipeableDefaults.AnimationSpec, + skipHalfExpanded: Boolean = true, +): ModalBottomSheetState { + val sheetState = rememberModalBottomSheetState( + ModalBottomSheetValue.Hidden, + animationSpec + ) + + if (skipHalfExpanded) { + LaunchedEffect(sheetState) { + snapshotFlow { sheetState.isAnimationRunning } + .collect { + with(sheetState) { + val isOpening = currentValue == ModalBottomSheetValue.Hidden && targetValue == ModalBottomSheetValue.HalfExpanded + val isClosing = currentValue == ModalBottomSheetValue.Expanded && targetValue == ModalBottomSheetValue.HalfExpanded + when { + isOpening -> animateTo(ModalBottomSheetValue.Expanded) + isClosing -> animateTo(ModalBottomSheetValue.Hidden) + } + } + } + } + } + + return sheetState +} diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt index 98d11d99..b201d419 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt @@ -24,70 +24,85 @@ import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.tween import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.MaterialTheme +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.dimension.maskbook.common.ui.theme.modalScrimColor import moe.tlaster.precompose.navigation.NavController import moe.tlaster.precompose.navigation.NavHost +import moe.tlaster.precompose.navigation.NavTransition import moe.tlaster.precompose.navigation.RouteBuilder import moe.tlaster.precompose.navigation.rememberNavController -import moe.tlaster.precompose.navigation.NavTransition private const val navHostAnimationDurationMillis = 320 @ExperimentalAnimationApi +@ExperimentalMaterialApi @Composable fun RouteHost( - // bottomSheetNavigator: BottomSheetNavigator = rememberMaskBottomSheetNavigator(), navController: NavController = rememberNavController(), + bottomSheetState: ModalBottomSheetState = rememberMaskBottomSheetNavigator(), startDestination: String, builder: RouteBuilder.() -> Unit ) { - // ModalBottomSheetLayout( - // // bottomSheetNavigator, - // sheetBackgroundColor = MaterialTheme.colors.background, - // sheetShape = MaterialTheme.shapes.large.copy( - // bottomStart = CornerSize(0.dp), - // bottomEnd = CornerSize(0.dp), - // ), - // scrimColor = MaterialTheme.colors.modalScrimColor, - // ) { - NavHost( - navController = navController, - initialRoute = startDestination, - navTransition = NavTransition( - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = tween( - navHostAnimationDurationMillis + ModalBottomSheetLayout( + sheetContent = navController.sheetContent ?: { + Box(Modifier.height(1.dp)) + }, + sheetState = bottomSheetState, + sheetBackgroundColor = MaterialTheme.colors.background, + sheetShape = MaterialTheme.shapes.large.copy( + bottomStart = CornerSize(0.dp), + bottomEnd = CornerSize(0.dp), + ), + scrimColor = MaterialTheme.colors.modalScrimColor, + ) { + NavHost( + navController = navController, + bottomSheetState = bottomSheetState, + initialRoute = startDestination, + navTransition = NavTransition( + enterTransition = { + slideInHorizontally( + initialOffsetX = { it }, + animationSpec = tween( + navHostAnimationDurationMillis + ) ) - ) - }, - exitTransition = { - slideOutHorizontally( - targetOffsetX = { -it }, - animationSpec = tween( - navHostAnimationDurationMillis + }, + exitTransition = { + slideOutHorizontally( + targetOffsetX = { -it }, + animationSpec = tween( + navHostAnimationDurationMillis + ) ) - ) - }, - popEnterTransition = { - slideInHorizontally( - initialOffsetX = { -it }, - animationSpec = tween( - navHostAnimationDurationMillis + }, + popEnterTransition = { + slideInHorizontally( + initialOffsetX = { -it }, + animationSpec = tween( + navHostAnimationDurationMillis + ) ) - ) - }, - popExitTransition = { - slideOutHorizontally( - targetOffsetX = { it }, - animationSpec = tween( - navHostAnimationDurationMillis + }, + popExitTransition = { + slideOutHorizontally( + targetOffsetX = { it }, + animationSpec = tween( + navHostAnimationDurationMillis + ) ) - ) - }, - ), - builder = builder, - ) - // } + }, + ), + builder = builder, + ) + } } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt index ffc81888..6b9530ab 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt @@ -26,6 +26,11 @@ import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.with +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.SwipeableDefaults +import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect @@ -49,18 +54,39 @@ import moe.tlaster.precompose.ui.LocalViewModelStoreOwner * @param initialRoute the route for the start destination * @param builder the builder used to construct the graph */ -@OptIn(ExperimentalAnimationApi::class) + +@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class) +@Composable +fun NavHost( + navController: NavController, + initialRoute: String, + navTransition: NavTransition = remember { NavTransition() }, + builder: RouteBuilder.() -> Unit, +) { + NavHost( + navController = navController, + initialRoute = initialRoute, + navTransition = navTransition, + bottomSheetState = rememberModalBottomSheetState( + ModalBottomSheetValue.Hidden, + SwipeableDefaults.AnimationSpec + ), + builder = builder, + ) +} +@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class) @Composable fun NavHost( navController: NavController, initialRoute: String, + bottomSheetState: ModalBottomSheetState, navTransition: NavTransition = remember { NavTransition() }, builder: RouteBuilder.() -> Unit, ) { val stateHolder = rememberSaveableStateHolder() val manager = remember { val graph = RouteBuilder(initialRoute = initialRoute).apply(builder).build() - RouteStackManager(stateHolder, graph).apply { + RouteStackManager(graph, stateHolder, bottomSheetState).apply { navController.stackManager = this } } @@ -152,9 +178,10 @@ fun NavHost( initEntry(stack.topEntry) - if (stack.currentEntry != stack.topEntry) { + val currentEntry = stack.currentDialogEntry + if (currentEntry != null) { AnimatedContent( - stack.currentEntry, + currentEntry, transitionSpec = { finalEntryEnter(this) with finalEntryExit(this) }, diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt index a65c4b5e..928effb4 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -20,6 +20,8 @@ */ package moe.tlaster.precompose.navigation +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State @@ -37,6 +39,7 @@ fun rememberNavController(): NavController { return remember { NavController() } } +@OptIn(ExperimentalMaterialApi::class) class NavController { // FIXME: 2021/4/1 Temp workaround for deeplink private var pendingNavigation: String? = null @@ -49,11 +52,14 @@ class NavController { } val backQueue: List - get() = stackManager?.backStacks?.mapNotNull { it.currentEntry } ?: emptyList() + get() = stackManager?.backStacks?.map { it.currentEntry } ?: emptyList() val currentBackStackEntry: BackStackEntry? get() = stackManager?.currentEntry + val sheetContent: (@Composable ColumnScope.() -> Unit)? + get() = stackManager?.sheetContent + fun getBackStackEntry(route: String): BackStackEntry? { return stackManager?.getBackStackEntry(route) } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt index 620829e1..9a259a41 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt @@ -21,6 +21,7 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.route.BottomSheetRoute import moe.tlaster.precompose.navigation.route.DialogRoute import moe.tlaster.precompose.navigation.route.Route import moe.tlaster.precompose.navigation.route.SceneRoute @@ -65,7 +66,21 @@ class RouteBuilder( route = route, deepLinks = deepLinks, navTransition = navTransition, - content = content + content = content, + ) + } + + fun bottomSheet( + route: String, + deepLinks: List = emptyList(), + navTransition: NavTransition? = null, + content: @Composable (BackStackEntry) -> Unit, + ) { + this.route += BottomSheetRoute( + route = route, + deepLinks = deepLinks, + navTransition = navTransition, + content = content, ) } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt index 2278b611..2992096c 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt @@ -23,6 +23,8 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList +import moe.tlaster.precompose.navigation.route.BottomSheetRoute +import moe.tlaster.precompose.navigation.route.DialogRoute @Stable class RouteStack internal constructor( @@ -39,6 +41,12 @@ class RouteStack internal constructor( val canGoBack: Boolean get() = entries.isNotEmpty() + internal val currentDialogEntry: BackStackEntry? + get() = entries.lastOrNull { it.route is DialogRoute } + + internal val currentBottomSheetEntry: BackStackEntry? + get() = entries.lastOrNull { it.route is BottomSheetRoute } + fun goBack(): BackStackEntry { return entries.removeLast().also { it.destroy() diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt index 4f1ebcf6..4fcaa669 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt @@ -20,13 +20,20 @@ */ package moe.tlaster.precompose.navigation +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.SaveableStateHolder +import androidx.compose.runtime.saveable.rememberSaveableStateHolder import moe.tlaster.precompose.lifecycle.Lifecycle import moe.tlaster.precompose.lifecycle.LifecycleObserver import moe.tlaster.precompose.lifecycle.LifecycleOwner +import moe.tlaster.precompose.navigation.bottomsheet.SheetContentHost +import moe.tlaster.precompose.navigation.route.BottomSheetRoute import moe.tlaster.precompose.navigation.route.ComposeRoute import moe.tlaster.precompose.navigation.route.DialogRoute import moe.tlaster.precompose.navigation.route.SceneRoute @@ -37,10 +44,12 @@ import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +@OptIn(ExperimentalMaterialApi::class) @Stable internal class RouteStackManager( - private val stateHolder: SaveableStateHolder, private val routeGraph: RouteGraph, + private val stateHolder: SaveableStateHolder, + private val sheetState: ModalBottomSheetState, ) : LifecycleObserver, BackHandler { // FIXME: 2021/4/1 Temp workaround for deeplink private var pendingNavigation: String? = null @@ -76,6 +85,25 @@ internal class RouteStackManager( val isPop = mutableStateOf(false) + internal val sheetContent: @Composable ColumnScope.() -> Unit = @Composable { + // val columnScope = this + val saveableStateHolder = rememberSaveableStateHolder() + + val currentEntry = currentStack?.currentBottomSheetEntry + SheetContentHost( + backStackEntry = currentEntry, + sheetState = sheetState, + stateHolder = saveableStateHolder, + onSheetShown = { + + }, + onSheetDismissed = { + goBack() + } + ) + + } + private val routeParser: RouteParser by lazy { RouteParser().apply { routeGraph.routes.asSequence() @@ -160,7 +188,8 @@ internal class RouteStackManager( newTask(entry, matchResult.route.navTransition) ) } - is DialogRoute -> { + is DialogRoute, + is BottomSheetRoute -> { currentStack?.entries?.add(entry) } } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/bottomsheet/SheetContentHost.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/bottomsheet/SheetContentHost.kt new file mode 100644 index 00000000..12f74de1 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/bottomsheet/SheetContentHost.kt @@ -0,0 +1,165 @@ +package moe.tlaster.precompose.navigation.bottomsheet + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.saveable.SaveableStateHolder +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.withFrameNanos +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.ui.LocalLifecycleOwner +import moe.tlaster.precompose.ui.LocalViewModelStoreOwner + +@OptIn(ExperimentalMaterialApi::class) +@Composable +internal fun SheetContentHost( + backStackEntry: BackStackEntry?, + sheetState: ModalBottomSheetState, + stateHolder: SaveableStateHolder, + onSheetShown: (entry: BackStackEntry) -> Unit, + onSheetDismissed: (entry: BackStackEntry) -> Unit, +) { + val scope = rememberCoroutineScope() + if (backStackEntry != null) { + val currentOnSheetShown by rememberUpdatedState(onSheetShown) + val currentOnSheetDismissed by rememberUpdatedState(onSheetDismissed) + var hideCalled by remember(backStackEntry) { mutableStateOf(false) } + LaunchedEffect(backStackEntry, hideCalled) { + val sheetVisibility = snapshotFlow { sheetState.isVisible } + sheetVisibility + // We are only interested in changes in the sheet's visibility + .distinctUntilChanged() + // distinctUntilChanged emits the initial value which we don't need + .drop(1) + // We want to know when the sheet was visible but is not anymore + .filter { isVisible -> !isVisible } + // Finally, pop the back stack when the sheet has been hidden + .collect { if (!hideCalled) currentOnSheetDismissed(backStackEntry) } + } + + // We use this signal to know when its (almost) safe to `show` the bottom sheet + // It will be set after the sheet's content has been `onGloballyPositioned` + val contentPositionedSignal = remember(backStackEntry) { + CompletableDeferred() + } + + // Whenever the composable associated with the backStackEntry enters the composition, we + // want to show the sheet, and hide it when this composable leaves the composition + DisposableEffect(backStackEntry) { + scope.launch { + contentPositionedSignal.await() + try { + // If we don't wait for a few frames before calling `show`, we will be too early + // and the sheet content won't have been laid out yet (even with our content + // positioned signal). If a sheet is tall enough to have a HALF_EXPANDED state, + // we might be here before the SwipeableState's anchors have been properly + // calculated, resulting in the sheet animating to the EXPANDED state when + // calling `show`. As a workaround, we wait for a magic number of frames. + // https://issuetracker.google.com/issues/200980998 + repeat(AWAIT_FRAMES_BEFORE_SHOW) { awaitFrame() } + sheetState.show() + } catch (sheetShowCancelled: CancellationException) { + // There is a race condition in ModalBottomSheetLayout that happens when the + // sheet content changes due to the anchors being re-calculated. This causes an + // animation to run for a short time, cancelling any currently running animation + // such as the one triggered by our `show` call. + // The sheet will still snap to the EXPANDED or HALF_EXPANDED state. + // In that case we want to wait until the sheet is visible. For safety, we only + // wait for 800 milliseconds - if the sheet is not visible until then, something + // has gone horribly wrong. + // https://issuetracker.google.com/issues/200980998 + withTimeout(800) { + while (!sheetState.isVisible) { + awaitFrame() + } + } + } finally { + // If, for some reason, the sheet is in a state where the animation is still + // running, there is a chance that it is already targeting the EXPANDED or + // HALF_EXPANDED state and will snap to that. In that case we can be fairly + // certain that the sheet will actually end up in that state. + if (sheetState.isVisible || sheetState.willBeVisible) { + currentOnSheetShown(backStackEntry) + } + } + } + onDispose { + scope.launch { + hideCalled = true + try { + sheetState.internalHide() + } finally { + hideCalled = false + } + } + } + } + + stateHolder.SaveableStateProvider(backStackEntry.id) { + CompositionLocalProvider( + LocalViewModelStoreOwner provides backStackEntry, + LocalLifecycleOwner provides backStackEntry, + ) { + Box(Modifier.onGloballyPositioned { contentPositionedSignal.complete(Unit) }) { + backStackEntry.route.content(backStackEntry) + } + } + } + + } else { + EmptySheet() + } +} + +@Composable +private fun EmptySheet() { + // The swipeable modifier has a bug where it doesn't support having something with + // height = 0 + // b/178529942 + // If there are no destinations on the back stack, we need to add something to work + // around this + Box(Modifier.height(1.dp)) +} + +private suspend fun awaitFrame() = withFrameNanos(onFrame = {}) + +/** + * This magic number has been chosen through painful experiments. + * - Waiting for 1 frame still results in the sheet fully expanding, which we don't want + * - Waiting for 2 frames results in the `show` call getting cancelled + * - Waiting for 3+ frames results in the sheet expanding to the correct state. Success! + * We wait for a few frames more just to be sure. + */ +private const val AWAIT_FRAMES_BEFORE_SHOW = 3 + +// We have the same issue when we are hiding the sheet, but snapTo works better +@OptIn(ExperimentalMaterialApi::class) +private suspend fun ModalBottomSheetState.internalHide() { + snapTo(ModalBottomSheetValue.Hidden) +} + +@OptIn(ExperimentalMaterialApi::class) +private val ModalBottomSheetState.willBeVisible: Boolean + get() = targetValue == ModalBottomSheetValue.HalfExpanded || targetValue == ModalBottomSheetValue.Expanded diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/BottomSheetRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/BottomSheetRoute.kt new file mode 100644 index 00000000..2c2b906d --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/BottomSheetRoute.kt @@ -0,0 +1,37 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation.route + +import androidx.compose.runtime.Composable +import moe.tlaster.precompose.navigation.BackStackEntry +import moe.tlaster.precompose.navigation.NavTransition + +internal class BottomSheetRoute( + route: String, + deepLinks: List, + navTransition: NavTransition?, + content: @Composable (BackStackEntry) -> Unit +) : ComposeRoute( + route = route, + deepLinks = deepLinks, + navTransition = navTransition, + content = content +) diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt index b956beb7..460e2135 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt @@ -22,6 +22,7 @@ package com.dimension.maskbook.entry.ui import android.net.Uri import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import com.dimension.maskbook.common.CommonSetup @@ -48,13 +49,11 @@ import kotlinx.coroutines.flow.firstOrNull import moe.tlaster.precompose.navigation.rememberNavController import org.koin.mp.KoinPlatformTools -@OptIn(ExperimentalAnimationApi::class) +@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class) @Composable fun Router( startDestination: String, ) { - // val bottomSheetNavigator = rememberMaskBottomSheetNavigator() - // val navController = rememberAnimatedNavController(bottomSheetNavigator) val navController = rememberNavController() LaunchedEffect(Unit) { val initialRoute = getInitialRoute() @@ -75,7 +74,6 @@ fun Router( } } RouteHost( - // bottomSheetNavigator = bottomSheetNavigator, navController = navController, startDestination = startDestination, ) { From 1ae6a7752568e5ae225d12d8f729ad837a6a9aa7 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 16:49:57 +0800 Subject: [PATCH 13/18] catch wallet backup if no mnemonic --- .../maskbook/wallet/repository/WalletRepository.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletRepository.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletRepository.kt index 51652e97..b283bce5 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletRepository.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletRepository.kt @@ -842,7 +842,11 @@ internal class WalletRepository( override suspend fun createWalletBackup(): List { return database.walletDao().getAll().map { val privateKey = WalletKey.load(it.storedKey.data).firstOrNull() - val mnemonic = privateKey?.exportMnemonic("") + val mnemonic = try { + privateKey?.exportMnemonic("") + } catch (ignored: Exception) { + null + } // TODO: support other coin types val privateKeyHex = privateKey?.exportPrivateKey(CoinType.Ethereum, "") BackupWalletData( From 6eeb76039281b0167d2b879844ec7164db5873af Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 20:18:21 +0800 Subject: [PATCH 14/18] move navigation route --- .../maskbook/common/route/RouteBuilder.kt | 2 +- .../precompose/navigation/BackStackEntry.kt | 1 - .../tlaster/precompose/navigation/Route.kt | 63 +++++++++++++++++++ .../precompose/navigation/RouteBuilder.kt | 4 -- .../precompose/navigation/RouteGraph.kt | 2 - .../precompose/navigation/RouteMatch.kt | 1 - .../precompose/navigation/RouteMatchResult.kt | 2 - .../precompose/navigation/RouteParser.kt | 1 - .../precompose/navigation/RouteStack.kt | 2 - .../navigation/RouteStackManager.kt | 4 -- .../navigation/route/BottomSheetRoute.kt | 37 ----------- .../navigation/route/ComposeRoute.kt | 32 ---------- .../navigation/route/DialogRoute.kt | 37 ----------- .../precompose/navigation/route/Route.kt | 25 -------- .../precompose/navigation/route/SceneRoute.kt | 37 ----------- 15 files changed, 64 insertions(+), 186 deletions(-) create mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Route.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/BottomSheetRoute.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt delete mode 100644 common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt index ce6c4896..be1938e3 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt @@ -90,7 +90,7 @@ fun RouteBuilder.dialog( fun RouteBuilder.navigation( route: String, startDestination: String, - content: @Composable RouteBuilder.(BackStackEntry) -> Unit + content: RouteBuilder.() -> Unit ) { // TODO } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt index ce509009..bbf6479c 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt @@ -23,7 +23,6 @@ package moe.tlaster.precompose.navigation import moe.tlaster.precompose.lifecycle.Lifecycle import moe.tlaster.precompose.lifecycle.LifecycleOwner import moe.tlaster.precompose.lifecycle.LifecycleRegistry -import moe.tlaster.precompose.navigation.route.ComposeRoute import moe.tlaster.precompose.viewmodel.ViewModelStore import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Route.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Route.kt new file mode 100644 index 00000000..00f2dcef --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Route.kt @@ -0,0 +1,63 @@ +/* + * Mask-Android + * + * Copyright (C) 2022 DimensionDev and Contributors + * + * This file is part of Mask-Android. + * + * Mask-Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Mask-Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Mask-Android. If not, see . + */ +package moe.tlaster.precompose.navigation + +import androidx.compose.runtime.Composable + +interface Route { + val route: String +} + +interface ComposeRoute : Route { + val content: @Composable (BackStackEntry) -> Unit + val deepLinks: List get() = emptyList() + val navTransition: NavTransition? get() = null +} + +internal class NavigationRoute( + override val route: String, + val graph: RouteGraph, + val initialRoute: ComposeRoute, +) : ComposeRoute { + override val content: (BackStackEntry) -> Unit + get() = {} +} + +internal class SceneRoute( + override val route: String, + override val deepLinks: List = emptyList(), + override val navTransition: NavTransition? = null, + override val content: @Composable (BackStackEntry) -> Unit, +) : ComposeRoute + +internal class DialogRoute( + override val route: String, + override val deepLinks: List = emptyList(), + override val navTransition: NavTransition? = null, + override val content: @Composable (BackStackEntry) -> Unit +) : ComposeRoute + +internal class BottomSheetRoute( + override val route: String, + override val deepLinks: List = emptyList(), + override val navTransition: NavTransition? = null, + override val content: @Composable (BackStackEntry) -> Unit +) : ComposeRoute diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt index 9a259a41..a42b89f4 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt @@ -21,10 +21,6 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Composable -import moe.tlaster.precompose.navigation.route.BottomSheetRoute -import moe.tlaster.precompose.navigation.route.DialogRoute -import moe.tlaster.precompose.navigation.route.Route -import moe.tlaster.precompose.navigation.route.SceneRoute class RouteBuilder( private val initialRoute: String, diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt index 35011436..94603f91 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt @@ -20,8 +20,6 @@ */ package moe.tlaster.precompose.navigation -import moe.tlaster.precompose.navigation.route.Route - internal data class RouteGraph( val initialRoute: String, val routes: List, diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt index f60a2a8e..9372a45a 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt @@ -20,7 +20,6 @@ */ package moe.tlaster.precompose.navigation -import moe.tlaster.precompose.navigation.route.ComposeRoute import kotlin.math.min internal class RouteMatch { diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt index 2a37979b..c5ea0e7f 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt @@ -20,8 +20,6 @@ */ package moe.tlaster.precompose.navigation -import moe.tlaster.precompose.navigation.route.Route - internal data class RouteMatchResult( val route: Route, val pathMap: Map = emptyMap(), diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt index bcdc69e9..56bdee03 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt @@ -20,7 +20,6 @@ */ package moe.tlaster.precompose.navigation -import moe.tlaster.precompose.navigation.route.Route import kotlin.math.min internal class RouteParser { diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt index 2992096c..d89bdbaa 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt @@ -23,8 +23,6 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList -import moe.tlaster.precompose.navigation.route.BottomSheetRoute -import moe.tlaster.precompose.navigation.route.DialogRoute @Stable class RouteStack internal constructor( diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt index 4fcaa669..778e4e20 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt @@ -33,10 +33,6 @@ import moe.tlaster.precompose.lifecycle.Lifecycle import moe.tlaster.precompose.lifecycle.LifecycleObserver import moe.tlaster.precompose.lifecycle.LifecycleOwner import moe.tlaster.precompose.navigation.bottomsheet.SheetContentHost -import moe.tlaster.precompose.navigation.route.BottomSheetRoute -import moe.tlaster.precompose.navigation.route.ComposeRoute -import moe.tlaster.precompose.navigation.route.DialogRoute -import moe.tlaster.precompose.navigation.route.SceneRoute import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackHandler import moe.tlaster.precompose.viewmodel.ViewModelStore diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/BottomSheetRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/BottomSheetRoute.kt deleted file mode 100644 index 2c2b906d..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/BottomSheetRoute.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.route - -import androidx.compose.runtime.Composable -import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.NavTransition - -internal class BottomSheetRoute( - route: String, - deepLinks: List, - navTransition: NavTransition?, - content: @Composable (BackStackEntry) -> Unit -) : ComposeRoute( - route = route, - deepLinks = deepLinks, - navTransition = navTransition, - content = content -) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt deleted file mode 100644 index 92f22ec6..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/ComposeRoute.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.route - -import androidx.compose.runtime.Composable -import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.NavTransition - -abstract class ComposeRoute( - override val route: String, - val deepLinks: List, - val navTransition: NavTransition?, - val content: @Composable (BackStackEntry) -> Unit -) : Route diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt deleted file mode 100644 index 9f54b1d9..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/DialogRoute.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.route - -import androidx.compose.runtime.Composable -import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.NavTransition - -internal class DialogRoute( - route: String, - deepLinks: List, - navTransition: NavTransition?, - content: @Composable (BackStackEntry) -> Unit -) : ComposeRoute( - route = route, - deepLinks = deepLinks, - navTransition = navTransition, - content = content -) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt deleted file mode 100644 index 30e44008..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/Route.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.route - -interface Route { - val route: String -} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt deleted file mode 100644 index 8ec1e00c..00000000 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/route/SceneRoute.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package moe.tlaster.precompose.navigation.route - -import androidx.compose.runtime.Composable -import moe.tlaster.precompose.navigation.BackStackEntry -import moe.tlaster.precompose.navigation.NavTransition - -internal class SceneRoute( - route: String, - deepLinks: List, - navTransition: NavTransition?, - content: @Composable (BackStackEntry) -> Unit, -) : ComposeRoute( - route = route, - deepLinks = deepLinks, - navTransition = navTransition, - content = content -) From 458c81c96f9fbd1f282493ba842afc8d14c7d4d2 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 20:34:06 +0800 Subject: [PATCH 15/18] [wip]support navigation route --- .../dimension/maskbook/common/ext/KoinExt.kt | 6 +- .../maskbook/common/route/RouteBuilder.kt | 8 +- .../precompose/navigation/BackStackEntry.kt | 2 +- .../precompose/navigation/Navigator.kt | 4 +- .../precompose/navigation/RouteBuilder.kt | 19 ++++ .../precompose/navigation/RouteGraph.kt | 33 +++++- .../precompose/navigation/RouteStack.kt | 26 ++++- .../navigation/RouteStackManager.kt | 104 +++++++++++------- 8 files changed, 150 insertions(+), 52 deletions(-) diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/KoinExt.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/KoinExt.kt index e03ac3a2..eede1bbf 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/KoinExt.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/KoinExt.kt @@ -48,8 +48,8 @@ inline fun NavController.getNestedNavigationViewModel( noinline parameters: ParametersDefinition? = null, ): T { return remember(route, qualifier, scope, parameters) { - val backStackEntry = getBackStackEntry(route) - ?: throw RuntimeException("not find backStackEntry with route: $route") - backStackEntry.getViewModel(qualifier, scope, parameters) + val routeStack = getRouteStack(route) + ?: throw RuntimeException("not find routeStack with route: $route") + routeStack.getViewModel(qualifier, scope, parameters) } } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt index be1938e3..224b4aed 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/RouteBuilder.kt @@ -90,7 +90,11 @@ fun RouteBuilder.dialog( fun RouteBuilder.navigation( route: String, startDestination: String, - content: RouteBuilder.() -> Unit + builder: RouteBuilder.() -> Unit ) { - // TODO + navigation( + route = route, + initialRoute = startDestination, + builder = builder, + ) } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt index bbf6479c..6aba4286 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt @@ -61,7 +61,7 @@ class BackStackEntry internal constructor( destroyAfterTransition = true } else { lifecycleRegistry.currentState = Lifecycle.State.Destroyed - viewModelStore.clear() + viewModel.clear(id) } } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt index 928effb4..9af7e21a 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -60,8 +60,8 @@ class NavController { val sheetContent: (@Composable ColumnScope.() -> Unit)? get() = stackManager?.sheetContent - fun getBackStackEntry(route: String): BackStackEntry? { - return stackManager?.getBackStackEntry(route) + fun getRouteStack(route: String): RouteStack? { + return stackManager?.getRouteStack(route) } /** diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt index a42b89f4..e3dbfcb2 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt @@ -80,6 +80,25 @@ class RouteBuilder( ) } + fun navigation( + route: String, + initialRoute: String, + builder: RouteBuilder.() -> Unit + ) { + val graph = RouteBuilder(initialRoute).apply(builder).build() + val targetRoute = requireNotNull(graph.routes.find { it.route == initialRoute }) { + "not find initialRoute:$initialRoute in navigation" + } + require(targetRoute is ComposeRoute) { + "navigation initialRoute:${initialRoute} is not ComposeRoute" + } + this.route += NavigationRoute( + route = route, + graph = graph, + initialRoute = targetRoute, + ) + } + fun addRoute(route: Route) { this.route += route } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt index 94603f91..30873580 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.kt @@ -23,4 +23,35 @@ package moe.tlaster.precompose.navigation internal data class RouteGraph( val initialRoute: String, val routes: List, -) +) { + private val routeParser: RouteParser by lazy { + fun matchRoute(route: Route): List { + val matches = RouteParser.expandOptionalVariables(route.route) + if (route !is ComposeRoute) return matches + + return if (route is NavigationRoute) { + matches + route.graph.routes.flatMapTo(mutableListOf()) { childRoute -> + matchRoute(childRoute) + } + } else if (route.deepLinks.isNotEmpty()) { + matches + route.deepLinks.flatMap { + RouteParser.expandOptionalVariables(it) + } + } else { + matches + } + } + + RouteParser().apply { + routes.asSequence() + .map { route -> matchRoute(route) to route } + .flatMap { it.first.map { route -> route to it.second } } + .forEach { insert(it.first, it.second) } + } + } + + fun findRoute(route: String): RouteMatchResult? { + val routePath = route.substringBefore('?') + return routeParser.find(path = routePath) + } +} diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt index d89bdbaa..142ef51f 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.kt @@ -23,6 +23,11 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList +import moe.tlaster.precompose.lifecycle.Lifecycle +import moe.tlaster.precompose.lifecycle.LifecycleOwner +import moe.tlaster.precompose.lifecycle.LifecycleRegistry +import moe.tlaster.precompose.viewmodel.ViewModelStore +import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner @Stable class RouteStack internal constructor( @@ -30,9 +35,22 @@ class RouteStack internal constructor( val topEntry: BackStackEntry, val entries: SnapshotStateList = mutableStateListOf(), val navTransition: NavTransition? = null, -) { + internal val stackRoute: String? = null, + internal val viewModel: NavControllerViewModel, +) : ViewModelStoreOwner, LifecycleOwner { + private var destroyAfterTransition = false + override val viewModelStore: ViewModelStore + get() = viewModel.get(id = id) + + private val lifecycleRegistry: LifecycleRegistry by lazy { + LifecycleRegistry() + } + + override val lifecycle: Lifecycle + get() = lifecycleRegistry + val currentEntry: BackStackEntry get() = entries.lastOrNull() ?: topEntry @@ -52,10 +70,12 @@ class RouteStack internal constructor( } fun onActive() { + lifecycleRegistry.currentState = Lifecycle.State.Active currentEntry.active() } fun onInActive() { + lifecycleRegistry.currentState = Lifecycle.State.InActive currentEntry.inActive() if (destroyAfterTransition) { onDestroyed() @@ -67,12 +87,14 @@ class RouteStack internal constructor( } fun onDestroyed() { - topEntry.destroy() + lifecycleRegistry.currentState = Lifecycle.State.Destroyed entries.forEach { it.destroy() } entries.clear() + viewModel.clear(id) } fun hasRoute(route: String): Boolean { return topEntry.route.route == route || entries.any { it.route.route == route } + || stackRoute == route } } diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt index 778e4e20..e911c5bb 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt @@ -49,22 +49,28 @@ internal class RouteStackManager( ) : LifecycleObserver, BackHandler { // FIXME: 2021/4/1 Temp workaround for deeplink private var pendingNavigation: String? = null + private val _suspendResult = linkedMapOf>() + + private var stackEntryId = Long.MIN_VALUE + private var routeStackId = Long.MIN_VALUE + var backDispatcher: BackDispatcher? = null set(value) { field?.unregister(this) field = value value?.register(this) } - private var stackEntryId = Long.MIN_VALUE - private var routeStackId = Long.MIN_VALUE + var lifeCycleOwner: LifecycleOwner? = null set(value) { field?.lifecycle?.removeObserver(this) field = value value?.lifecycle?.addObserver(this) } + private var viewModel: NavControllerViewModel? = null + private val _backStacks = mutableStateListOf() internal val backStacks: List @@ -97,35 +103,11 @@ internal class RouteStackManager( goBack() } ) - } - private val routeParser: RouteParser by lazy { - RouteParser().apply { - routeGraph.routes.asSequence() - .map { route -> - RouteParser.expandOptionalVariables(route.route).let { - if (route is SceneRoute) { - it + route.deepLinks.flatMap { - RouteParser.expandOptionalVariables(it) - } - } else { - it - } - } to route - } - .flatMap { it.first.map { route -> route to it.second } } - .forEach { insert(it.first, it.second) } - } - } - - private fun findRoute(route: String): RouteMatchResult? { - val routePath = route.substringBefore('?') - return routeParser.find(path = routePath) - } - - internal fun getBackStackEntry(route: String): BackStackEntry? { - return _backStacks.find { it.hasRoute(route) }?.currentEntry + internal fun getRouteStack(route: String): RouteStack? { + val matchResult = routeGraph.findRoute(route) ?: return null + return _backStacks.find { it.hasRoute(matchResult.route.route) } } internal fun setViewModelStore(viewModelStore: ViewModelStore) { @@ -141,15 +123,19 @@ internal class RouteStackManager( } isPop.value = false - val matchResult = findRoute(route) - checkNotNull(matchResult) { "RouteStackManager: navigate target $route not found" } - require(matchResult.route is ComposeRoute) { "RouteStackManager: navigate target $route is not ComposeRoute" } + val matchResult = routeGraph.findRoute(route) + checkNotNull(matchResult) { + "RouteStackManager: navigate target $route not found" + } + require(matchResult.route is ComposeRoute) { + "RouteStackManager: navigate target $route is not ComposeRoute" + } val query = route.substringAfter('?', "") - fun newEntry(): BackStackEntry { + fun newEntry(route: ComposeRoute): BackStackEntry { return BackStackEntry( id = stackEntryId++, - route = matchResult.route, + route = route, pathMap = matchResult.pathMap, queryString = query.takeIf { it.isNotEmpty() }?.let { QueryString(it) @@ -158,11 +144,17 @@ internal class RouteStackManager( ) } - fun newTask(entry: BackStackEntry, navTransition: NavTransition? = null): RouteStack { + fun newStack( + entry: BackStackEntry, + navTransition: NavTransition? = null, + stackRoute: String? = null, + ): RouteStack { return RouteStack( id = routeStackId++, topEntry = entry, navTransition = navTransition, + stackRoute = stackRoute, + viewModel = vm, ) } @@ -177,17 +169,47 @@ internal class RouteStackManager( } if (!launchSingleTopSuccess) { - val entry = newEntry() - when (matchResult.route) { + when (val matchRoute = matchResult.route) { is SceneRoute -> { - _backStacks.add( - newTask(entry, matchResult.route.navTransition) - ) + val entry = newEntry(matchRoute) + val stack = newStack(entry, matchRoute.navTransition) + _backStacks.add(stack) } is DialogRoute, is BottomSheetRoute -> { + val entry = newEntry(matchRoute) currentStack?.entries?.add(entry) } + is NavigationRoute -> { + val currentStack = currentStack + + val stack = if (currentStack?.stackRoute == matchRoute.route) { + currentStack + } else { + val initialEntry = newEntry(matchRoute.initialRoute) + val stack = newStack( + initialEntry, + navTransition = matchRoute.initialRoute.navTransition, + stackRoute = matchRoute.route, + ) + _backStacks.add(stack) + stack + } + + if (route != matchRoute.route) { + val childMatchResult = matchRoute.graph.findRoute(route) + requireNotNull(childMatchResult) { + "RouteStackManager: child navigate target $route not found" + } + require(childMatchResult.route is ComposeRoute) { + "RouteStackManager: child navigate target $route is not ComposeRoute" + } + + if (childMatchResult.route != matchRoute.initialRoute) { + stack.entries.add(newEntry(childMatchResult.route)) + } + } + } } } @@ -215,7 +237,7 @@ internal class RouteStackManager( isPop.value = true if (!route.isNullOrEmpty()) { - val matchResult = findRoute(route) + val matchResult = routeGraph.findRoute(route) if (matchResult != null) { val index = _backStacks.indexOfLast { it.hasRoute(matchResult.route.route) } if (index != -1) { From e1f2383120f875b02329433c2a601afaa87fa068 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 20:34:17 +0800 Subject: [PATCH 16/18] cleanup route processor --- .../maskbook/common/routeProcessor/RouteDefinition.kt | 3 --- .../maskbook/common/routeProcessor/RouteGraphProcessor.kt | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteDefinition.kt b/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteDefinition.kt index 2d4103f8..40d14b96 100644 --- a/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteDefinition.kt +++ b/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteDefinition.kt @@ -85,7 +85,6 @@ internal data class ParameterRouteDefinition( childRoute.forEach { if (it is FunctionRouteDefinition) { val pathParams = it.parameters.filter { !it.parameter.type.resolve().isMarkedNullable } - // val queryParams = it.parameters.filter { it.parameter.type.resolve().isMarkedNullable } addProperty( PropertySpec.builder("path", String::class) .addModifiers(KModifier.CONST) @@ -96,8 +95,6 @@ internal data class ParameterRouteDefinition( name, if (pathParams.any()) RouteDivider else "", pathParams.joinToString(RouteDivider) { "{${it.name}}" }, - // if (queryParams.any()) "?" else "", - // queryParams.joinToString("&") { "${it.name}={${it.name}}" } ) .build() ) diff --git a/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteGraphProcessor.kt b/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteGraphProcessor.kt index 13e33636..a4ed39d9 100644 --- a/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteGraphProcessor.kt +++ b/common/routeProcessor/src/main/java/com/dimension/maskbook/common/routeProcessor/RouteGraphProcessor.kt @@ -160,7 +160,7 @@ internal class RouteGraphProcessor( val query = it.getAnnotationsByType(Query::class).first() builder.addStatement( - "val ${it.name?.asString()}: %T? = it.query(%S)", + "val ${it.name?.asString()}: %T = it.query(%S)", it.type.toTypeName(), query.name, ) From 3783e088cd14e5c70f547635809c51fef64a3d80 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 20:43:15 +0800 Subject: [PATCH 17/18] [wip]support navigation route --- .../tlaster/precompose/navigation/NavHost.kt | 2 +- .../precompose/navigation/Navigator.kt | 2 +- .../navigation/RouteStackManager.kt | 65 +++++++++++-------- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt index 6b9530ab..60e2c42a 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt @@ -55,7 +55,7 @@ import moe.tlaster.precompose.ui.LocalViewModelStoreOwner * @param builder the builder used to construct the graph */ -@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class) @Composable fun NavHost( navController: NavController, diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt index 9af7e21a..7d626388 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -52,7 +52,7 @@ class NavController { } val backQueue: List - get() = stackManager?.backStacks?.map { it.currentEntry } ?: emptyList() + get() = stackManager?.backStacks?.mapNotNull { it.currentEntry } ?: emptyList() val currentBackStackEntry: BackStackEntry? get() = stackManager?.currentEntry diff --git a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt index e911c5bb..904a157e 100644 --- a/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt @@ -131,6 +131,10 @@ internal class RouteStackManager( "RouteStackManager: navigate target $route is not ComposeRoute" } + if (options?.popUpTo != null) { + popTo(options.popUpTo.route, options.popUpTo.inclusive) + } + val query = route.substringAfter('?', "") fun newEntry(route: ComposeRoute): BackStackEntry { return BackStackEntry( @@ -212,18 +216,6 @@ internal class RouteStackManager( } } } - - if (options?.popUpTo != null && matchResult.route is SceneRoute) { - val index = _backStacks.indexOfLast { it.hasRoute(options.popUpTo.route) } - if (index != -1 && index != _backStacks.lastIndex) { - _backStacks.removeRange( - if (options.popUpTo.inclusive) index else index + 1, - _backStacks.lastIndex, - ) - } else if (options.popUpTo.route.isEmpty()) { - _backStacks.removeRange(0, _backStacks.lastIndex) - } - } } fun goBack( @@ -236,21 +228,10 @@ internal class RouteStackManager( } isPop.value = true - if (!route.isNullOrEmpty()) { - val matchResult = routeGraph.findRoute(route) - if (matchResult != null) { - val index = _backStacks.indexOfLast { it.hasRoute(matchResult.route.route) } - if (index != -1) { - _backStacks.removeRange( - if (inclusive) index else index + 1, - _backStacks.lastIndex, - ) - return true - } - } - } - when { + !route.isNullOrEmpty() -> { + popTo(route, inclusive) + } currentStack?.canGoBack == true -> { currentStack?.goBack() } @@ -272,6 +253,38 @@ internal class RouteStackManager( return true } + private fun popTo(route: String, inclusive: Boolean = false): BackStackEntry? { + val matchResult = routeGraph.findRoute(route) ?: return null + + val matchRoute = matchResult.route + val stackIndex = _backStacks.indexOfLast { it.hasRoute(matchRoute.route) } + if (stackIndex == -1) return null + + val stack = _backStacks[stackIndex] + val entryIndex = stack.entries.indexOfLast { it.route.route == matchRoute.route } + + return when (matchRoute) { + is SceneRoute, is NavigationRoute -> { + if (entryIndex == -1) { + val fromIndex = if (inclusive) stackIndex else stackIndex + 1 + val toIndex = _backStacks.lastIndex + if (fromIndex <= toIndex) { + _backStacks.removeRange(fromIndex, toIndex) + } + currentEntry + } else { + val fromIndex = if (inclusive) entryIndex else entryIndex + 1 + val toIndex = stack.entries.lastIndex + if (fromIndex <= toIndex) { + stack.entries.removeRange(fromIndex, toIndex) + } + stack.currentEntry + } + } + else -> null + } + } + suspend fun waitingForResult(entry: BackStackEntry): Any? = suspendCoroutine { _suspendResult[entry] = it } From d11f090cb1d574e9f1c33d2182dbf3ae27b4aff4 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 12 Apr 2022 21:08:00 +0800 Subject: [PATCH 18/18] fix merge --- .../ui/scenes/wallets/create/create/CreateWalletHost.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt index 656213aa..d69c2ffd 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/ui/scenes/wallets/create/create/CreateWalletHost.kt @@ -31,6 +31,7 @@ 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.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -102,10 +103,10 @@ fun VerifyRoute( .getNestedNavigationViewModel(WalletRoute.CreateWallet.Route) { parametersOf(wallet) } - val result by viewModel.result.observeAsState(initial = null) - val correct by viewModel.correct.observeAsState(initial = false) - val selectedWords by viewModel.selectedWords.observeAsState(initial = emptyList()) - val wordsInRandomOrder by viewModel.wordsInRandomOrder.observeAsState(initial = emptyList()) + val result by viewModel.result.collectAsState(initial = null) + val correct by viewModel.correct.collectAsState(initial = false) + val selectedWords by viewModel.selectedWords.collectAsState(initial = emptyList()) + val wordsInRandomOrder by viewModel.wordsInRandomOrder.collectAsState(initial = emptyList()) var showDialog by remember { mutableStateOf(false) }