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/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 ffca0021..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" @@ -61,7 +60,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..6d77028c 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,32 @@ 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}") - // 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}") + // 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) // Coil api("io.coil-kt:coil-compose:${Versions.coil}") @@ -62,28 +67,21 @@ 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 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}") 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/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/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..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,19 +85,16 @@ 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) .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}}" } ) .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..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 @@ -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/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/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/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..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 @@ -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) { - val backStackEntry = getBackStackEntry(route) - val owner = ViewModelOwner.from(backStackEntry, backStackEntry) - scope.getViewModel(qualifier, { owner }, parameters) + return remember(route, 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/ext/NavControllerExt.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ext/NavControllerExt.kt index 21429d6d..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 @@ -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 } + currentBackStackEntry?.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) { + currentBackStackEntry?.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/Consts.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/route/Consts.kt index e4151d84..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,21 +20,7 @@ */ 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 - -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" @@ -45,56 +31,3 @@ const val navigationComposeModalComposable = "modalComposable" const val navigationComposeBottomSheetPackage = "com.dimension.maskbook.common.route" const val navigationComposeBottomSheet = "bottomSheet" - -@OptIn(ExperimentalAnimationApi::class) -fun NavGraphBuilder.composable( - route: String, - arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit -) { - composable( - route = route, - arguments = arguments, - deepLinks = deepLinks, - content = content, - ) -} - -@OptIn(ExperimentalAnimationApi::class) -fun NavGraphBuilder.modalComposable( - route: String, - arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit -) { - composable( - route = route, - arguments = arguments, - deepLinks = deepLinks, - content = content, - enterTransition = { - slideInVertically { it } - }, - exitTransition = null, - popEnterTransition = null, - popExitTransition = { - slideOutVertically { it } - }, - ) -} - -@OptIn(ExperimentalMaterialNavigationApi::class) -fun NavGraphBuilder.bottomSheet( - route: String, - arguments: List = emptyList(), - deepLinks: List = emptyList(), - content: @Composable ColumnScope.(backstackEntry: NavBackStackEntry) -> Unit -) { - bottomSheet( - route = route, - arguments = arguments, - deepLinks = deepLinks, - content = content - ) -} 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..224b4aed --- /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.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 + +fun RouteBuilder.composable( + route: String, + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + scene( + route = route, + deepLinks = deepLinks, + content = content, + ) +} + +@OptIn(ExperimentalAnimationApi::class) +fun RouteBuilder.modalComposable( + route: String, + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + scene( + route = route, + deepLinks = deepLinks, + content = content, + navTransition = NavTransition( + enterTransition = { + slideInVertically { it } + }, + exitTransition = NavTransition.NoneExit, + popEnterTransition = NavTransition.NoneEnter, + popExitTransition = { + slideOutVertically { it } + }, + ) + ) +} + +fun RouteBuilder.bottomSheet( + route: String, + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + bottomSheet( + route = route, + deepLinks = deepLinks, + content = content + ) +} + +fun RouteBuilder.dialog( + route: String, + deepLinks: List = emptyList(), + content: @Composable (BackStackEntry) -> Unit +) { + dialog( + route = route, + deepLinks = deepLinks, + content = content, + ) +} + +fun RouteBuilder.navigation( + route: String, + startDestination: String, + builder: RouteBuilder.() -> Unit +) { + navigation( + route = route, + initialRoute = startDestination, + builder = builder, + ) +} 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..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 @@ -29,27 +29,27 @@ 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.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 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..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 @@ -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.ui.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/MaskBottomSheetNavigator.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/MaskBottomSheetNavigator.kt index 5f25f379..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 @@ -22,22 +22,20 @@ package com.dimension.maskbook.common.ui.widget 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.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,) +@OptIn(ExperimentalMaterialApi::class) @Composable fun rememberMaskBottomSheetNavigator( animationSpec: AnimationSpec = SwipeableDefaults.AnimationSpec, skipHalfExpanded: Boolean = true, -): BottomSheetNavigator { +): ModalBottomSheetState { val sheetState = rememberModalBottomSheetState( ModalBottomSheetValue.Hidden, animationSpec @@ -59,7 +57,5 @@ fun rememberMaskBottomSheetNavigator( } } - return remember(sheetState) { - BottomSheetNavigator(sheetState = sheetState) - } + return sheetState } 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/RouteHost.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ui/widget/RouteHost.kt index e4abd950..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,32 +24,39 @@ 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 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.NavTransition +import moe.tlaster.precompose.navigation.RouteBuilder +import moe.tlaster.precompose.navigation.rememberNavController private const val navHostAnimationDurationMillis = 320 @ExperimentalAnimationApi -@ExperimentalMaterialNavigationApi +@ExperimentalMaterialApi @Composable fun RouteHost( - bottomSheetNavigator: BottomSheetNavigator = rememberMaskBottomSheetNavigator(), - navController: NavHostController = rememberAnimatedNavController(bottomSheetNavigator), + navController: NavController = rememberNavController(), + bottomSheetState: ModalBottomSheetState = rememberMaskBottomSheetNavigator(), startDestination: String, - builder: NavGraphBuilder.() -> Unit + builder: RouteBuilder.() -> Unit ) { ModalBottomSheetLayout( - bottomSheetNavigator, + sheetContent = navController.sheetContent ?: { + Box(Modifier.height(1.dp)) + }, + sheetState = bottomSheetState, sheetBackgroundColor = MaterialTheme.colors.background, sheetShape = MaterialTheme.shapes.large.copy( bottomStart = CornerSize(0.dp), @@ -57,41 +64,44 @@ fun RouteHost( ), scrimColor = MaterialTheme.colors.modalScrimColor, ) { - AnimatedNavHost( + NavHost( navController = navController, - startDestination = startDestination, - enterTransition = { - slideInHorizontally( - initialOffsetX = { it }, - animationSpec = tween( - navHostAnimationDurationMillis + 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, ) } 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/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/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/BackStackEntry.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt new file mode 100644 index 00000000..6aba4286 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt @@ -0,0 +1,98 @@ +/* + * 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.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 + viewModel.clear(id) + } + } + + inline fun path(path: String): T { + val value = requireNotNull(pathMap[path]) + return convertValue(value) + } + + inline fun query(name: String): T? { + return query(name, null) + } + + 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 { + 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..60e2c42a --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt @@ -0,0 +1,194 @@ +/* + * 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.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.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 +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveableStateHolder +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 builder the builder used to construct the graph + */ + +@OptIn(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(graph, stateHolder, bottomSheetState).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) { + + val finalStackEnter: AnimatedContentScope.() -> EnterTransition = { + if (manager.isPop.value) { + (targetState.navTransition ?: navTransition).popEnterTransition.invoke(this) + } else { + (targetState.navTransition ?: navTransition).enterTransition.invoke(this) + } + } + 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 { + stack.onInActive() + } + } + + @Composable + fun initEntry(entry: BackStackEntry) { + DisposableEffect(entry) { + entry.active() + onDispose { + entry.inActive() + } + } + + stateHolder.SaveableStateProvider(entry.id) { + CompositionLocalProvider( + LocalViewModelStoreOwner provides entry, + LocalLifecycleOwner provides entry, + ) { + entry.route.content(entry) + } + } + } + + initEntry(stack.topEntry) + + val currentEntry = stack.currentDialogEntry + if (currentEntry != null) { + AnimatedContent( + currentEntry, + transitionSpec = { + finalEntryEnter(this) with finalEntryExit(this) + }, + ) { entry -> + initEntry(entry) + } + } + } + } +} 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/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 new file mode 100644 index 00000000..7d626388 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -0,0 +1,131 @@ +/* + * 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.foundation.layout.ColumnScope +import androidx.compose.material.ExperimentalMaterialApi +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() } +} + +@OptIn(ExperimentalMaterialApi::class) +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 currentBackStackEntry: BackStackEntry? + get() = stackManager?.currentEntry + + val sheetContent: (@Composable ColumnScope.() -> Unit)? + get() = stackManager?.sheetContent + + fun getRouteStack(route: String): RouteStack? { + return stackManager?.getRouteStack(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(route: String? = null, inclusive: Boolean = false) { + stackManager?.goBack( + route = route, + inclusive = inclusive, + ) + } + + fun goBackWith(result: Any? = null) { + stackManager?.goBack(result = result) + } + + /** + * Compatibility layer for Jetpack Navigation + */ + fun popBackStack() { + goBack() + } + + fun popBackStack(route: String, inclusive: Boolean = false) { + goBack(route, inclusive) + } + + /** + * 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..c0ca78d1 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/QueryString.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.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() + } +} 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 new file mode 100644 index 00000000..e3dbfcb2 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteBuilder.kt @@ -0,0 +1,119 @@ +/* + * 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 + +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, + 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 + * @param content composable for the destination + */ + 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, + ) + } + + 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, + ) + } + + 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 + } + + 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..30873580 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteGraph.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 + +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/RouteMatch.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.kt new file mode 100644 index 00000000..9372a45a --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatch.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 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..c5ea0e7f --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteMatchResult.kt @@ -0,0 +1,26 @@ +/* + * 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 + +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..56bdee03 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteParser.kt @@ -0,0 +1,694 @@ +/* + * 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 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..142ef51f --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStack.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 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( + val id: Long, + 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 + + 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() + } + } + + fun onActive() { + lifecycleRegistry.currentState = Lifecycle.State.Active + currentEntry.active() + } + + fun onInActive() { + lifecycleRegistry.currentState = Lifecycle.State.InActive + currentEntry.inActive() + if (destroyAfterTransition) { + onDestroyed() + } + } + + fun destroyAfterTransition() { + destroyAfterTransition = true + } + + fun onDestroyed() { + 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 new file mode 100644 index 00000000..904a157e --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/navigation/RouteStackManager.kt @@ -0,0 +1,320 @@ +/* + * 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.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.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 + +@OptIn(ExperimentalMaterialApi::class) +@Stable +internal class RouteStackManager( + 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 + + 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) + } + + 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 == true || _backStacks.size > 1 + + 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() + } + ) + } + + 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) { + if (viewModel != NavControllerViewModel.create(viewModelStore)) { + viewModel = NavControllerViewModel.create(viewModelStore) + } + } + + fun navigate(route: String, options: NavOptions? = null) { + val vm = viewModel ?: run { + pendingNavigation = route + return + } + isPop.value = false + + 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" + } + + if (options?.popUpTo != null) { + popTo(options.popUpTo.route, options.popUpTo.inclusive) + } + + val query = route.substringAfter('?', "") + fun newEntry(route: ComposeRoute): BackStackEntry { + return BackStackEntry( + id = stackEntryId++, + route = route, + pathMap = matchResult.pathMap, + queryString = query.takeIf { it.isNotEmpty() }?.let { + QueryString(it) + }, + viewModel = vm, + ) + } + + fun newStack( + entry: BackStackEntry, + navTransition: NavTransition? = null, + stackRoute: String? = null, + ): RouteStack { + return RouteStack( + id = routeStackId++, + topEntry = entry, + navTransition = navTransition, + stackRoute = stackRoute, + viewModel = vm, + ) + } + + 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) { + when (val matchRoute = matchResult.route) { + is SceneRoute -> { + 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)) + } + } + } + } + } + } + + fun goBack( + route: String? = null, + inclusive: Boolean = false, + result: Any? = null, + ): Boolean { + if (!canGoBack) { + return false + } + isPop.value = true + + when { + !route.isNullOrEmpty() -> { + popTo(route, inclusive) + } + 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) + } + 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 + } + + 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 goBack() + } + + fun navigateInitial(initialRoute: String) { + navigate(initialRoute) + pendingNavigation?.let { + navigate(it) + } + } +} 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/ui/BackHandler.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackHandler.kt new file mode 100644 index 00000000..f779552f --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/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.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 + +@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 : OnBackPressedCallback(enabled) { + override fun handleOnBackPressed() { + currentOnBack.invoke() + } + } + } + + SideEffect { + backCallback.isEnabled = enabled + } + + 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/ui/BackPressAdapter.kt b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt new file mode 100644 index 00000000..fb7c9519 --- /dev/null +++ b/common/src/commonMain/kotlin/moe/tlaster/precompose/ui/BackPressAdapter.kt @@ -0,0 +1,67 @@ +/* + * 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) + } +} + +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/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/Router.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/Router.kt index 4f5db8ff..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,15 +22,17 @@ 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 +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 +45,16 @@ 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, ExperimentalMaterialApi::class) @Composable fun Router( startDestination: String, ) { - val bottomSheetNavigator = rememberMaskBottomSheetNavigator() - val navController = rememberAnimatedNavController(bottomSheetNavigator) + val navController = rememberNavController() LaunchedEffect(Unit) { val initialRoute = getInitialRoute() navController.navigate(initialRoute) { @@ -67,14 +67,13 @@ 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, navController = navController, startDestination = startDestination, ) { 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..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,7 +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 androidx.navigation.NavController +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 @@ -76,7 +76,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/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/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt index 3d8f4aaa..db1d0dd2 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -25,15 +25,9 @@ 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,40 +38,44 @@ 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 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 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 } + navTransition = NavTransition( + enterTransition = NavTransition.NoneEnter, + exitTransition = { + scaleOut( + targetScale = 0.9f, + ) + }, + popExitTransition = NavTransition.NoneExit, + popEnterTransition = { + scaleIn( + initialScale = 0.9f, + ) + } ), - exitTransition = { - scaleOut( - targetScale = 0.9f, - ) - }, - popExitTransition = null, - popEnterTransition = { - scaleIn( - initialScale = 0.9f, - ) - } ) { 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..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 @@ -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.ui.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 16afd877..fb41eae0 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.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage @@ -54,7 +54,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, @@ -68,7 +68,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..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 @@ -51,7 +52,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 +75,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 @@ -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/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/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/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..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 @@ -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( @@ -310,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) { @@ -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..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 @@ -29,10 +29,10 @@ 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.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 @@ -54,7 +54,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( @@ -71,7 +72,7 @@ fun SynchronizationScan( onBack = onBack, onResult = { try { - navController.navigate( + navController.navigateUri( Uri.parse(it), navOptions { popUpTo(PersonaRoute.Synchronization.Scan) { @@ -113,7 +114,7 @@ fun SynchronizationSuccess( }, buttons = { PrimaryButton(onClick = { - navController.navigate( + navController.navigateUri( Uri.parse(Deeplinks.Main.Home(CommonRoute.Main.Tabs.Persona)), navOptions { launchSingleTop = true @@ -170,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 @@ -236,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 } } @@ -250,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/BackupPasswordScene.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/scenes/BackupPasswordScene.kt index 96c671a8..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 @@ -21,10 +21,9 @@ 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.ext.navOptions import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage @@ -33,7 +32,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 +49,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..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,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.CommonRoute import com.dimension.maskbook.common.route.navigationComposeDialog import com.dimension.maskbook.common.route.navigationComposeDialogPackage @@ -42,7 +41,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 1d7ec3b3..6b42a9ce 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 @@ -84,8 +84,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..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 @@ -46,8 +46,8 @@ 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.ext.navigateUri import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage @@ -63,7 +63,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, @@ -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/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..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 @@ -33,14 +33,14 @@ 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.ext.navigateUri import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage @@ -50,7 +50,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 +63,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 = { @@ -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/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..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 @@ -55,11 +55,10 @@ 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 -import com.dimension.maskbook.common.ext.observeAsState +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.route.navigationComposeBottomSheet @@ -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 @@ -384,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") }, ) { @@ -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/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/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..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 @@ -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,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.navigate import com.dimension.maskbook.common.route.navigationComposeBottomSheet import com.dimension.maskbook.common.route.navigationComposeBottomSheetPackage import com.dimension.maskbook.common.route.navigationComposeDialog @@ -59,7 +59,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 +131,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 +169,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 +255,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 +336,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..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,8 +23,8 @@ 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.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 +35,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 +50,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 +84,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/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, 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/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/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..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 @@ -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,8 @@ 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.navigate +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 +82,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 +174,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 +202,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 +340,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 +481,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 +579,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 +676,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 +705,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..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 @@ -25,13 +25,13 @@ 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.ext.navigate import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks import com.dimension.maskbook.common.route.navigationComposeBottomSheet @@ -55,7 +55,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 +267,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 +301,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 +386,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 +429,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 +463,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 +546,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 +581,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 +662,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 +705,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 +745,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..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,9 +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 androidx.navigation.NavController import com.dimension.maskbook.common.ext.getNestedNavigationViewModel -import com.dimension.maskbook.common.ext.observeAsState +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 @@ -68,6 +67,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 +145,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/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", 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 { 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 af3d64e3..70996c93 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/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( 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 c34c1ee9..3c1a6b43 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,11 +36,10 @@ 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.navOptions +import com.dimension.maskbook.common.ext.navigate +import com.dimension.maskbook.common.ext.navigateUri import com.dimension.maskbook.common.bigDecimal.BigDecimal -import com.dimension.maskbook.common.ext.observeAsState import com.dimension.maskbook.common.ext.shareText import com.dimension.maskbook.common.route.CommonRoute import com.dimension.maskbook.common.route.Deeplinks @@ -97,8 +96,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( @@ -115,9 +116,9 @@ fun CollectibleDetail( val viewModel = getViewModel { parametersOf(id) } - val data by viewModel.data.observeAsState(initial = null) - val transactions by viewModel.transactions.observeAsState() - val nativeToken by viewModel.walletNativeToken.observeAsState() + val data by viewModel.data.collectAsState(initial = null) + val transactions by viewModel.transactions.collectAsState() + val nativeToken by viewModel.walletNativeToken.collectAsState() CollectibleDetailScene( data = data, onBack = onBack, @@ -150,7 +151,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 @@ -180,11 +181,11 @@ 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 nativeToken by viewModel.walletNativeToken.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() + val nativeToken by viewModel.walletNativeToken.collectAsState() TokenDetailScene( onBack = onBack, @@ -257,7 +258,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, @@ -279,8 +280,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) { @@ -314,9 +315,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, @@ -403,7 +404,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, @@ -447,11 +448,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, @@ -486,8 +487,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, @@ -505,7 +506,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, @@ -532,13 +533,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 @@ -559,15 +560,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 = { @@ -581,7 +582,7 @@ fun WalletIntroHostLegal( navController.navigate( route, navOptions { - popUpTo(id = navBackStackEntry.destination.id) { + popUpTo(route = navBackStackEntry.route.route) { inclusive = true } launchSingleTop = true @@ -618,7 +619,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( @@ -801,9 +802,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, @@ -855,7 +856,7 @@ fun EmptyTokenDialogRoute( tokenSymbol = tokenSymbol, onCancel = onBack, onBuy = { - navController.navigate(deepLink = Uri.parse(Deeplinks.Labs.Transak)) + navController.navigateUri(Uri.parse(Deeplinks.Labs.Transak)) } ) } 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 5373e4cb..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 @@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.Box 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.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -34,10 +35,9 @@ 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 @@ -55,6 +55,7 @@ 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.create.CreateWalletRecoveryKeyViewModel +import moe.tlaster.precompose.navigation.NavController import org.koin.core.parameter.parametersOf private const val GeneratedRouteName = "createWalletRoute" @@ -75,7 +76,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 = { @@ -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) } @@ -159,7 +160,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/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 28f8563a..3d8a57da 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,36 +21,34 @@ 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.route.transfer 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 nativeToken by viewModel.walletNativeToken.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 nativeToken by viewModel.walletNativeToken.collectAsState() val currentWallet = wallet val currentDWebData = dWebData @@ -68,7 +66,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..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 @@ -21,22 +21,18 @@ 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 +48,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 +77,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 +126,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 +169,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 9b65e1a8..2a598a26 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,14 +185,14 @@ 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 isEnoughForGas by transferDetailViewModel.isEnoughForGas.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() + val isEnoughForGas by transferDetailViewModel.isEnoughForGas.collectAsState() transferDetailViewModel.setGasTotal(gasTotal = gasTotal) TransferDetailScene( @@ -272,8 +271,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() @@ -310,7 +309,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() @@ -342,15 +341,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(), @@ -401,7 +400,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, @@ -439,10 +438,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) } @@ -450,9 +449,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..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 @@ -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.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 import com.dimension.maskbook.common.ui.widget.HorizontalScenePadding @@ -96,12 +93,12 @@ 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.NavHost +import moe.tlaster.precompose.navigation.rememberNavController import org.koin.core.parameter.parametersOf enum class WalletConnectType { @@ -112,7 +109,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 +132,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 @@ -151,9 +148,9 @@ fun WalletConnectModal(rootNavController: NavController) { textAlign = TextAlign.Center, ) - AnimatedNavHost( + NavHost( navController = navController, - startDestination = "WalletConnectTypeSelect" + initialRoute = "WalletConnectTypeSelect" ) { composable("WalletConnectTypeSelect") { TypeSelectScene( @@ -201,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() 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 61cd4ac8..c7a258a8 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 @@ -33,6 +31,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest 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 0658eec1..538b1296 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 @@ -47,6 +45,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 8c224e85..7c0a391c 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 @@ -32,6 +30,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest +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 9b037362..9f0e8c98 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()