From 9d42f819f02c14396e492c590ba8868f212842b3 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 19 Apr 2022 11:08:49 +0800 Subject: [PATCH 1/7] rename ioScope to appScope --- .../dimension/maskbook/common/CommonSetup.kt | 11 ++--- .../com/dimension/maskbook/common/Consts.kt | 2 - .../common/di/module/CoroutinesModule.kt | 45 +++++++++++++++++++ .../common/di/scope/CoroutinesQualifiers.kt | 30 +++++++++++++ .../maskbook/extension/ExtensionSetup.kt | 9 ++-- .../com/dimension/maskbook/labs/LabsSetup.kt | 7 ++- .../maskbook/persona/PersonaSetup.kt | 9 ++-- 7 files changed, 89 insertions(+), 24 deletions(-) create mode 100644 common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt create mode 100644 common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt 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..65b1ba2a 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt @@ -22,18 +22,14 @@ package com.dimension.maskbook.common import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder +import com.dimension.maskbook.common.di.module.coroutinesModule import com.dimension.maskbook.common.manager.ImageLoaderManager import com.dimension.maskbook.common.manager.KeyStoreManager import com.dimension.maskbook.common.util.BiometricAuthenticator -import com.dimension.maskbook.common.util.coroutineExceptionHandler import com.dimension.maskbook.common.viewmodel.BiometricEnableViewModel import com.dimension.maskbook.common.viewmodel.BiometricViewModel 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 org.koin.core.qualifier.named import org.koin.dsl.module object CommonSetup : ModuleSetup { @@ -41,9 +37,8 @@ object CommonSetup : ModuleSetup { } override fun dependencyInject() = module { - single(named(IoScopeName)) { - CoroutineScope(SupervisorJob() + Dispatchers.IO + coroutineExceptionHandler) - } + coroutinesModule() + single { BiometricAuthenticator() } single { KeyStoreManager(get()) } single { ImageLoaderManager(get()) } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/Consts.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/Consts.kt index c6daf0b8..711fe9bb 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/Consts.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/Consts.kt @@ -20,6 +20,4 @@ */ package com.dimension.maskbook.common -const val IoScopeName = "IoScope" - const val LocalBackupAccount = "" diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt new file mode 100644 index 00000000..85027aff --- /dev/null +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.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 com.dimension.maskbook.common.di.module + +import com.dimension.maskbook.common.di.scope.appScope +import com.dimension.maskbook.common.di.scope.defaultDispatcher +import com.dimension.maskbook.common.di.scope.ioDispatcher +import com.dimension.maskbook.common.di.scope.mainDispatcher +import com.dimension.maskbook.common.di.scope.mainImmediateDispatcher +import com.dimension.maskbook.common.util.coroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import org.koin.core.module.Module + +fun Module.coroutinesModule() { + single(defaultDispatcher) { Dispatchers.Default } + single(ioDispatcher) { Dispatchers.IO } + single(mainDispatcher) { Dispatchers.Main } + single(mainImmediateDispatcher) { Dispatchers.Main.immediate } + + single(appScope) { + CoroutineScope( + coroutineExceptionHandler + SupervisorJob() + Dispatchers.IO + ) + } +} diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt new file mode 100644 index 00000000..0279fdd3 --- /dev/null +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt @@ -0,0 +1,30 @@ +/* + * 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.di.scope + +import org.koin.core.qualifier.named + +val defaultDispatcher = named("DefaultDispatcher") +val ioDispatcher = named("IoDispatcher") +val mainDispatcher = named("MainDispatcher") +val mainImmediateDispatcher = named("MainImmediateDispatcher") + +val appScope = named("AppScope") 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 2e6da07a..986edebb 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -27,8 +27,8 @@ 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.di.scope.appScope import com.dimension.maskbook.common.ext.navigateToHome import com.dimension.maskbook.common.gecko.WebContentController import com.dimension.maskbook.common.route.Deeplinks @@ -40,7 +40,6 @@ 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 org.koin.core.qualifier.named import org.koin.dsl.module import org.koin.mp.KoinPlatformTools @@ -69,11 +68,11 @@ object ExtensionSetup : ModuleSetup { } override fun dependencyInject() = module { - single { WebContentController(get(), get(named(IoScopeName))) } + single { WebContentController(get(), get(appScope)) } single { ExtensionRepository(get()) } single { ExtensionServicesImpl(get(), get(), get()) } - single { BackgroundMessageChannel(get(), get(named(IoScopeName))) } - single { ContentMessageChannel(get(), get(named(IoScopeName))) } + single { BackgroundMessageChannel(get(), get(appScope)) } + single { ContentMessageChannel(get(), get(appScope)) } } override fun onExtensionReady() { 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 7ba97e29..466c2b7f 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt @@ -23,8 +23,8 @@ 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.di.scope.appScope import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.labs.data.JSMethod import com.dimension.maskbook.labs.data.RedPacketMethod @@ -39,7 +39,6 @@ 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 org.koin.core.qualifier.named import org.koin.dsl.bind import org.koin.dsl.module import org.koin.mp.KoinPlatformTools @@ -53,10 +52,10 @@ object LabsSetup : ModuleSetup { override fun dependencyInject() = module { single { AppRepository(get()) } single { - PreferenceRepository(get().labsDataStore, get(named(IoScopeName))) + PreferenceRepository(get().labsDataStore, get(appScope)) } single { JSMethod(get()) } - single { RedPacketMethod(get(named(IoScopeName)), get()) } + single { RedPacketMethod(get(appScope), get()) } single { LabsTabScreen() } bind TabScreen::class 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 67a9c4dd..8a111e1e 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt @@ -25,9 +25,9 @@ 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.di.scope.appScope import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.persona.data.JSMethod import com.dimension.maskbook.persona.data.JSMethodV2 @@ -81,7 +81,6 @@ import com.google.accompanist.navigation.animation.navigation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.core.qualifier.named import org.koin.dsl.bind import org.koin.dsl.binds import org.koin.dsl.module @@ -118,7 +117,7 @@ object PersonaSetup : ModuleSetup { } single { PersonaRepository( - get(named(IoScopeName)), + get(appScope), get(), get(), get(), get(), get(), get(), get(), @@ -131,14 +130,14 @@ object PersonaSetup : ModuleSetup { single { PreferenceRepository( get().personaDataStore, - get(named(IoScopeName)) + get(appScope) ) } single { JSMethod(get()) } single { JSMethodV2( - get(named(IoScopeName)), + get(appScope), get(), get(), get(), get(), From 2b63f7e861a5ceb5b570195774d8175c69c07f8a Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 19 Apr 2022 11:32:45 +0800 Subject: [PATCH 2/7] unified appScope --- .../dimension/maskbook/common/ModuleSetup.kt | 3 +- .../dimension/maskbook/entry/EntrySetup.kt | 38 ++++---- .../entry/repository/EntryRepository.kt | 3 +- .../com/dimension/maskbook/entry/ui/App.kt | 20 +++-- .../maskbook/extension/ExtensionSetup.kt | 10 +-- .../repository/ExtensionRepository.kt | 3 +- .../com/dimension/maskbook/labs/LabsSetup.kt | 10 +-- .../maskbook/labs/repository/AppRepository.kt | 3 +- .../maskbook/persona/PersonaSetup.kt | 8 +- .../maskbook/setting/SettingSetup.kt | 29 +++++-- .../maskbook/setting/data/JSDataSource.kt | 3 +- .../setting/data/SettingDataSource.kt | 3 +- .../setting/repository/BackupRepository.kt | 3 +- .../dimension/maskbook/wallet/WalletSetup.kt | 87 ++++++++++--------- .../wallet/handler/Web3MessageHandler.kt | 3 +- .../repository/ISendHistoryRepository.kt | 3 +- .../repository/IWalletConnectRepository.kt | 7 +- .../repository/WalletContactRepository.kt | 4 +- .../wallet/repository/WalletRepository.kt | 7 +- 19 files changed, 131 insertions(+), 116 deletions(-) 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..9587ca3b 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/ModuleSetup.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/ModuleSetup.kt @@ -22,12 +22,13 @@ package com.dimension.maskbook.common import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder +import org.koin.core.Koin import org.koin.core.module.Module interface ModuleSetup { fun NavGraphBuilder.route(navController: NavController) fun dependencyInject(): Module - fun onExtensionReady() {} + fun onExtensionReady(koin: Koin) = Unit } fun ModuleSetup.route(builder: NavGraphBuilder, navController: NavController) = 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..e29e9840 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt @@ -24,18 +24,20 @@ import android.content.Context import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.ModuleSetup +import com.dimension.maskbook.common.di.scope.appScope +import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.route.Navigator import com.dimension.maskbook.entry.data.JSMethod import com.dimension.maskbook.entry.repository.EntryRepository import com.dimension.maskbook.entry.repository.entryDataStore import com.dimension.maskbook.entry.ui.scene.generatedRoute +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch +import org.koin.core.Koin import org.koin.dsl.module -import org.koin.mp.KoinPlatformTools object EntrySetup : ModuleSetup { override fun NavGraphBuilder.route(navController: NavController) { @@ -43,25 +45,25 @@ object EntrySetup : ModuleSetup { } override fun dependencyInject() = module { - single { EntryRepository(get().entryDataStore) } + single { EntryRepository(get().entryDataStore, get(appScope)) } single { JSMethod(get()) } } - override fun onExtensionReady() { - KoinPlatformTools.defaultContext().get().get().apply { - CoroutineScope(Dispatchers.IO).launch { - launch { - merge( - openCreateWalletView(), - openDashboardView(), - openAppsView(), - openSettingsView(), - ).filter { uri -> - uri.isNotEmpty() - }.collect { uri -> - Navigator.deeplink(uri) - } - } + override fun onExtensionReady(koin: Koin) { + val appScope = koin.get(appScope) + val ioDispatcher = koin.get(ioDispatcher) + val jsMethod = koin.get() + + appScope.launch(ioDispatcher) { + merge( + jsMethod.openCreateWalletView(), + jsMethod.openDashboardView(), + jsMethod.openAppsView(), + jsMethod.openSettingsView(), + ).filter { uri -> + uri.isNotEmpty() + }.collect { uri -> + Navigator.deeplink(uri) } } } diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt index 224d64a2..d757dfa4 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt @@ -27,7 +27,6 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -37,8 +36,8 @@ val Context.entryDataStore: DataStore by preferencesDataStore(name class EntryRepository( private val dataStore: DataStore, + private val scope: CoroutineScope, ) { - private val scope = CoroutineScope(Dispatchers.IO) val shouldShowEntry: Flow get() = dataStore.data.map { it[ShouldShowEntryKey] ?: true diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/App.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/App.kt index 35f412df..ff578269 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/App.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/ui/App.kt @@ -33,7 +33,7 @@ import com.dimension.maskbook.labs.LabsSetup import com.dimension.maskbook.persona.PersonaSetup import com.dimension.maskbook.setting.SettingSetup import com.dimension.maskbook.wallet.WalletSetup -import org.koin.mp.KoinPlatformTools +import org.koin.core.context.GlobalContext @OptIn(ExperimentalAnimationApi::class) @Composable @@ -52,12 +52,14 @@ fun App( } private suspend fun warmingUp() { - KoinPlatformTools.defaultContext().get().get().ensureExtensionActive() - CommonSetup.onExtensionReady() - WalletSetup.onExtensionReady() - SettingSetup.onExtensionReady() - LabsSetup.onExtensionReady() - PersonaSetup.onExtensionReady() - EntrySetup.onExtensionReady() - ExtensionSetup.onExtensionReady() + val koin = GlobalContext.get() + + koin.get().ensureExtensionActive() + CommonSetup.onExtensionReady(koin) + WalletSetup.onExtensionReady(koin) + SettingSetup.onExtensionReady(koin) + LabsSetup.onExtensionReady(koin) + PersonaSetup.onExtensionReady(koin) + EntrySetup.onExtensionReady(koin) + ExtensionSetup.onExtensionReady(koin) } 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 986edebb..19b0c6ad 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -40,8 +40,8 @@ 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 org.koin.core.Koin import org.koin.dsl.module -import org.koin.mp.KoinPlatformTools object ExtensionSetup : ModuleSetup { override fun NavGraphBuilder.route(navController: NavController) { @@ -69,14 +69,14 @@ object ExtensionSetup : ModuleSetup { override fun dependencyInject() = module { single { WebContentController(get(), get(appScope)) } - single { ExtensionRepository(get()) } + single { ExtensionRepository(get(), get(appScope)) } single { ExtensionServicesImpl(get(), get(), get()) } single { BackgroundMessageChannel(get(), get(appScope)) } single { ContentMessageChannel(get(), get(appScope)) } } - override fun onExtensionReady() { - KoinPlatformTools.defaultContext().get().get().startMessageCollect() - KoinPlatformTools.defaultContext().get().get().startMessageCollect() + override fun onExtensionReady(koin: Koin) { + koin.get().startMessageCollect() + koin.get().startMessageCollect() } } diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt index 223540ff..39364512 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt @@ -25,7 +25,6 @@ import com.dimension.maskbook.extension.export.model.Site import com.dimension.maskbook.extension.ext.site import com.dimension.maskbook.extension.ext.url import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow @@ -36,8 +35,8 @@ import kotlinx.coroutines.launch @OptIn(InternalCoroutinesApi::class) class ExtensionRepository( private val controller: WebContentController, + private val scope: CoroutineScope, ) { - private val scope = CoroutineScope(Dispatchers.IO) private val _currentSite = MutableStateFlow(Site.Twitter) val currentSite = _currentSite.asSharedFlow() fun setCurrentSite(site: Site) { 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 466c2b7f..94f58b4e 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt @@ -39,9 +39,9 @@ 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 org.koin.core.Koin import org.koin.dsl.bind import org.koin.dsl.module -import org.koin.mp.KoinPlatformTools object LabsSetup : ModuleSetup { @@ -50,7 +50,7 @@ object LabsSetup : ModuleSetup { } override fun dependencyInject() = module { - single { AppRepository(get()) } + single { AppRepository(get(), get(appScope)) } single { PreferenceRepository(get().labsDataStore, get(appScope)) } @@ -64,8 +64,8 @@ object LabsSetup : ModuleSetup { viewModel { (dataRaw: String, requestRaw: String?) -> LuckDropViewModel(dataRaw, requestRaw, get(), get()) } } - override fun onExtensionReady() { - KoinPlatformTools.defaultContext().get().get().init() - KoinPlatformTools.defaultContext().get().get().startSubscribe() + override fun onExtensionReady(koin: Koin) { + koin.get().init() + koin.get().startSubscribe() } } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt index a1f1c34b..c1ff3da7 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt @@ -24,15 +24,14 @@ import com.dimension.maskbook.labs.data.JSMethod import com.dimension.maskbook.labs.export.model.AppData import com.dimension.maskbook.labs.export.model.AppKey import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch internal class AppRepository( private val jsMethod: JSMethod, + private val scope: CoroutineScope, ) : IAppRepository { - private val scope = CoroutineScope(Dispatchers.IO) private val _apps = MutableStateFlow( AppKey.values().map { AppData(it, true) } 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 8a111e1e..c6ad955e 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt @@ -81,10 +81,10 @@ import com.google.accompanist.navigation.animation.navigation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.Koin import org.koin.dsl.bind import org.koin.dsl.binds import org.koin.dsl.module -import org.koin.mp.KoinPlatformTools object PersonaSetup : ModuleSetup { @@ -206,8 +206,8 @@ object PersonaSetup : ModuleSetup { viewModel { PersonaLogoutViewModel(get(), get()) } } - override fun onExtensionReady() { - KoinPlatformTools.defaultContext().get().get().init() - KoinPlatformTools.defaultContext().get().get().startSubscribe() + override fun onExtensionReady(koin: Koin) { + koin.get().init() + koin.get().startSubscribe() } } 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 27f7d78e..197a37fb 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt @@ -25,6 +25,7 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.navigation import com.dimension.maskbook.common.ModuleSetup +import com.dimension.maskbook.common.di.scope.appScope import com.dimension.maskbook.common.retrofit.retrofit import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.setting.data.JSDataSource @@ -54,9 +55,9 @@ 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 org.koin.core.Koin import org.koin.dsl.bind import org.koin.dsl.module -import org.koin.mp.KoinPlatformTools object SettingSetup : ModuleSetup { override fun NavGraphBuilder.route(navController: NavController) { @@ -76,12 +77,24 @@ object SettingSetup : ModuleSetup { single { SettingsRepository(get(), get(), get(), get()) } - single { BackupRepository(get(), get().cacheDir, get().contentResolver) } - single { SettingServicesImpl(get(), get()) } bind com.dimension.maskbook.setting.export.BackupServices::class + single { + BackupRepository( + get(), + get().cacheDir, + get().contentResolver, + get(appScope) + ) + } + single { + SettingServicesImpl( + get(), + get() + ) + } bind com.dimension.maskbook.setting.export.BackupServices::class single { SettingsTabScreen() } bind TabScreen::class - single { JSDataSource(get()) } + single { JSDataSource(get(), get(appScope)) } single { JSMethod(get()) } - single { SettingDataSource(get().settingsDataStore) } + single { SettingDataSource(get().settingsDataStore, get(appScope)) } viewModel { LanguageSettingsViewModel(get()) } viewModel { AppearanceSettingsViewModel(get()) } @@ -93,14 +106,14 @@ object SettingSetup : ModuleSetup { viewModel { PhoneSetupViewModel(get(), get()) } viewModel { EmailBackupViewModel(get()) } viewModel { PhoneBackupViewModel(get()) } - viewModel { (onDone: () -> Unit, url: String, account: String,) -> + viewModel { (onDone: () -> Unit, url: String, account: String) -> BackupMergeConfirmViewModel(get(), get(), get().contentResolver, onDone, url, account) } viewModel { BackupCloudViewModel(get()) } viewModel { BackupCloudExecuteViewModel(get(), get(), get()) } } - override fun onExtensionReady() { - KoinPlatformTools.defaultContext().get().get().initData() + override fun onExtensionReady(koin: Koin) { + koin.get().initData() } } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt index c18dcdd1..2542882e 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt @@ -24,7 +24,6 @@ import com.dimension.maskbook.setting.export.model.Appearance import com.dimension.maskbook.setting.export.model.DataProvider import com.dimension.maskbook.setting.export.model.Language import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow @@ -33,8 +32,8 @@ import kotlinx.coroutines.launch internal class JSDataSource( private val jsMethod: JSMethod, + private val scope: CoroutineScope, ) { - private val scope = CoroutineScope(Dispatchers.IO) private val _appearance = MutableStateFlow(Appearance.default) private val _dataProvider = MutableStateFlow(DataProvider.COIN_GECKO) private val _language = MutableStateFlow(Language.auto) diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt index d3f4f7b2..fe4054bd 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt @@ -28,7 +28,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -43,8 +42,8 @@ val Context.settingsDataStore: DataStore by preferencesDataStore(na class SettingDataSource( private val dataStore: DataStore, + private val scope: CoroutineScope, ) { - private val scope = CoroutineScope(Dispatchers.IO) val biometricEnabled: Flow get() = dataStore.data.map { it[BiometricEnabledKey] ?: false diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt index 460faf20..ef62d0f0 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt @@ -36,7 +36,6 @@ import com.dimension.maskbook.setting.services.model.SendCodeBody import com.dimension.maskbook.setting.services.model.UploadBody import com.dimension.maskbook.setting.services.model.ValidateCodeBody import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext import kotlinx.serialization.builtins.serializer @@ -57,8 +56,8 @@ class BackupRepository( private val backupServices: BackupServices, private val cacheDir: File, private val contentResolver: ContentResolver, + private val scope: CoroutineScope, ) { - private val scope = CoroutineScope(Dispatchers.IO) suspend fun sendPhoneCode(phone: String) { withContext(scope.coroutineContext) { backupServices.sendCode( 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..23037705 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt @@ -26,6 +26,8 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.room.Room import com.dimension.maskbook.common.ModuleSetup +import com.dimension.maskbook.common.di.scope.appScope +import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.wallet.data.JSMethod import com.dimension.maskbook.wallet.db.AppDatabase @@ -109,15 +111,16 @@ 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.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.Koin import org.koin.core.module.Module import org.koin.dsl.bind import org.koin.dsl.module -import org.koin.mp.KoinPlatformTools import com.dimension.maskbook.wallet.export.WalletServices as ExportWalletServices object WalletSetup : ModuleSetup { @@ -162,59 +165,62 @@ object WalletSetup : ModuleSetup { provideServices() } - override fun onExtensionReady() { - initRepository() - initWalletConnect() - initEvent() + override fun onExtensionReady(koin: Koin) { + initRepository(koin) + initWalletConnect(koin) + initEvent(koin) } } -private fun initEvent() { - with(KoinPlatformTools.defaultContext().get()) { - CoroutineScope(Dispatchers.IO).launch { - launch { - get().web3Event().collect { - get().handle(it) +private fun initEvent(koin: Koin) { + val appScope = koin.get(appScope) + val ioDispatcher = koin.get(ioDispatcher) + val jsMethod = koin.get() + val walletRepository = koin.get() + + appScope.launch(ioDispatcher) { + jsMethod.web3Event().collect { + koin.get().handle(it) + } + } + + appScope.launch(ioDispatcher) { + jsMethod.switchBlockChain().collect { data -> + if (data.coinId != null) { + val platform = CoinPlatformType.values().firstOrNull { it.coinId == data.coinId } + if (platform != null) { + walletRepository.setActiveCoinPlatformType(platform) } } - launch { - get().switchBlockChain().collect { data -> - if (data.coinId != null) { - val platform = - CoinPlatformType.values().firstOrNull { it.coinId == data.coinId } - if (platform != null) { - get().setActiveCoinPlatformType(platform) - } - } - if (data.networkId != null) { - val chainType = - ChainType.values().firstOrNull { it.chainId == data.networkId } - if (chainType != null) { - get().setChainType(chainType, false) - } - } + if (data.networkId != null) { + val chainType = ChainType.values().firstOrNull { it.chainId == data.networkId } + if (chainType != null) { + walletRepository.setChainType(chainType, false) } } } } } -private fun initRepository() { - KoinPlatformTools.defaultContext().get().get().init() - KoinPlatformTools.defaultContext().get().get().init() +private fun initRepository(koin: Koin) { + koin.get().init() + koin.get().init() } -private fun initWalletConnect() { - val walletRepository = KoinPlatformTools.defaultContext().get().get() - KoinPlatformTools.defaultContext().get().get() +private fun initWalletConnect(koin: Koin) { + val appScope = koin.get(appScope) + val ioDispatcher = koin.get(ioDispatcher) + val walletRepository = koin.get() + + koin.get() .initSessions { address -> - CoroutineScope(Dispatchers.IO).launch { + appScope.launch(ioDispatcher) { walletRepository.findWalletByAddress(address)?.let { wallet -> walletRepository.deleteWallet(wallet.id) } } } - KoinPlatformTools.defaultContext().get().get() + koin.get() .init { _, _ -> // clientMeta, request -> TODO("navigate to wallet connect request handle scene") } @@ -234,17 +240,18 @@ private fun Module.provideRepository() { get(), get(), get(), - get() + get(), + get(appScope) ) } single { JSMethod(get()) } - single { Web3MessageHandler(get()) } + single { Web3MessageHandler(get(), get(appScope)) } single { CollectibleRepository(get(), get()) } single { TransactionRepository(get(), get()) } single { TokenRepository(get()) } - single { SendHistoryRepository(get()) } - single { WalletContactRepository(get()) } - single { WalletConnectRepository(get(), get()) } + single { SendHistoryRepository(get(), get(appScope)) } + single { WalletContactRepository(get(), get(appScope)) } + single { WalletConnectRepository(get(), get(), get(appScope)) } } private fun Module.provideUseCase() { diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/handler/Web3MessageHandler.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/handler/Web3MessageHandler.kt index 2abeb4f0..a84e0eca 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/handler/Web3MessageHandler.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/handler/Web3MessageHandler.kt @@ -37,7 +37,6 @@ import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.NullNode import com.fasterxml.jackson.databind.node.ObjectNode import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.web3j.protocol.core.Request @@ -45,8 +44,8 @@ import org.web3j.protocol.core.Response internal class Web3MessageHandler( private val walletRepository: IWalletRepository, + private val scope: CoroutineScope, ) { - private val scope = CoroutineScope(Dispatchers.IO) fun handle(request: Web3Request) { scope.launch { val payload = request.payload diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/ISendHistoryRepository.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/ISendHistoryRepository.kt index 0ed27409..56eda066 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/ISendHistoryRepository.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/ISendHistoryRepository.kt @@ -23,7 +23,6 @@ package com.dimension.maskbook.wallet.repository import com.dimension.maskbook.wallet.db.AppDatabase import com.dimension.maskbook.wallet.db.model.DbSendHistory import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest @@ -41,8 +40,8 @@ interface ISendHistoryRepository { class SendHistoryRepository( private val database: AppDatabase, + private val scope: CoroutineScope, ) : ISendHistoryRepository { - private val scope = CoroutineScope(Dispatchers.IO) override val recent: Flow> get() = database.sendHistoryDao().getAll().map { list -> list.sortedByDescending { it.history.lastSend } diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletConnectRepository.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletConnectRepository.kt index d2c7e84b..2ca36ca7 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletConnectRepository.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletConnectRepository.kt @@ -34,7 +34,6 @@ import com.dimension.maskbook.wallet.services.WalletServices import com.dimension.maskbook.wallet.services.model.WCSupportedWallet import com.dimension.maskbook.wallet.walletconnect.WCResponder import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull @@ -97,12 +96,12 @@ interface IWalletConnectRepository { class WalletConnectRepository( private val walletServices: WalletServices, - private val database: AppDatabase + private val database: AppDatabase, + private val scope: CoroutineScope, ) : IWalletConnectRepository { - private val wcScope = CoroutineScope(Dispatchers.IO) override fun init() { - wcScope.launch { + scope.launch { try { refreshSupportedWallets() } catch (e: Throwable) { diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletContactRepository.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletContactRepository.kt index b4aa3b3f..bc60a7b2 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletContactRepository.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletContactRepository.kt @@ -24,7 +24,6 @@ import com.dimension.maskbook.wallet.db.AppDatabase import com.dimension.maskbook.wallet.db.model.DbSendHistoryWithContact import com.dimension.maskbook.wallet.db.model.DbWalletContact import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext @@ -63,8 +62,9 @@ interface IWalletContactRepository { class WalletContactRepository( private val database: AppDatabase, + private val scope: CoroutineScope, ) : IWalletContactRepository { - private val scope = CoroutineScope(Dispatchers.IO) + override val contacts: Flow> get() = database.walletContactDao().getAll().map { it.map { SearchAddressData.fromDb(it) } } 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 3b9ecb52..393d4d39 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 @@ -115,13 +115,12 @@ internal class WalletRepository( private val services: WalletServices, private val walletConnectManager: WalletConnectClientManager, private val jsMethod: JSMethod, + private val scope: CoroutineScope, ) : IWalletRepository { - private val tokenScope = CoroutineScope(Dispatchers.IO) - private val scope = CoroutineScope(Dispatchers.IO) @OptIn(ExperimentalTime::class) override fun init() { - tokenScope.launch { + scope.launch { refreshChainData() while (true) { delay(12.seconds) @@ -826,7 +825,7 @@ internal class WalletRepository( } override suspend fun refreshWallet() { - withContext(tokenScope.coroutineContext) { + withContext(scope.coroutineContext) { refreshCurrentWalletToken() refreshCurrentWalletCollectibles() refreshNativeTokens() From 908312773fb26c16e25b99d86a4bb654ec8ad7da Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 19 Apr 2022 13:43:29 +0800 Subject: [PATCH 3/7] remove dispatcher in appScope --- .../common/gecko/WebContentController.kt | 9 +- .../common/di/module/CoroutinesModule.kt | 9 +- .../dimension/maskbook/entry/EntrySetup.kt | 13 +- .../entry/repository/EntryRepository.kt | 6 +- .../maskbook/extension/ExtensionSetup.kt | 21 +- .../repository/ExtensionRepository.kt | 20 +- .../extension/utils/MessageChannel.kt | 12 +- .../com/dimension/maskbook/labs/LabsSetup.kt | 29 ++- .../maskbook/labs/data/RedPacketMethod.kt | 10 +- .../maskbook/labs/repository/AppRepository.kt | 8 +- .../labs/repository/PreferenceRepository.kt | 6 +- .../maskbook/persona/PersonaSetup.kt | 16 +- .../maskbook/persona/data/JSMethodV2.kt | 13 +- .../persona/repository/PersonaRepository.kt | 32 +-- .../repository/PreferenceRepository.kt | 15 +- .../maskbook/setting/SettingSetup.kt | 38 ++- .../maskbook/setting/data/JSDataSource.kt | 12 +- .../setting/data/SettingDataSource.kt | 16 +- .../setting/repository/BackupRepository.kt | 20 +- .../setting/repository/SettingsRepository.kt | 7 +- .../dimension/maskbook/wallet/WalletSetup.kt | 32 ++- .../wallet/handler/Web3MessageHandler.kt | 232 +++++++++--------- .../repository/ISendHistoryRepository.kt | 6 +- .../repository/IWalletConnectRepository.kt | 23 +- .../wallet/repository/IWalletRepository.kt | 2 +- .../repository/WalletContactRepository.kt | 6 +- .../wallet/repository/WalletRepository.kt | 49 ++-- 27 files changed, 380 insertions(+), 282 deletions(-) diff --git a/common/gecko/src/androidMain/kotlin/com/dimension/maskbook/common/gecko/WebContentController.kt b/common/gecko/src/androidMain/kotlin/com/dimension/maskbook/common/gecko/WebContentController.kt index 7d1dc85e..b3bc6bc2 100644 --- a/common/gecko/src/androidMain/kotlin/com/dimension/maskbook/common/gecko/WebContentController.kt +++ b/common/gecko/src/androidMain/kotlin/com/dimension/maskbook/common/gecko/WebContentController.kt @@ -25,6 +25,7 @@ import android.content.Intent import android.net.Uri import android.util.Log import androidx.fragment.app.FragmentActivity +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -34,6 +35,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -91,6 +93,7 @@ private class MessageHolder : MessageHandler { class WebContentController( context: Context, private val scope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, var onNavigate: (String) -> Boolean = { true }, ) : Closeable { private val _browserState = MutableStateFlow(null) @@ -189,12 +192,12 @@ class WebContentController( } } } - }.launchIn(scope) + }.flowOn(dispatcher).launchIn(scope) _browserState.mapNotNull { it?.closedTabs }.onEach { list -> list.forEach { tab -> _contentMessageHolders.value -= tab.id } - }.launchIn(scope) + }.flowOn(dispatcher).launchIn(scope) } ) } @@ -233,7 +236,7 @@ class WebContentController( fun sendContentMessage(message: JSONObject) { Log.i(TAG, "sendContentMessage: $message") - scope.launch { + scope.launch(dispatcher) { _contentMessageHolders.firstOrNull()?.let { holders -> _activeTabId.firstOrNull()?.let { tabId -> holders[tabId]?.sendMessage(message) diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt index 85027aff..81b2a669 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt @@ -26,20 +26,21 @@ import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.mainDispatcher import com.dimension.maskbook.common.di.scope.mainImmediateDispatcher import com.dimension.maskbook.common.util.coroutineExceptionHandler +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import org.koin.core.module.Module -fun Module.coroutinesModule() { +internal fun Module.coroutinesModule() { single(defaultDispatcher) { Dispatchers.Default } single(ioDispatcher) { Dispatchers.IO } - single(mainDispatcher) { Dispatchers.Main } - single(mainImmediateDispatcher) { Dispatchers.Main.immediate } + single(mainDispatcher) { Dispatchers.Main } + single(mainImmediateDispatcher) { Dispatchers.Main.immediate } single(appScope) { CoroutineScope( - coroutineExceptionHandler + SupervisorJob() + Dispatchers.IO + coroutineExceptionHandler + SupervisorJob() ) } } 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 e29e9840..524ffec9 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt @@ -25,6 +25,7 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope +import com.dimension.maskbook.common.di.scope.defaultDispatcher import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.route.Navigator import com.dimension.maskbook.entry.data.JSMethod @@ -45,8 +46,16 @@ object EntrySetup : ModuleSetup { } override fun dependencyInject() = module { - single { EntryRepository(get().entryDataStore, get(appScope)) } - single { JSMethod(get()) } + single { + EntryRepository( + get(appScope), + get(defaultDispatcher), + get().entryDataStore + ) + } + single { + JSMethod(get()) + } } override fun onExtensionReady(koin: Koin) { diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt index d757dfa4..1d3f474a 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt @@ -26,6 +26,7 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -35,8 +36,9 @@ private val ShouldShowEntryKey = booleanPreferencesKey("ShouldShowEntry") val Context.entryDataStore: DataStore by preferencesDataStore(name = "entry") class EntryRepository( + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, private val dataStore: DataStore, - private val scope: CoroutineScope, ) { val shouldShowEntry: Flow get() = dataStore.data.map { @@ -44,7 +46,7 @@ class EntryRepository( } fun setShouldShowEntry(shouldShowEntry: Boolean) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[ShouldShowEntryKey] = shouldShowEntry } 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 19b0c6ad..607dc07d 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -29,6 +29,7 @@ import androidx.navigation.navArgument import androidx.navigation.navDeepLink import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope +import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.ext.navigateToHome import com.dimension.maskbook.common.gecko.WebContentController import com.dimension.maskbook.common.route.Deeplinks @@ -68,11 +69,21 @@ object ExtensionSetup : ModuleSetup { } override fun dependencyInject() = module { - single { WebContentController(get(), get(appScope)) } - single { ExtensionRepository(get(), get(appScope)) } - single { ExtensionServicesImpl(get(), get(), get()) } - single { BackgroundMessageChannel(get(), get(appScope)) } - single { ContentMessageChannel(get(), get(appScope)) } + single { + WebContentController(get(), get(appScope), get(ioDispatcher)) + } + single { + ExtensionRepository(get(appScope), get()) + } + single { + ExtensionServicesImpl(get(), get(), get()) + } + single { + BackgroundMessageChannel(get(), get(appScope), get(ioDispatcher)) + } + single { + ContentMessageChannel(get(), get(appScope), get(ioDispatcher)) + } } override fun onExtensionReady(koin: Koin) { diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt index 39364512..c9fc44a7 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt @@ -34,14 +34,14 @@ import kotlinx.coroutines.launch @OptIn(InternalCoroutinesApi::class) class ExtensionRepository( + private val appScope: CoroutineScope, private val controller: WebContentController, - private val scope: CoroutineScope, ) { private val _currentSite = MutableStateFlow(Site.Twitter) val currentSite = _currentSite.asSharedFlow() fun setCurrentSite(site: Site) { _currentSite.value = site - scope.launch { + appScope.launch { // workaround for this case:set current site to Twitter first, then set current site to facebook, // then go back to twitter tab, currentSite's value is still facebook, if we set current // site to facebook again, _currentSite won't update due to MutableStateFlow won't emit @@ -60,17 +60,15 @@ class ExtensionRepository( controller.onNavigate = { onNavigate(it) } - scope.launch { - launch { - _currentSite.collect { - controller.loadUrl(it.url) - } - } - launch { - isExtensionConnected.first { it } - controller.loadUrl(_currentSite.value.url) + appScope.launch { + _currentSite.collect { + controller.loadUrl(it.url) } } + appScope.launch { + isExtensionConnected.first { it } + controller.loadUrl(_currentSite.value.url) + } } private fun onNavigate(target: String): Boolean { diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/utils/MessageChannel.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/utils/MessageChannel.kt index 624f2510..260a7e13 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/utils/MessageChannel.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/utils/MessageChannel.kt @@ -23,12 +23,14 @@ package com.dimension.maskbook.extension.utils import com.dimension.maskbook.common.gecko.WebContentController import com.dimension.maskbook.extension.export.model.ExtensionId import com.dimension.maskbook.extension.export.model.ExtensionMessage +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.json.JSONObject @@ -38,6 +40,7 @@ import java.util.concurrent.ConcurrentHashMap internal abstract class MessageChannel( private val flow: Flow, private val scope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, ) { private val queue = ConcurrentHashMap>() @@ -47,8 +50,7 @@ internal abstract class MessageChannel( protected abstract fun sendMessage(message: JSONObject) fun startMessageCollect() { - flow.onEach { onMessage(it) } - .launchIn(scope) + flow.onEach(::onMessage).flowOn(dispatcher).launchIn(scope) } fun sendResponseMessage(map: Map) { @@ -87,7 +89,7 @@ internal abstract class MessageChannel( } fun subscribeMessage(vararg method: String): Flow { - return _extensionMessage.filter { it.method in method } + return extensionMessage.filter { it.method in method } } private fun onMessage(jsonObject: JSONObject) { @@ -131,9 +133,11 @@ internal abstract class MessageChannel( internal class BackgroundMessageChannel( private val controller: WebContentController, scope: CoroutineScope, + dispatcher: CoroutineDispatcher, ) : MessageChannel( flow = controller.backgroundMessage, scope = scope, + dispatcher = dispatcher, ) { override fun sendMessage(message: JSONObject) { controller.sendBackgroundMessage(message) @@ -143,9 +147,11 @@ internal class BackgroundMessageChannel( internal class ContentMessageChannel( private val controller: WebContentController, scope: CoroutineScope, + dispatcher: CoroutineDispatcher, ) : MessageChannel( flow = controller.contentMessage, scope = scope, + dispatcher = dispatcher, ) { override fun sendMessage(message: JSONObject) { controller.sendContentMessage(message) 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 94f58b4e..b192f72c 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt @@ -25,6 +25,8 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope +import com.dimension.maskbook.common.di.scope.defaultDispatcher +import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.labs.data.JSMethod import com.dimension.maskbook.labs.data.RedPacketMethod @@ -50,12 +52,29 @@ object LabsSetup : ModuleSetup { } override fun dependencyInject() = module { - single { AppRepository(get(), get(appScope)) } + single { + AppRepository( + get(appScope), + get(ioDispatcher), + get() + ) + } single { - PreferenceRepository(get().labsDataStore, get(appScope)) + PreferenceRepository( + get().labsDataStore, + get(appScope), + get(defaultDispatcher), + ) + } + single { + JSMethod(get()) + } + single { + RedPacketMethod( + get(appScope), + get(defaultDispatcher), get() + ) } - single { JSMethod(get()) } - single { RedPacketMethod(get(appScope), get()) } single { LabsTabScreen() } bind TabScreen::class @@ -66,6 +85,6 @@ object LabsSetup : ModuleSetup { override fun onExtensionReady(koin: Koin) { koin.get().init() - koin.get().startSubscribe() + koin.get().startCollect() } } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/data/RedPacketMethod.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/data/RedPacketMethod.kt index 4a27449c..2d545de7 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/data/RedPacketMethod.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/data/RedPacketMethod.kt @@ -28,16 +28,19 @@ import com.dimension.maskbook.extension.export.ExtensionServices import com.dimension.maskbook.labs.model.SendMethodRequest import com.dimension.maskbook.labs.model.options.RedPacketOptions import com.dimension.maskbook.labs.route.LabsRoute +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach class RedPacketMethod( - private val scope: CoroutineScope, + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, private val services: ExtensionServices, ) { - fun startSubscribe() { + fun startCollect() { services.subscribeCurrentContentJSEvent(notifyRedPacket, claimOrRefundRedPacket) .onEach { message -> when (message.method) { @@ -56,7 +59,8 @@ class RedPacketMethod( } } } - .launchIn(scope) + .flowOn(dispatcher) + .launchIn(appScope) } companion object { diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt index c1ff3da7..0d77e4ad 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt @@ -23,14 +23,16 @@ package com.dimension.maskbook.labs.repository import com.dimension.maskbook.labs.data.JSMethod import com.dimension.maskbook.labs.export.model.AppData import com.dimension.maskbook.labs.export.model.AppKey +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch internal class AppRepository( + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, private val jsMethod: JSMethod, - private val scope: CoroutineScope, ) : IAppRepository { private val _apps = MutableStateFlow( @@ -48,14 +50,14 @@ internal class AppRepository( } override fun setEnabled(appKey: AppKey, enabled: Boolean) { - scope.launch { + appScope.launch(dispatcher) { jsMethod.setPluginStatus(appKey.id, enabled) refreshApps() } } override fun init() { - scope.launch { + appScope.launch(dispatcher) { refreshApps() } } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/PreferenceRepository.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/PreferenceRepository.kt index 44186833..5d535428 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/PreferenceRepository.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/PreferenceRepository.kt @@ -26,6 +26,7 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -36,7 +37,8 @@ val Context.labsDataStore: DataStore by preferencesDataStore(name = class PreferenceRepository( private val dataStore: DataStore, - private val ioScope: CoroutineScope, + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, ) : IPreferenceRepository { override val shouldShowPluginSettingsTipDialog: Flow @@ -45,7 +47,7 @@ class PreferenceRepository( } override fun setShowPluginSettingsTipDialog(bool: Boolean) { - ioScope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[ShouldShowPluginSettingsTipDialog] = bool } 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 c6ad955e..513eea66 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt @@ -28,6 +28,9 @@ import androidx.room.Room import com.dimension.maskbook.common.LocalBackupAccount import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope +import com.dimension.maskbook.common.di.scope.defaultDispatcher +import com.dimension.maskbook.common.di.scope.ioDispatcher +import com.dimension.maskbook.common.di.scope.mainDispatcher import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.persona.data.JSMethod import com.dimension.maskbook.persona.data.JSMethodV2 @@ -78,7 +81,7 @@ import com.dimension.maskbook.persona.viewmodel.register.RemoteBackupRecoveryVie 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.CoroutineDispatcher import kotlinx.coroutines.asExecutor import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.Koin @@ -107,9 +110,10 @@ object PersonaSetup : ModuleSetup { override fun dependencyInject() = module { single { + val executor = get(ioDispatcher).asExecutor() Room.databaseBuilder(get(), PersonaDatabase::class.java, "maskbook_persona") - .setQueryExecutor(Dispatchers.IO.asExecutor()) - .setTransactionExecutor(Dispatchers.IO.asExecutor()) + .setQueryExecutor(executor) + .setTransactionExecutor(executor) .fallbackToDestructiveMigration() .addTypeConverter(EncryptStringConverter(get())) .addTypeConverter(EncryptJsonObjectConverter(get())) @@ -118,6 +122,8 @@ object PersonaSetup : ModuleSetup { single { PersonaRepository( get(appScope), + get(mainDispatcher), + get(ioDispatcher), get(), get(), get(), get(), get(), get(), get(), @@ -130,7 +136,8 @@ object PersonaSetup : ModuleSetup { single { PreferenceRepository( get().personaDataStore, - get(appScope) + get(appScope), + get(defaultDispatcher), ) } @@ -138,6 +145,7 @@ object PersonaSetup : ModuleSetup { single { JSMethodV2( get(appScope), + get(defaultDispatcher), get(), get(), get(), get(), diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/data/JSMethodV2.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/data/JSMethodV2.kt index d9a45259..c3abff32 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/data/JSMethodV2.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/data/JSMethodV2.kt @@ -59,14 +59,17 @@ import com.dimension.maskbook.persona.model.options.UpdateProfileOptions import com.dimension.maskbook.persona.model.options.UpdateRelationOptions import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.persona.repository.IPreferenceRepository +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch class JSMethodV2( - private val scope: CoroutineScope, + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, private val services: ExtensionServices, private val database: PersonaDatabase, private val personaRepository: IPersonaRepository, @@ -77,7 +80,7 @@ class JSMethodV2( private val postDataSource: JsPostDataSource, ) { fun startSubscribe() { - scope.launch { + appScope.launch(dispatcher) { if (preferenceRepository.isMigratorIndexedDb.first()) { return@launch } @@ -98,7 +101,8 @@ class JSMethodV2( subscribeWithPost(it) || subscribeWithHelper(it) } - .launchIn(scope) + .flowOn(dispatcher) + .launchIn(appScope) } // Persona @@ -121,7 +125,8 @@ class JSMethodV2( return message.responseSuccess(personaDataSource.queryPersona(options)) } queryPersonaByProfile -> { - val options = message.decodeOptions>()?.options ?: return true + val options = + message.decodeOptions>()?.options ?: return true return message.responseSuccess(personaDataSource.queryPersonaByProfile(options)) } queryPersonas -> { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt index afd0c98e..f092e0c9 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt @@ -38,8 +38,8 @@ import com.dimension.maskbook.persona.export.model.PersonaData import com.dimension.maskbook.persona.export.model.PlatformType import com.dimension.maskbook.persona.export.model.SocialData import com.dimension.maskbook.persona.model.ContactData +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow @@ -54,7 +54,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext internal class PersonaRepository( - private val scope: CoroutineScope, + private val appScope: CoroutineScope, + private val mainDispatcher: CoroutineDispatcher, + private val ioDispatcher: CoroutineDispatcher, private val jsMethod: JSMethod, private val extensionServices: ExtensionServices, private val preferenceRepository: IPreferenceRepository, @@ -107,17 +109,17 @@ internal class PersonaRepository( .filterNot { personaDataSource.hasConnected(it) } .flatMapLatest { profileDataSource.getSocialFlow(it) } .filterNotNull() - .flowOn(Dispatchers.IO) + .flowOn(ioDispatcher) .onEach { onDone.invoke(ConnectAccountData(personaId, it)) connectingJob?.cancel() } - .flowOn(Dispatchers.Main) - .launchIn(scope) + .flowOn(mainDispatcher) + .launchIn(appScope) } override fun init() { - scope.launch { + appScope.launch(ioDispatcher) { if (personaDataSource.isEmpty()) { return@launch } @@ -133,7 +135,7 @@ internal class PersonaRepository( } override suspend fun setCurrentPersona(id: String) { - withContext(scope.coroutineContext) { + withContext(ioDispatcher) { if (id.isEmpty() || personaDataSource.getPersona(id) != null) { preferenceRepository.setCurrentPersonaIdentifier(id) jsMethod.setCurrentPersonaIdentifier(id) @@ -142,7 +144,7 @@ internal class PersonaRepository( } override suspend fun logout() { - withContext(scope.coroutineContext) { + withContext(ioDispatcher) { val deletePersona = currentPersona.firstOrNull() ?: return@withContext // set current persona first ,avoid currentPersona emmit null if there has other personas val newCurrentPersona = personaDataSource.getPersonaList().firstOrNull { @@ -156,14 +158,14 @@ internal class PersonaRepository( } override fun updatePersona(id: String, nickname: String) { - scope.launch { + appScope.launch(ioDispatcher) { personaDataSource.updateNickName(id, nickname) jsMethod.updatePersonaInfo(id, nickname) } } override fun updateCurrentPersona(nickname: String) { - scope.launch { + appScope.launch(ioDispatcher) { val id = currentPersona.firstOrNull()?.identifier ?: return@launch personaDataSource.updateNickName(id, nickname) jsMethod.updatePersonaInfo(id, nickname) @@ -171,19 +173,19 @@ internal class PersonaRepository( } override fun connectProfile(personaId: String, profileId: String) { - scope.launch { + appScope.launch(ioDispatcher) { jsMethod.connectProfile(personaId, profileId) } } override fun disconnectProfile(personaId: String, profileId: String) { - scope.launch { + appScope.launch(ioDispatcher) { jsMethod.disconnectProfile(profileId) } } override suspend fun createPersonaFromMnemonic(value: List, name: String) { - withContext(scope.coroutineContext) { + withContext(ioDispatcher) { val mnemonic = value.joinToString(" ") if (personaDataSource.containsMnemonic(mnemonic)) { throw PersonaAlreadyExitsError() @@ -193,7 +195,7 @@ internal class PersonaRepository( } override suspend fun createPersonaFromPrivateKey(value: String, name: String) { - withContext(scope.coroutineContext) { + withContext(ioDispatcher) { if (personaDataSource.containsPrivateKey(value)) throw PersonaAlreadyExitsError() jsMethod.restoreFromPrivateKey(privateKey = value, nickname = name) } @@ -208,7 +210,7 @@ internal class PersonaRepository( } override fun setAvatarForCurrentPersona(avatar: Uri?) { - scope.launch { + appScope.launch(ioDispatcher) { currentPersona.firstOrNull()?.let { personaData -> personaDataSource.updateAvatar(personaData.identifier, avatar?.toString()) } diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PreferenceRepository.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PreferenceRepository.kt index 69b49e47..abf047c9 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PreferenceRepository.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PreferenceRepository.kt @@ -27,13 +27,13 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext private val CurrentPersonaKey = stringPreferencesKey("current_persona") private val ShouldShowContactsTipDialog = booleanPreferencesKey("ShouldShowContactsTipDialog") @@ -42,7 +42,8 @@ val Context.personaDataStore: DataStore by preferencesDataStore(nam class PreferenceRepository( private val dataStore: DataStore, - private val ioScope: CoroutineScope, + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, ) : IPreferenceRepository { override val data: Flow @@ -54,10 +55,8 @@ class PreferenceRepository( } override suspend fun setCurrentPersonaIdentifier(identifier: String) { - withContext(ioScope.coroutineContext) { - dataStore.edit { - it[CurrentPersonaKey] = identifier - } + dataStore.edit { + it[CurrentPersonaKey] = identifier } } @@ -67,7 +66,7 @@ class PreferenceRepository( } override fun setShowContactsTipDialog(bool: Boolean) { - ioScope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[ShouldShowContactsTipDialog] = bool } @@ -80,7 +79,7 @@ class PreferenceRepository( } override fun setIsMigratorIndexedDb(bool: Boolean) { - ioScope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[IsMigratorIndexedDb] = bool } 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 197a37fb..ef4ac94e 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt @@ -26,6 +26,8 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.navigation import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope +import com.dimension.maskbook.common.di.scope.defaultDispatcher +import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.retrofit.retrofit import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.setting.data.JSDataSource @@ -57,6 +59,7 @@ import com.dimension.maskbook.setting.viewmodel.PhoneSetupViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.Koin import org.koin.dsl.bind +import org.koin.dsl.binds import org.koin.dsl.module object SettingSetup : ModuleSetup { @@ -75,26 +78,47 @@ object SettingSetup : ModuleSetup { retrofit("https://vaalh28dbi.execute-api.ap-east-1.amazonaws.com") } single { - SettingsRepository(get(), get(), get(), get()) + SettingsRepository( + get(defaultDispatcher), + get(), + get(), + get(), + get() + ) } single { BackupRepository( get(), get().cacheDir, get().contentResolver, - get(appScope) + get(defaultDispatcher), ) } - single { + single { SettingServicesImpl( get(), - get() + get(), ) - } bind com.dimension.maskbook.setting.export.BackupServices::class + } binds arrayOf( + SettingServices::class, + com.dimension.maskbook.setting.export.BackupServices::class, + ) single { SettingsTabScreen() } bind TabScreen::class - single { JSDataSource(get(), get(appScope)) } + single { + JSDataSource( + get(), + get(appScope), + get(ioDispatcher), + ) + } single { JSMethod(get()) } - single { SettingDataSource(get().settingsDataStore, get(appScope)) } + single { + SettingDataSource( + get().settingsDataStore, + get(appScope), + get(defaultDispatcher), + ) + } viewModel { LanguageSettingsViewModel(get()) } viewModel { AppearanceSettingsViewModel(get()) } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt index 2542882e..75af2c25 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt @@ -23,6 +23,7 @@ package com.dimension.maskbook.setting.data import com.dimension.maskbook.setting.export.model.Appearance import com.dimension.maskbook.setting.export.model.DataProvider import com.dimension.maskbook.setting.export.model.Language +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -32,7 +33,8 @@ import kotlinx.coroutines.launch internal class JSDataSource( private val jsMethod: JSMethod, - private val scope: CoroutineScope, + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, ) { private val _appearance = MutableStateFlow(Appearance.default) private val _dataProvider = MutableStateFlow(DataProvider.COIN_GECKO) @@ -43,28 +45,28 @@ internal class JSDataSource( val dataProvider = _dataProvider.asSharedFlow() fun setLanguage(language: Language) { - scope.launch { + appScope.launch(dispatcher) { jsMethod.setLanguage(language) _language.value = jsMethod.getLanguage() } } fun setAppearance(appearance: Appearance) { - scope.launch { + appScope.launch(dispatcher) { jsMethod.setTheme(appearance) _appearance.value = jsMethod.getTheme() } } fun setDataProvider(dataProvider: DataProvider) { - scope.launch { + appScope.launch(dispatcher) { jsMethod.setTrendingDataSource(dataProvider) _dataProvider.value = jsMethod.getTrendingDataSource() } } fun initData() { - scope.launch { + appScope.launch(dispatcher) { awaitAll( async { _language.value = jsMethod.getLanguage() }, async { _appearance.value = jsMethod.getTheme() }, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt index fe4054bd..29a306b7 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt @@ -27,6 +27,7 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -42,7 +43,8 @@ val Context.settingsDataStore: DataStore by preferencesDataStore(na class SettingDataSource( private val dataStore: DataStore, - private val scope: CoroutineScope, + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, ) { val biometricEnabled: Flow get() = dataStore.data.map { @@ -69,7 +71,7 @@ class SettingDataSource( } fun setPaymentPassword(value: String) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[PaymentPasswordKey] = value } @@ -77,7 +79,7 @@ class SettingDataSource( } fun setBackupPassword(value: String) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[BackupPasswordKey] = value } @@ -85,7 +87,7 @@ class SettingDataSource( } fun setShouldShowLegalScene(value: Boolean) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[ShouldShowLegalSceneKey] = value } @@ -93,7 +95,7 @@ class SettingDataSource( } fun setBiometricEnabled(value: Boolean) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[BiometricEnabledKey] = value } @@ -101,7 +103,7 @@ class SettingDataSource( } fun setRegisterEmail(value: String) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[RegisterEmail] = value } @@ -109,7 +111,7 @@ class SettingDataSource( } fun setRegisterPhone(value: String) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[RegisterPhone] = value } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt index ef62d0f0..566a82f3 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt @@ -35,7 +35,7 @@ import com.dimension.maskbook.setting.services.model.Scenario import com.dimension.maskbook.setting.services.model.SendCodeBody import com.dimension.maskbook.setting.services.model.UploadBody import com.dimension.maskbook.setting.services.model.ValidateCodeBody -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext import kotlinx.serialization.builtins.serializer @@ -56,10 +56,10 @@ class BackupRepository( private val backupServices: BackupServices, private val cacheDir: File, private val contentResolver: ContentResolver, - private val scope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, ) { suspend fun sendPhoneCode(phone: String) { - withContext(scope.coroutineContext) { + withContext(dispatcher) { backupServices.sendCode( SendCodeBody( account_type = AccountType.phone, @@ -72,7 +72,7 @@ class BackupRepository( } suspend fun sendEmailCode(email: String) { - withContext(scope.coroutineContext) { + withContext(dispatcher) { backupServices.sendCode( SendCodeBody( account_type = AccountType.email, @@ -85,7 +85,7 @@ class BackupRepository( } suspend fun validatePhoneCode(phone: String, code: String) { - withContext(scope.coroutineContext) { + withContext(dispatcher) { backupServices.validateCode( ValidateCodeBody( code = code, @@ -97,7 +97,7 @@ class BackupRepository( } suspend fun validateEmailCode(email: String, code: String) { - withContext(scope.coroutineContext) { + withContext(dispatcher) { backupServices.validateCode( ValidateCodeBody( code = code, @@ -149,7 +149,7 @@ class BackupRepository( } suspend fun downloadBackupWithEmail(email: String, code: String) = - withContext(scope.coroutineContext) { + withContext(dispatcher) { val response = backupServices.download( ValidateCodeBody( code = code, @@ -167,7 +167,7 @@ class BackupRepository( } suspend fun downloadBackupWithPhone(phone: String, code: String) = - withContext(scope.coroutineContext) { + withContext(dispatcher) { val response = backupServices.download( ValidateCodeBody( code = code, @@ -184,7 +184,7 @@ class BackupRepository( ) } - private suspend fun downloadFile(url: String) = withContext(scope.coroutineContext) { + private suspend fun downloadFile(url: String) = withContext(dispatcher) { val stream = OkHttpClient.Builder() .build() .newCall( @@ -212,7 +212,7 @@ class BackupRepository( account: String, abstract: String, content: BackupMetaFile, - ) = withContext(scope.coroutineContext) { + ) = withContext(dispatcher) { val response = backupServices.upload( UploadBody( code = code, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt index 92743522..7e68512a 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt @@ -40,11 +40,12 @@ import com.dimension.maskbook.setting.model.mapping.toIndexedDBProfile import com.dimension.maskbook.setting.model.mapping.toIndexedDBRelation import com.dimension.maskbook.setting.model.mapping.toWalletData import com.dimension.maskbook.wallet.export.WalletServices -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext internal class SettingsRepository( + private val dispatcher: CoroutineDispatcher, private val personaServices: PersonaServices, private val settingDataSource: SettingDataSource, private val jsDataSource: JSDataSource, @@ -123,7 +124,7 @@ internal class SettingsRepository( } override suspend fun restoreBackup(value: BackupMetaFile) { - withContext(Dispatchers.IO) { + withContext(dispatcher) { val persona = value.personas.map { it.toIndexedDBPersona() } personaServices.restorePersonaBackup(persona) val profile = value.profiles.map { it.toIndexedDBProfile() } @@ -145,7 +146,7 @@ internal class SettingsRepository( noRelations: Boolean, hasPrivateKeyOnly: Boolean ): BackupMetaFile { - return withContext(Dispatchers.IO) { + return withContext(dispatcher) { val personas = if (noPersonas) { emptyList() } else { 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 23037705..2648589e 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt @@ -113,7 +113,6 @@ import com.dimension.maskbook.wallet.walletconnect.v1.server.WalletConnectServer import com.google.accompanist.navigation.animation.navigation import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.dsl.viewModel @@ -144,9 +143,10 @@ object WalletSetup : ModuleSetup { override fun dependencyInject() = module { single { + val executor = get(ioDispatcher).asExecutor() Room.databaseBuilder(get(), AppDatabase::class.java, "maskbook") - .setQueryExecutor(Dispatchers.IO.asExecutor()) - .setTransactionExecutor(Dispatchers.IO.asExecutor()) + .setQueryExecutor(executor) + .setTransactionExecutor(executor) .addMigrations( RoomMigrations.MIGRATION_6_7, RoomMigrations.MIGRATION_7_8, @@ -203,18 +203,25 @@ private fun initEvent(koin: Koin) { } private fun initRepository(koin: Koin) { - koin.get().init() - koin.get().init() + val scope = koin.get(appScope) + val dispatcher = koin.get(ioDispatcher) + + scope.launch(dispatcher) { + koin.get().init() + } + scope.launch(dispatcher) { + koin.get().init() + } } private fun initWalletConnect(koin: Koin) { val appScope = koin.get(appScope) - val ioDispatcher = koin.get(ioDispatcher) + val dispatcher = koin.get(ioDispatcher) val walletRepository = koin.get() koin.get() .initSessions { address -> - appScope.launch(ioDispatcher) { + appScope.launch(dispatcher) { walletRepository.findWalletByAddress(address)?.let { wallet -> walletRepository.deleteWallet(wallet.id) } @@ -237,21 +244,22 @@ private fun Module.provideRepository() { single { WalletRepository( get().walletDataStore, + get(appScope), + get(ioDispatcher), get(), get(), get(), get(), - get(appScope) ) } single { JSMethod(get()) } - single { Web3MessageHandler(get(), get(appScope)) } + single { Web3MessageHandler(get()) } single { CollectibleRepository(get(), get()) } single { TransactionRepository(get(), get()) } single { TokenRepository(get()) } - single { SendHistoryRepository(get(), get(appScope)) } - single { WalletContactRepository(get(), get(appScope)) } - single { WalletConnectRepository(get(), get(), get(appScope)) } + single { SendHistoryRepository(get(), get(ioDispatcher)) } + single { WalletContactRepository(get(), get(ioDispatcher)) } + single { WalletConnectRepository(get(), get()) } } private fun Module.provideUseCase() { diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/handler/Web3MessageHandler.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/handler/Web3MessageHandler.kt index a84e0eca..9ed7390a 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/handler/Web3MessageHandler.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/handler/Web3MessageHandler.kt @@ -36,147 +36,145 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.NullNode import com.fasterxml.jackson.databind.node.ObjectNode -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.launch import org.web3j.protocol.core.Request import org.web3j.protocol.core.Response internal class Web3MessageHandler( private val walletRepository: IWalletRepository, - private val scope: CoroutineScope, ) { - fun handle(request: Web3Request) { - scope.launch { - val payload = request.payload - if (payload?.id != null) { - when (payload.method) { - "getRPCurl" -> { - walletRepository.dWebData.firstOrNull()?.chainType?.endpoint?.let { - request.message.response( - Web3SendResponse.success(request, mapOf("rpcURL" to it)) - ) - } - } - "eth_coinbase" -> { - val address = walletRepository.currentWallet.firstOrNull()?.address ?: "" + suspend fun handle(request: Web3Request) { + val payload = request.payload + if (payload?.id != null) { + when (payload.method) { + "getRPCurl" -> { + walletRepository.dWebData.firstOrNull()?.chainType?.endpoint?.let { request.message.response( - Web3SendResponse.success( - request, - mapOf("coinbase" to address) - ) + Web3SendResponse.success(request, mapOf("rpcURL" to it)) ) } - "eth_getAccounts", "eth_accounts" -> { - val address = walletRepository.currentWallet.firstOrNull()?.address - request.message.response( - Web3SendResponse.success( - request, - listOfNotNull(address) - ) + } + "eth_coinbase" -> { + val address = walletRepository.currentWallet.firstOrNull()?.address ?: "" + request.message.response( + Web3SendResponse.success( + request, + mapOf("coinbase" to address) ) - } - "eth_sendTransaction" -> { - val dataRaw = payload.params.firstOrNull() - ?.decodeJson() - ?.encodeJson() ?: return@launch - val requestRaw = SendTokenRequest( - messageId = request.id, - payloadId = request.payload.id, - jsonrpc = request.payload.jsonrpc, - ).encodeJson() - Navigator.navigate(WalletRoute.SendTokenConfirm(dataRaw, requestRaw)) - } - "personal_sign" -> { - val message = payload.params.getOrNull(0)?.normalized as? String - ?: return@launch - val fromAddress = payload.params.getOrNull(1)?.normalized as? String - ?: return@launch - val hex = walletRepository.signMessage(message, fromAddress) - ?: return@launch - request.message.response( - Web3SendResponse.success( - request, - listOfNotNull(hex) - ) + ) + } + "eth_getAccounts", "eth_accounts" -> { + val address = walletRepository.currentWallet.firstOrNull()?.address + request.message.response( + Web3SendResponse.success( + request, + listOfNotNull(address) ) - } - else -> { - val method = payload.method - val chainType = - walletRepository.dWebData.firstOrNull()?.chainType ?: return@launch - val service = chainType.httpService - try { - val response = service.send( - Request( - method, - payload.params.map { it.normalized }, - service, - JsonResponse::class.java - ), + ) + } + "eth_sendTransaction" -> { + val dataRaw = payload.params.firstOrNull() + ?.decodeJson() + ?.encodeJson() ?: return + val requestRaw = SendTokenRequest( + messageId = request.id, + payloadId = request.payload.id, + jsonrpc = request.payload.jsonrpc, + ).encodeJson() + Navigator.navigate(WalletRoute.SendTokenConfirm(dataRaw, requestRaw)) + } + "personal_sign" -> { + val message = payload.params.getOrNull(0)?.normalized as? String + ?: return + val fromAddress = payload.params.getOrNull(1)?.normalized as? String + ?: return + val hex = walletRepository.signMessage(message, fromAddress) + ?: return + request.message.response( + Web3SendResponse.success( + request, + listOfNotNull(hex) + ) + ) + } + else -> { + val method = payload.method + val chainType = + walletRepository.dWebData.firstOrNull()?.chainType ?: return + val service = chainType.httpService + try { + val response = service.send( + Request( + method, + payload.params.map { it.normalized }, + service, JsonResponse::class.java - ) - val result = response?.result - if (result == null) { - request.message.response( - Web3SendResponse.error( - request, - "No response" - ) - ) - } else { - request.message.response( - Web3SendResponse.success( - request, - when (result) { - is NullNode -> null - is ObjectNode -> ObjectMapper().convertValue( - result, - object : TypeReference>() {}, - ) - is ArrayNode -> ObjectMapper().convertValue( - result, - object : TypeReference>() {}, - ) - else -> result.asText() - } - ) - ) - } - } catch (e: Throwable) { - e.printStackTrace() + ), + JsonResponse::class.java + ) + val result = response?.result + if (result == null) { request.message.response( Web3SendResponse.error( request, - e.message ?: "error" + "No response" + ) + ) + } else { + request.message.response( + Web3SendResponse.success( + request, + when (result) { + is NullNode -> null + is ObjectNode -> ObjectMapper().convertValue( + result, + object : TypeReference>() {}, + ) + is ArrayNode -> ObjectMapper().convertValue( + result, + object : TypeReference>() {}, + ) + else -> result.asText() + } ) ) } + } catch (e: Throwable) { + e.printStackTrace() + request.message.response( + Web3SendResponse.error( + request, + e.message ?: "error" + ) + ) } } } } } -} -private inline fun Web3SendResponse.Companion.success(request: Web3Request, result: T?): Map { - requireNotNull(request.payload) - return success( - messageId = request.id, - jsonrpc = request.payload.jsonrpc, - payloadId = request.payload.id, - result = result - ) -} + private inline fun Web3SendResponse.Companion.success( + request: Web3Request, + result: T? + ): Map { + requireNotNull(request.payload) + return success( + messageId = request.id, + jsonrpc = request.payload.jsonrpc, + payloadId = request.payload.id, + result = result + ) + } -private fun Web3SendResponse.Companion.error(request: Web3Request, error: String): Map { - requireNotNull(request.payload) - return error( - messageId = request.id, - jsonrpc = request.payload.jsonrpc, - payloadId = request.payload.id, - error = error - ) -} + private fun Web3SendResponse.Companion.error(request: Web3Request, error: String): Map { + requireNotNull(request.payload) + return error( + messageId = request.id, + jsonrpc = request.payload.jsonrpc, + payloadId = request.payload.id, + error = error + ) + } -class JsonResponse : Response() + class JsonResponse : Response() +} diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/ISendHistoryRepository.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/ISendHistoryRepository.kt index 56eda066..4d9d89be 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/ISendHistoryRepository.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/ISendHistoryRepository.kt @@ -22,7 +22,7 @@ package com.dimension.maskbook.wallet.repository import com.dimension.maskbook.wallet.db.AppDatabase import com.dimension.maskbook.wallet.db.model.DbSendHistory -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest @@ -40,7 +40,7 @@ interface ISendHistoryRepository { class SendHistoryRepository( private val database: AppDatabase, - private val scope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, ) : ISendHistoryRepository { override val recent: Flow> get() = database.sendHistoryDao().getAll().map { list -> @@ -49,7 +49,7 @@ class SendHistoryRepository( } override suspend fun addOrUpdate(address: String, name: String) { - withContext(scope.coroutineContext) { + withContext(dispatcher) { with(database.sendHistoryDao()) { val currentTime = System.currentTimeMillis() if (contains(address) > 0) { diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletConnectRepository.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletConnectRepository.kt index 2ca36ca7..7ce3b25c 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletConnectRepository.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletConnectRepository.kt @@ -33,12 +33,10 @@ import com.dimension.maskbook.wallet.export.model.ChainType import com.dimension.maskbook.wallet.services.WalletServices import com.dimension.maskbook.wallet.services.model.WCSupportedWallet import com.dimension.maskbook.wallet.walletconnect.WCResponder -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch import java.util.UUID data class WCWallet( @@ -89,7 +87,7 @@ data class WCWallet( interface IWalletConnectRepository { val supportedWallets: Flow> - fun init() + suspend fun init() // returns id of first wallet suspend fun saveAccounts(responder: WCResponder, platformType: CoinPlatformType): String? } @@ -97,19 +95,16 @@ interface IWalletConnectRepository { class WalletConnectRepository( private val walletServices: WalletServices, private val database: AppDatabase, - private val scope: CoroutineScope, ) : IWalletConnectRepository { - override fun init() { - scope.launch { - try { - refreshSupportedWallets() - } catch (e: Throwable) { - if (BuildConfig.DEBUG) e.printStackTrace() - // retry - delay(30000) - refreshSupportedWallets() - } + override suspend fun init() { + try { + refreshSupportedWallets() + } catch (e: Throwable) { + if (BuildConfig.DEBUG) e.printStackTrace() + // retry + delay(30000) + refreshSupportedWallets() } } diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletRepository.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletRepository.kt index d7647b87..e1eeeaf8 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletRepository.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/IWalletRepository.kt @@ -159,7 +159,7 @@ data class DWebData( ) interface IWalletRepository { - fun init() + suspend fun init() val dWebData: Flow fun setActiveCoinPlatformType(platformType: CoinPlatformType) fun setChainType(networkType: ChainType, notifyJS: Boolean = true) diff --git a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletContactRepository.kt b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletContactRepository.kt index bc60a7b2..66d66086 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletContactRepository.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/repository/WalletContactRepository.kt @@ -23,7 +23,7 @@ package com.dimension.maskbook.wallet.repository import com.dimension.maskbook.wallet.db.AppDatabase import com.dimension.maskbook.wallet.db.model.DbSendHistoryWithContact import com.dimension.maskbook.wallet.db.model.DbWalletContact -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext @@ -62,14 +62,14 @@ interface IWalletContactRepository { class WalletContactRepository( private val database: AppDatabase, - private val scope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, ) : IWalletContactRepository { override val contacts: Flow> get() = database.walletContactDao().getAll().map { it.map { SearchAddressData.fromDb(it) } } override suspend fun addOrUpdate(address: String, name: String) { - withContext(scope.coroutineContext) { + withContext(dispatcher) { val item = database.walletContactDao().getByAddress(address = address)?.copy(name = name) ?: DbWalletContact(UUID.randomUUID().toString(), name, address) database.walletContactDao().add(listOf(item)) 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 393d4d39..0bc10e23 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 @@ -66,8 +66,8 @@ import com.dimension.maskbook.wallet.services.WalletServices import com.dimension.maskbook.wallet.walletconnect.WalletConnectClientManager import com.dimension.maskwalletcore.CoinType import com.dimension.maskwalletcore.WalletKey +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -91,7 +91,6 @@ import java.math.BigInteger import java.util.UUID import kotlin.math.pow import kotlin.time.Duration.Companion.seconds -import kotlin.time.ExperimentalTime private val CurrentCoinPlatformTypeKey = stringPreferencesKey("coin_platform_type") private val CurrentWalletKey = stringPreferencesKey("current_wallet") @@ -111,21 +110,19 @@ private fun Token.toDbToken(chainId: ChainID?) = DbToken( internal class WalletRepository( private val dataStore: DataStore, + private val appScope: CoroutineScope, + private val dispatcher: CoroutineDispatcher, private val database: AppDatabase, private val services: WalletServices, private val walletConnectManager: WalletConnectClientManager, private val jsMethod: JSMethod, - private val scope: CoroutineScope, ) : IWalletRepository { - @OptIn(ExperimentalTime::class) - override fun init() { - scope.launch { - refreshChainData() - while (true) { - delay(12.seconds) - refreshWallet() - } + override suspend fun init() { + refreshChainData() + while (true) { + delay(12.seconds) + refreshWallet() } } @@ -142,7 +139,7 @@ internal class WalletRepository( } override fun setActiveCoinPlatformType(platformType: CoinPlatformType) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[CurrentCoinPlatformTypeKey] = platformType.name } @@ -150,7 +147,7 @@ internal class WalletRepository( } override fun setChainType(networkType: ChainType, notifyJS: Boolean) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[ChainTypeKey] = networkType.name } @@ -349,7 +346,7 @@ internal class WalletRepository( } override fun setCurrentWallet(walletId: String) { - scope.launch { + appScope.launch(dispatcher) { database.walletDao().getById(walletId)?.let { setCurrentWallet(it.wallet) } @@ -357,7 +354,7 @@ internal class WalletRepository( } fun setCurrentWallet(dbWallet: DbWallet?) { - scope.launch { + appScope.launch(dispatcher) { dataStore.edit { it[CurrentWalletKey] = dbWallet?.id.orEmpty() } @@ -412,7 +409,7 @@ internal class WalletRepository( path: List, platformType: CoinPlatformType, ) { - scope.launch { + appScope.launch(dispatcher) { val wallet = WalletKey.fromMnemonic(mnemonic = mnemonicCode.joinToString(" "), "") val accounts = path.map { wallet.addNewAccountAtPath(platformType.coinType, it, name, "") @@ -494,7 +491,7 @@ internal class WalletRepository( privateKey: String, platformType: CoinPlatformType, ) { - scope.launch { + appScope.launch(dispatcher) { val wallet = WalletKey.fromPrivateKey( privateKey = privateKey, name = name, @@ -575,14 +572,14 @@ internal class WalletRepository( } override fun deleteCurrentWallet() { - scope.launch { + appScope.launch(dispatcher) { val currentWallet = currentWallet.firstOrNull() ?: return@launch deleteWallet(currentWallet.id) } } override fun deleteWallet(id: String) { - scope.launch { + appScope.launch(dispatcher) { // get it before remove val currentWallet = currentWallet.firstOrNull() @@ -600,7 +597,7 @@ internal class WalletRepository( } override fun renameWallet(value: String, id: String) { - scope.launch { + appScope.launch(dispatcher) { database.walletDao().getById(id)?.wallet?.copy(name = value)?.let { database.walletDao().add(listOf(it)) } @@ -608,7 +605,7 @@ internal class WalletRepository( } override fun renameCurrentWallet(value: String) { - scope.launch { + appScope.launch(dispatcher) { currentWallet.firstOrNull()?.let { wallet -> database.walletDao().getById(wallet.id)?.wallet }?.copy(name = value)?.let { @@ -633,7 +630,7 @@ internal class WalletRepository( onDone: (String?) -> Unit, onError: (Throwable) -> Unit ) { - scope.launch { + appScope.launch(dispatcher) { currentWallet.firstOrNull()?.let { wallet -> val data = when (collectible.contract.schema) { CollectibleContractSchema.ERC721 -> listOf( @@ -684,7 +681,7 @@ internal class WalletRepository( onDone: (String?) -> Unit, onError: (Throwable) -> Unit, ) { - scope.launch { + appScope.launch(dispatcher) { val wallet = currentWallet.filterNotNull().first() if (wallet.fromWalletConnect) { walletConnectManager.sendToken( @@ -742,7 +739,7 @@ internal class WalletRepository( onDone: (String?) -> Unit, onError: (Throwable) -> Unit, ) { - scope.launch { + appScope.launch(dispatcher) { val isNativeToken = tokenData.address == database.chainDao().getByIdFlow(tokenData.chainType.chainId) .firstOrNull()?.token?.address @@ -800,7 +797,7 @@ internal class WalletRepository( } override suspend fun getEnsAddress(chainType: ChainType, name: String): String { - return withContext(Dispatchers.IO) { + return withContext(dispatcher) { val web3 = Web3j.build(chainType.httpService) EnsResolver(web3).resolve(name).apply { web3.shutdown() @@ -825,7 +822,7 @@ internal class WalletRepository( } override suspend fun refreshWallet() { - withContext(scope.coroutineContext) { + withContext(dispatcher) { refreshCurrentWalletToken() refreshCurrentWalletCollectibles() refreshNativeTokens() From 183a598270056172c5db27d986ccbe96b7edd850 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 19 Apr 2022 15:15:55 +0800 Subject: [PATCH 4/7] reduce appScope and power coroutineContext --- .../common/gecko/sample/MainActivity.kt | 4 +- .../common/di/module/CoroutinesModule.kt | 10 ++ .../common/di/scope/CoroutinesQualifiers.kt | 3 + .../dimension/maskbook/entry/EntrySetup.kt | 9 +- .../entry/repository/EntryRepository.kt | 12 +- .../maskbook/entry/ui/scene/IntroScene.kt | 10 +- .../entry/viewModel/IntroViewModel.kt | 39 ++++++ .../extension/export/ExtensionServices.kt | 2 +- .../extension/ExtensionServicesImpl.kt | 2 +- .../maskbook/extension/ExtensionSetup.kt | 23 +++- .../repository/ExtensionRepository.kt | 35 +++--- .../extension/utils/MessageChannel.kt | 19 +-- .../com/dimension/maskbook/labs/LabsSetup.kt | 35 +++--- .../maskbook/labs/data/RedPacketMethod.kt | 43 +++---- .../maskbook/labs/repository/AppRepository.kt | 19 +-- .../labs/repository/IAppRepository.kt | 4 +- .../labs/repository/IPreferenceRepository.kt | 2 +- .../labs/repository/PreferenceRepository.kt | 12 +- .../maskbook/labs/viewmodel/LabsViewModel.kt | 11 +- .../labs/viewmodel/PluginSettingsViewModel.kt | 14 ++- .../maskbook/persona/PersonaSetup.kt | 19 ++- .../maskbook/persona/data/JSMethodV2.kt | 48 +++----- .../persona/repository/IPersonaRepository.kt | 2 +- .../repository/IPreferenceRepository.kt | 4 +- .../persona/repository/PersonaRepository.kt | 30 ++--- .../repository/PreferenceRepository.kt | 16 ++- .../setting/export/SettingServices.kt | 6 +- .../maskbook/setting/SettingServicesImpl.kt | 7 +- .../maskbook/setting/SettingSetup.kt | 29 +++-- .../maskbook/setting/data/JSDataSource.kt | 24 ++-- .../setting/data/SettingDataSource.kt | 32 +++-- .../setting/repository/ISettingsRepository.kt | 20 +-- .../setting/repository/SettingsRepository.kt | 114 +++++++++--------- .../viewmodel/AppearanceSettingsViewModel.kt | 7 +- .../BackupPasswordSettingsViewModel.kt | 7 +- .../viewmodel/DataSourceSettingsViewModel.kt | 7 +- .../viewmodel/LanguageSettingsViewModel.kt | 7 +- .../PaymentPasswordSettingsViewModel.kt | 7 +- 38 files changed, 374 insertions(+), 320 deletions(-) create mode 100644 entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt diff --git a/common/gecko/sample/src/main/java/com/dimension/maskbook/common/gecko/sample/MainActivity.kt b/common/gecko/sample/src/main/java/com/dimension/maskbook/common/gecko/sample/MainActivity.kt index 65223a7a..6383141d 100644 --- a/common/gecko/sample/src/main/java/com/dimension/maskbook/common/gecko/sample/MainActivity.kt +++ b/common/gecko/sample/src/main/java/com/dimension/maskbook/common/gecko/sample/MainActivity.kt @@ -43,16 +43,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope import com.dimension.maskbook.common.gecko.WebContent import com.dimension.maskbook.common.gecko.WebContentController -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers class MainActivity : FragmentActivity() { lateinit var controller: WebContentController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - controller = WebContentController(this, CoroutineScope(Dispatchers.IO)).apply { + controller = WebContentController(this, lifecycleScope, Dispatchers.IO).apply { installExtensions( "borderify@mozilla.org", "resource://android/assets/extensions/borderify/" diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt index 81b2a669..9476836d 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt @@ -25,10 +25,13 @@ import com.dimension.maskbook.common.di.scope.defaultDispatcher import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.mainDispatcher import com.dimension.maskbook.common.di.scope.mainImmediateDispatcher +import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext +import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.util.coroutineExceptionHandler import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.SupervisorJob import org.koin.core.module.Module @@ -38,6 +41,13 @@ internal fun Module.coroutinesModule() { single(mainDispatcher) { Dispatchers.Main } single(mainImmediateDispatcher) { Dispatchers.Main.immediate } + single(preferenceCoroutineContext) { + NonCancellable + Dispatchers.Default + } + single(viewModelCoroutineContext) { + coroutineExceptionHandler + Dispatchers.Default + } + single(appScope) { CoroutineScope( coroutineExceptionHandler + SupervisorJob() diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt index 0279fdd3..d1f6ced4 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt @@ -27,4 +27,7 @@ val ioDispatcher = named("IoDispatcher") val mainDispatcher = named("MainDispatcher") val mainImmediateDispatcher = named("MainImmediateDispatcher") +val preferenceCoroutineContext = defaultDispatcher +val viewModelCoroutineContext = defaultDispatcher + val appScope = named("AppScope") 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 524ffec9..662d3284 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt @@ -25,18 +25,21 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope -import com.dimension.maskbook.common.di.scope.defaultDispatcher import com.dimension.maskbook.common.di.scope.ioDispatcher +import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext +import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.route.Navigator import com.dimension.maskbook.entry.data.JSMethod import com.dimension.maskbook.entry.repository.EntryRepository import com.dimension.maskbook.entry.repository.entryDataStore import com.dimension.maskbook.entry.ui.scene.generatedRoute +import com.dimension.maskbook.entry.viewModel.IntroViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.Koin import org.koin.dsl.module @@ -48,14 +51,14 @@ object EntrySetup : ModuleSetup { override fun dependencyInject() = module { single { EntryRepository( - get(appScope), - get(defaultDispatcher), + get(preferenceCoroutineContext), get().entryDataStore ) } single { JSMethod(get()) } + viewModel { IntroViewModel(get(viewModelCoroutineContext), get()) } } override fun onExtensionReady(koin: Koin) { diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt index 1d3f474a..f6fa28a2 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt @@ -26,18 +26,16 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext private val ShouldShowEntryKey = booleanPreferencesKey("ShouldShowEntry") val Context.entryDataStore: DataStore by preferencesDataStore(name = "entry") class EntryRepository( - private val appScope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, + private val preferenceCoroutineScope: CoroutineContext, private val dataStore: DataStore, ) { val shouldShowEntry: Flow @@ -45,8 +43,8 @@ class EntryRepository( it[ShouldShowEntryKey] ?: true } - fun setShouldShowEntry(shouldShowEntry: Boolean) { - appScope.launch(dispatcher) { + suspend fun setShouldShowEntry(shouldShowEntry: Boolean) { + withContext(preferenceCoroutineScope) { dataStore.edit { it[ShouldShowEntryKey] = shouldShowEntry } 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..14b7c82f 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 @@ -67,8 +67,8 @@ import com.dimension.maskbook.common.route.navigationComposeAnimComposable import com.dimension.maskbook.common.route.navigationComposeAnimComposablePackage import com.dimension.maskbook.common.routeProcessor.annotations.NavGraphDestination import com.dimension.maskbook.entry.R -import com.dimension.maskbook.entry.repository.EntryRepository import com.dimension.maskbook.entry.route.EntryRoute +import com.dimension.maskbook.entry.viewModel.IntroViewModel import com.dimension.maskbook.persona.route.PersonaRoute import com.google.accompanist.insets.navigationBarsPadding import com.google.accompanist.insets.statusBarsPadding @@ -76,7 +76,7 @@ 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 org.koin.androidx.compose.getViewModel private data class IntroData( @DrawableRes val img: Int, @@ -93,7 +93,7 @@ private data class IntroData( fun IntroScene( navController: NavController, ) { - val repository = get() + val viewModel = getViewModel() val introList = remember { listOf( IntroData( @@ -136,7 +136,7 @@ fun IntroScene( item = introList[page], isEnd = isEnd, onStartClick = { - repository.setShouldShowEntry(false) + viewModel.setShouldShowEntry(false) navController.navigate(PersonaRoute.Register.Init) { popUpTo(EntryRoute.Intro) { inclusive = true @@ -147,7 +147,7 @@ fun IntroScene( if (!isEnd) { TextButton( onClick = { - repository.setShouldShowEntry(false) + viewModel.setShouldShowEntry(false) navController.navigate(PersonaRoute.Register.Init) { popUpTo(EntryRoute.Intro) { inclusive = true diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt new file mode 100644 index 00000000..e620cce8 --- /dev/null +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt @@ -0,0 +1,39 @@ +/* + * 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.entry.viewModel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.dimension.maskbook.entry.repository.EntryRepository +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext + +class IntroViewModel( + private val viewModelCoroutineContext: CoroutineContext, + private val repository: EntryRepository, +) : ViewModel() { + + fun setShouldShowEntry(value: Boolean) { + viewModelScope.launch(viewModelCoroutineContext) { + repository.setShouldShowEntry(value) + } + } +} diff --git a/extension/export/src/commonMain/kotlin/com/dimension/maskbook/extension/export/ExtensionServices.kt b/extension/export/src/commonMain/kotlin/com/dimension/maskbook/extension/export/ExtensionServices.kt index 4b17bca6..80b8cd38 100644 --- a/extension/export/src/commonMain/kotlin/com/dimension/maskbook/extension/export/ExtensionServices.kt +++ b/extension/export/src/commonMain/kotlin/com/dimension/maskbook/extension/export/ExtensionServices.kt @@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.Flow interface ExtensionServices { val site: Flow - fun setSite(site: Site) + suspend fun setSite(site: Site) val isExtensionActive: Flow suspend fun ensureExtensionActive() suspend fun runBackgroundJSMethod(method: String, isWait: Boolean, vararg args: Pair): String? diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionServicesImpl.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionServicesImpl.kt index bbb17572..87c5c69c 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionServicesImpl.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionServicesImpl.kt @@ -37,7 +37,7 @@ internal class ExtensionServicesImpl( override val site: Flow get() = repository.currentSite - override fun setSite(site: Site) { + override suspend fun setSite(site: Site) { repository.setCurrentSite(site) } 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 607dc07d..c0faa154 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -41,6 +41,9 @@ 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 kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.koin.core.Koin import org.koin.dsl.module @@ -73,21 +76,31 @@ object ExtensionSetup : ModuleSetup { WebContentController(get(), get(appScope), get(ioDispatcher)) } single { - ExtensionRepository(get(appScope), get()) + ExtensionRepository(get()) } single { ExtensionServicesImpl(get(), get(), get()) } single { - BackgroundMessageChannel(get(), get(appScope), get(ioDispatcher)) + BackgroundMessageChannel(get()) } single { - ContentMessageChannel(get(), get(appScope), get(ioDispatcher)) + ContentMessageChannel(get()) } } override fun onExtensionReady(koin: Koin) { - koin.get().startMessageCollect() - koin.get().startMessageCollect() + val appScope = koin.get(appScope) + val dispatcher = koin.get(ioDispatcher) + + appScope.launch(dispatcher) { + koin.get().startMessageCollect() + } + appScope.launch(dispatcher) { + koin.get().startMessageCollect() + } + appScope.launch(dispatcher) { + koin.get().startCollect() + } } } diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt index c9fc44a7..fa441bb4 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt @@ -24,31 +24,26 @@ import com.dimension.maskbook.common.gecko.WebContentController import com.dimension.maskbook.extension.export.model.Site import com.dimension.maskbook.extension.ext.site import com.dimension.maskbook.extension.ext.url -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.launch @OptIn(InternalCoroutinesApi::class) class ExtensionRepository( - private val appScope: CoroutineScope, private val controller: WebContentController, ) { private val _currentSite = MutableStateFlow(Site.Twitter) val currentSite = _currentSite.asSharedFlow() - fun setCurrentSite(site: Site) { + suspend fun setCurrentSite(site: Site) { _currentSite.value = site - appScope.launch { - // workaround for this case:set current site to Twitter first, then set current site to facebook, - // then go back to twitter tab, currentSite's value is still facebook, if we set current - // site to facebook again, _currentSite won't update due to MutableStateFlow won't emit - // same value twice - if (controller.url.firstOrNull()?.site != _currentSite.value) { - controller.loadUrl(_currentSite.value.url) - } + // workaround for this case:set current site to Twitter first, then set current site to facebook, + // then go back to twitter tab, currentSite's value is still facebook, if we set current + // site to facebook again, _currentSite won't update due to MutableStateFlow won't emit + // same value twice + if (controller.url.firstOrNull()?.site != _currentSite.value) { + controller.loadUrl(_currentSite.value.url) } } val isExtensionConnected = controller.isExtensionConnected @@ -60,14 +55,14 @@ class ExtensionRepository( controller.onNavigate = { onNavigate(it) } - appScope.launch { - _currentSite.collect { - controller.loadUrl(it.url) - } - } - appScope.launch { - isExtensionConnected.first { it } - controller.loadUrl(_currentSite.value.url) + } + + suspend fun startCollect() { + isExtensionConnected.first { it } + controller.loadUrl(_currentSite.value.url) + + _currentSite.collect { + controller.loadUrl(it.url) } } diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/utils/MessageChannel.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/utils/MessageChannel.kt index 260a7e13..1e382c63 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/utils/MessageChannel.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/utils/MessageChannel.kt @@ -23,24 +23,17 @@ package com.dimension.maskbook.extension.utils import com.dimension.maskbook.common.gecko.WebContentController import com.dimension.maskbook.extension.export.model.ExtensionId import com.dimension.maskbook.extension.export.model.ExtensionMessage -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import org.json.JSONObject import java.util.UUID import java.util.concurrent.ConcurrentHashMap internal abstract class MessageChannel( private val flow: Flow, - private val scope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, ) { private val queue = ConcurrentHashMap>() @@ -49,8 +42,8 @@ internal abstract class MessageChannel( protected abstract fun sendMessage(message: JSONObject) - fun startMessageCollect() { - flow.onEach(::onMessage).flowOn(dispatcher).launchIn(scope) + suspend fun startMessageCollect() { + flow.collect { onMessage(it) } } fun sendResponseMessage(map: Map) { @@ -132,12 +125,8 @@ internal abstract class MessageChannel( internal class BackgroundMessageChannel( private val controller: WebContentController, - scope: CoroutineScope, - dispatcher: CoroutineDispatcher, ) : MessageChannel( flow = controller.backgroundMessage, - scope = scope, - dispatcher = dispatcher, ) { override fun sendMessage(message: JSONObject) { controller.sendBackgroundMessage(message) @@ -146,12 +135,8 @@ internal class BackgroundMessageChannel( internal class ContentMessageChannel( private val controller: WebContentController, - scope: CoroutineScope, - dispatcher: CoroutineDispatcher, ) : MessageChannel( flow = controller.contentMessage, - scope = scope, - dispatcher = dispatcher, ) { override fun sendMessage(message: JSONObject) { controller.sendContentMessage(message) 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 b192f72c..05c09e4d 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt @@ -25,8 +25,9 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope -import com.dimension.maskbook.common.di.scope.defaultDispatcher import com.dimension.maskbook.common.di.scope.ioDispatcher +import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext +import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.labs.data.JSMethod import com.dimension.maskbook.labs.data.RedPacketMethod @@ -40,6 +41,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 kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.Koin import org.koin.dsl.bind @@ -53,38 +57,37 @@ object LabsSetup : ModuleSetup { override fun dependencyInject() = module { single { - AppRepository( - get(appScope), - get(ioDispatcher), - get() - ) + AppRepository(get()) } single { PreferenceRepository( get().labsDataStore, - get(appScope), - get(defaultDispatcher), + get(preferenceCoroutineContext), ) } single { JSMethod(get()) } single { - RedPacketMethod( - get(appScope), - get(defaultDispatcher), get() - ) + RedPacketMethod(get()) } single { LabsTabScreen() } bind TabScreen::class - viewModel { LabsViewModel(get(), get()) } - viewModel { PluginSettingsViewModel(get(), get(), get()) } + viewModel { LabsViewModel(get(viewModelCoroutineContext), get(), get()) } + viewModel { PluginSettingsViewModel(get(viewModelCoroutineContext), get(), get(), get()) } viewModel { (dataRaw: String, requestRaw: String?) -> LuckDropViewModel(dataRaw, requestRaw, get(), get()) } } override fun onExtensionReady(koin: Koin) { - koin.get().init() - koin.get().startCollect() + val appScope = koin.get(appScope) + val dispatcher = koin.get(ioDispatcher) + + appScope.launch(dispatcher) { + koin.get().init() + } + appScope.launch(dispatcher) { + koin.get().startCollect() + } } } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/data/RedPacketMethod.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/data/RedPacketMethod.kt index 2d545de7..6fd4459f 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/data/RedPacketMethod.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/data/RedPacketMethod.kt @@ -28,39 +28,28 @@ import com.dimension.maskbook.extension.export.ExtensionServices import com.dimension.maskbook.labs.model.SendMethodRequest import com.dimension.maskbook.labs.model.options.RedPacketOptions import com.dimension.maskbook.labs.route.LabsRoute -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach class RedPacketMethod( - private val appScope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, private val services: ExtensionServices, ) { - - fun startCollect() { - services.subscribeCurrentContentJSEvent(notifyRedPacket, claimOrRefundRedPacket) - .onEach { message -> - when (message.method) { - notifyRedPacket -> { - message.responseSuccess(true) - } - claimOrRefundRedPacket -> { - val options = message.params?.decodeJson() ?: return@onEach - val requestRaw = SendMethodRequest( - id = message.id, - jsonrpc = message.jsonrpc, - method = message.method, - ).encodeJson() - Navigator.navigate(LabsRoute.RedPacket.LuckyDrop(options.encodeJson(), requestRaw)) - // response in LuckDropViewModel - } + suspend fun startCollect() { + services.subscribeCurrentContentJSEvent(notifyRedPacket, claimOrRefundRedPacket).collect { message -> + when (message.method) { + notifyRedPacket -> { + message.responseSuccess(true) + } + claimOrRefundRedPacket -> { + val options = message.params?.decodeJson() ?: return@collect + val requestRaw = SendMethodRequest( + id = message.id, + jsonrpc = message.jsonrpc, + method = message.method, + ).encodeJson() + Navigator.navigate(LabsRoute.RedPacket.LuckyDrop(options.encodeJson(), requestRaw)) + // response in LuckDropViewModel } } - .flowOn(dispatcher) - .launchIn(appScope) + } } companion object { diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt index 0d77e4ad..eaae8524 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt @@ -23,15 +23,10 @@ package com.dimension.maskbook.labs.repository import com.dimension.maskbook.labs.data.JSMethod import com.dimension.maskbook.labs.export.model.AppData import com.dimension.maskbook.labs.export.model.AppKey -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.launch internal class AppRepository( - private val appScope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, private val jsMethod: JSMethod, ) : IAppRepository { @@ -49,16 +44,12 @@ internal class AppRepository( } } - override fun setEnabled(appKey: AppKey, enabled: Boolean) { - appScope.launch(dispatcher) { - jsMethod.setPluginStatus(appKey.id, enabled) - refreshApps() - } + override suspend fun setEnabled(appKey: AppKey, enabled: Boolean) { + jsMethod.setPluginStatus(appKey.id, enabled) + refreshApps() } - override fun init() { - appScope.launch(dispatcher) { - refreshApps() - } + override suspend fun init() { + refreshApps() } } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/IAppRepository.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/IAppRepository.kt index cd2ac01d..0d32366c 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/IAppRepository.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/IAppRepository.kt @@ -26,6 +26,6 @@ import kotlinx.coroutines.flow.Flow interface IAppRepository { val apps: Flow> - fun setEnabled(appKey: AppKey, enabled: Boolean) - fun init() + suspend fun setEnabled(appKey: AppKey, enabled: Boolean) + suspend fun init() } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/IPreferenceRepository.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/IPreferenceRepository.kt index ae368709..3adb6ec3 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/IPreferenceRepository.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/IPreferenceRepository.kt @@ -24,5 +24,5 @@ import kotlinx.coroutines.flow.Flow interface IPreferenceRepository { val shouldShowPluginSettingsTipDialog: Flow - fun setShowPluginSettingsTipDialog(bool: Boolean) + suspend fun setShowPluginSettingsTipDialog(bool: Boolean) } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/PreferenceRepository.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/PreferenceRepository.kt index 5d535428..f17a6081 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/PreferenceRepository.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/PreferenceRepository.kt @@ -26,19 +26,17 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext private val ShouldShowPluginSettingsTipDialog = booleanPreferencesKey("ShouldShowPluginSettingsTipDialog") val Context.labsDataStore: DataStore by preferencesDataStore(name = "labs") class PreferenceRepository( private val dataStore: DataStore, - private val appScope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, + private val preferenceCoroutineContext: CoroutineContext, ) : IPreferenceRepository { override val shouldShowPluginSettingsTipDialog: Flow @@ -46,8 +44,8 @@ class PreferenceRepository( it[ShouldShowPluginSettingsTipDialog] ?: true } - override fun setShowPluginSettingsTipDialog(bool: Boolean) { - appScope.launch(dispatcher) { + override suspend fun setShowPluginSettingsTipDialog(bool: Boolean) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[ShouldShowPluginSettingsTipDialog] = bool } 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..eb4c5643 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 @@ -32,6 +32,8 @@ import com.dimension.maskbook.wallet.export.WalletServices import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext data class AppDisplayData( val key: AppKey, @@ -77,12 +79,15 @@ private val displayDataList = listOf( ) class LabsViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: IAppRepository, private val walletRepository: WalletServices, ) : ViewModel() { init { - repository.init() + viewModelScope.launch(viewModelCoroutineContext) { + repository.init() + } } val apps by lazy { @@ -101,8 +106,4 @@ class LabsViewModel( val wallet by lazy { walletRepository.currentWallet.asStateIn(viewModelScope, null) } - - fun setEnabled(key: AppKey, enabled: Boolean) { - repository.setEnabled(key, enabled) - } } 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..91c3b6ab 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 @@ -30,9 +30,10 @@ import com.dimension.maskbook.labs.export.model.AppKey import com.dimension.maskbook.labs.repository.IAppRepository import com.dimension.maskbook.labs.repository.IPreferenceRepository import com.dimension.maskbook.wallet.export.WalletServices -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext data class PluginDisplayData( val key: AppKey, @@ -95,6 +96,7 @@ private val displayDataList = listOf( ) class PluginSettingsViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: IAppRepository, private val walletRepository: WalletServices, private val preferenceRepository: IPreferenceRepository, @@ -109,7 +111,7 @@ class PluginSettingsViewModel( ) } } - .flowOn(Dispatchers.IO) + .flowOn(viewModelCoroutineContext) .asStateIn(viewModelScope, emptyList()) } @@ -118,7 +120,9 @@ class PluginSettingsViewModel( } fun setEnabled(key: AppKey, enabled: Boolean) { - repository.setEnabled(key, enabled) + viewModelScope.launch(viewModelCoroutineContext) { + repository.setEnabled(key, enabled) + } } val shouldShowPluginSettingsTipDialog by lazy { @@ -127,6 +131,8 @@ class PluginSettingsViewModel( } fun setShowPluginSettingsTipDialog(bool: Boolean) { - preferenceRepository.setShowPluginSettingsTipDialog(bool) + viewModelScope.launch(viewModelCoroutineContext) { + preferenceRepository.setShowPluginSettingsTipDialog(bool) + } } } 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 513eea66..35e4dc81 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt @@ -82,7 +82,9 @@ 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.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.Koin import org.koin.dsl.bind @@ -136,7 +138,6 @@ object PersonaSetup : ModuleSetup { single { PreferenceRepository( get().personaDataStore, - get(appScope), get(defaultDispatcher), ) } @@ -144,8 +145,6 @@ object PersonaSetup : ModuleSetup { single { JSMethod(get()) } single { JSMethodV2( - get(appScope), - get(defaultDispatcher), get(), get(), get(), get(), @@ -215,7 +214,17 @@ object PersonaSetup : ModuleSetup { } override fun onExtensionReady(koin: Koin) { - koin.get().init() - koin.get().startSubscribe() + val appScope = koin.get(appScope) + val dispatcher = koin.get(ioDispatcher) + + appScope.launch(dispatcher) { + koin.get().init() + } + appScope.launch(dispatcher) { + koin.get().startCollect() + } + appScope.launch(dispatcher) { + koin.get().tryMigrateIndexedDb() + } } } diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/data/JSMethodV2.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/data/JSMethodV2.kt index c3abff32..888617a3 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/data/JSMethodV2.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/data/JSMethodV2.kt @@ -59,17 +59,9 @@ import com.dimension.maskbook.persona.model.options.UpdateProfileOptions import com.dimension.maskbook.persona.model.options.UpdateRelationOptions import com.dimension.maskbook.persona.repository.IPersonaRepository import com.dimension.maskbook.persona.repository.IPreferenceRepository -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch class JSMethodV2( - private val appScope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, private val services: ExtensionServices, private val database: PersonaDatabase, private val personaRepository: IPersonaRepository, @@ -79,30 +71,28 @@ class JSMethodV2( private val relationDataSource: JsRelationDataSource, private val postDataSource: JsPostDataSource, ) { - fun startSubscribe() { - appScope.launch(dispatcher) { - if (preferenceRepository.isMigratorIndexedDb.first()) { - return@launch - } - val records: IndexedDBAllRecord? = services.execute("get_all_indexedDB_records") - if (records != null) { - IndexedDBDataMigrator.migrate(database, records) - preferenceRepository.setIsMigratorIndexedDb(true) - } + suspend fun tryMigrateIndexedDb() { + if (preferenceRepository.isMigratorIndexedDb.first()) { + return } - services.subscribeBackgroundJSEvent(*methods) - .onEach { - subscribeWithPersona(it) || - subscribeWithProfile(it) || - subscribeWithRelation(it) || - subscribeWithAvatar(it) || - subscribeWithPost(it) || - subscribeWithHelper(it) - } - .flowOn(dispatcher) - .launchIn(appScope) + val records: IndexedDBAllRecord? = services.execute("get_all_indexedDB_records") + if (records != null) { + IndexedDBDataMigrator.migrate(database, records) + preferenceRepository.setIsMigratorIndexedDb(true) + } + } + + suspend fun startCollect() { + services.subscribeBackgroundJSEvent(*methods).collect { + subscribeWithPersona(it) || + subscribeWithProfile(it) || + subscribeWithRelation(it) || + subscribeWithAvatar(it) || + subscribeWithPost(it) || + subscribeWithHelper(it) + } } // Persona diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPersonaRepository.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPersonaRepository.kt index bf7b7c3a..bccade31 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPersonaRepository.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPersonaRepository.kt @@ -48,7 +48,7 @@ interface IPersonaRepository { suspend fun createPersonaFromMnemonic(value: List, name: String) suspend fun createPersonaFromPrivateKey(value: String, name: String) suspend fun backupPrivateKey(id: String): String - fun init() + suspend fun init() fun setPlatform(platformType: PlatformType) fun setAvatarForCurrentPersona(avatar: Uri?) suspend fun createPersonaBackup(hasPrivateKeyOnly: Boolean): List diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPreferenceRepository.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPreferenceRepository.kt index b379a6d6..fa6f1d84 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPreferenceRepository.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPreferenceRepository.kt @@ -28,10 +28,10 @@ interface IPreferenceRepository { val currentPersonaIdentifier: Flow suspend fun setCurrentPersonaIdentifier(identifier: String) val shouldShowContactsTipDialog: Flow - fun setShowContactsTipDialog(bool: Boolean) + suspend fun setShowContactsTipDialog(bool: Boolean) val isMigratorIndexedDb: Flow - fun setIsMigratorIndexedDb(bool: Boolean) + suspend fun setIsMigratorIndexedDb(bool: Boolean) val lastDetectProfileIdentifier: Flow fun setLastDetectProfileIdentifier(identifier: String) diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt index f092e0c9..e29eadcf 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt @@ -102,7 +102,9 @@ internal class PersonaRepository( onDone: (ConnectAccountData) -> Unit, ) { connectingJob?.cancel() - extensionServices.setSite(platformType.toSite()) + appScope.launch { + extensionServices.setSite(platformType.toSite()) + } connectingJob = preferenceRepository.lastDetectProfileIdentifier .filterNot { it.isEmpty() } @@ -118,20 +120,18 @@ internal class PersonaRepository( .launchIn(appScope) } - override fun init() { - appScope.launch(ioDispatcher) { - if (personaDataSource.isEmpty()) { - return@launch - } - - val identifier = preferenceRepository.currentPersonaIdentifier.firstOrNull() - if (!identifier.isNullOrEmpty() && personaDataSource.contains(identifier)) { - return@launch - } + override suspend fun init() { + if (personaDataSource.isEmpty()) { + return + } - val newCurrentPersona = personaDataSource.getPersonaFirst() - setCurrentPersona(newCurrentPersona?.identifier.orEmpty()) + val identifier = preferenceRepository.currentPersonaIdentifier.firstOrNull() + if (!identifier.isNullOrEmpty() && personaDataSource.contains(identifier)) { + return } + + val newCurrentPersona = personaDataSource.getPersonaFirst() + setCurrentPersona(newCurrentPersona?.identifier.orEmpty()) } override suspend fun setCurrentPersona(id: String) { @@ -206,7 +206,9 @@ internal class PersonaRepository( } override fun setPlatform(platformType: PlatformType) { - extensionServices.setSite(platformType.toSite()) + appScope.launch { + extensionServices.setSite(platformType.toSite()) + } } override fun setAvatarForCurrentPersona(avatar: Uri?) { diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PreferenceRepository.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PreferenceRepository.kt index abf047c9..ee33e36f 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PreferenceRepository.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PreferenceRepository.kt @@ -27,13 +27,12 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext private val CurrentPersonaKey = stringPreferencesKey("current_persona") private val ShouldShowContactsTipDialog = booleanPreferencesKey("ShouldShowContactsTipDialog") @@ -42,8 +41,7 @@ val Context.personaDataStore: DataStore by preferencesDataStore(nam class PreferenceRepository( private val dataStore: DataStore, - private val appScope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, + private val preferenceCoroutineContext: CoroutineContext, ) : IPreferenceRepository { override val data: Flow @@ -65,8 +63,8 @@ class PreferenceRepository( it[ShouldShowContactsTipDialog] ?: true } - override fun setShowContactsTipDialog(bool: Boolean) { - appScope.launch(dispatcher) { + override suspend fun setShowContactsTipDialog(bool: Boolean) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[ShouldShowContactsTipDialog] = bool } @@ -78,8 +76,8 @@ class PreferenceRepository( it[IsMigratorIndexedDb] ?: false } - override fun setIsMigratorIndexedDb(bool: Boolean) { - appScope.launch(dispatcher) { + override suspend fun setIsMigratorIndexedDb(bool: Boolean) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[IsMigratorIndexedDb] = bool } diff --git a/setting/export/src/commonMain/kotlin/com/dimension/maskbook/setting/export/SettingServices.kt b/setting/export/src/commonMain/kotlin/com/dimension/maskbook/setting/export/SettingServices.kt index b6b34dfa..3ed2c383 100644 --- a/setting/export/src/commonMain/kotlin/com/dimension/maskbook/setting/export/SettingServices.kt +++ b/setting/export/src/commonMain/kotlin/com/dimension/maskbook/setting/export/SettingServices.kt @@ -29,7 +29,7 @@ interface SettingServices { val paymentPassword: Flow val backupPassword: Flow val shouldShowLegalScene: Flow - fun setBiometricEnabled(value: Boolean) - fun setPaymentPassword(value: String) - fun setShouldShowLegalScene(value: Boolean) + suspend fun setBiometricEnabled(value: Boolean) + suspend fun setPaymentPassword(value: String) + suspend fun setShouldShowLegalScene(value: Boolean) } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingServicesImpl.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingServicesImpl.kt index 9343e2a6..132ab366 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingServicesImpl.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingServicesImpl.kt @@ -48,16 +48,15 @@ class SettingServicesImpl( override val shouldShowLegalScene: Flow get() = settingsRepository.shouldShowLegalScene - - override fun setBiometricEnabled(value: Boolean) { + override suspend fun setBiometricEnabled(value: Boolean) { settingsRepository.setBiometricEnabled(value) } - override fun setPaymentPassword(value: String) { + override suspend fun setPaymentPassword(value: String) { settingsRepository.setPaymentPassword(value) } - override fun setShouldShowLegalScene(value: Boolean) { + override suspend fun setShouldShowLegalScene(value: Boolean) { settingsRepository.setShouldShowLegalScene(value) } 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 ef4ac94e..c2b552d7 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt @@ -28,6 +28,8 @@ import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope import com.dimension.maskbook.common.di.scope.defaultDispatcher import com.dimension.maskbook.common.di.scope.ioDispatcher +import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext +import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.retrofit.retrofit import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.setting.data.JSDataSource @@ -56,6 +58,9 @@ 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 kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.Koin import org.koin.dsl.bind @@ -79,7 +84,6 @@ object SettingSetup : ModuleSetup { } single { SettingsRepository( - get(defaultDispatcher), get(), get(), get(), @@ -107,24 +111,22 @@ object SettingSetup : ModuleSetup { single { JSDataSource( get(), - get(appScope), - get(ioDispatcher), + get(preferenceCoroutineContext), ) } single { JSMethod(get()) } single { SettingDataSource( get().settingsDataStore, - get(appScope), - get(defaultDispatcher), + get(preferenceCoroutineContext), ) } - viewModel { LanguageSettingsViewModel(get()) } - viewModel { AppearanceSettingsViewModel(get()) } - viewModel { DataSourceSettingsViewModel(get()) } - viewModel { PaymentPasswordSettingsViewModel(get()) } - viewModel { BackupPasswordSettingsViewModel(get()) } + viewModel { LanguageSettingsViewModel(get(viewModelCoroutineContext), get()) } + viewModel { AppearanceSettingsViewModel(get(viewModelCoroutineContext), get()) } + viewModel { DataSourceSettingsViewModel(get(viewModelCoroutineContext), get()) } + viewModel { PaymentPasswordSettingsViewModel(get(viewModelCoroutineContext), get()) } + viewModel { BackupPasswordSettingsViewModel(get(viewModelCoroutineContext), get()) } viewModel { BackupLocalViewModel(get(), get()) } viewModel { EmailSetupViewModel(get(), get()) } viewModel { PhoneSetupViewModel(get(), get()) } @@ -138,6 +140,11 @@ object SettingSetup : ModuleSetup { } override fun onExtensionReady(koin: Koin) { - koin.get().initData() + val appScope = koin.get(appScope) + val dispatcher = koin.get(ioDispatcher) + + appScope.launch(dispatcher) { + koin.get().initData() + } } } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt index 75af2c25..478fd3d6 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/JSDataSource.kt @@ -23,18 +23,16 @@ package com.dimension.maskbook.setting.data import com.dimension.maskbook.setting.export.model.Appearance import com.dimension.maskbook.setting.export.model.DataProvider import com.dimension.maskbook.setting.export.model.Language -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext internal class JSDataSource( private val jsMethod: JSMethod, - private val appScope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, + private val preferenceCoroutineContext: CoroutineContext, ) { private val _appearance = MutableStateFlow(Appearance.default) private val _dataProvider = MutableStateFlow(DataProvider.COIN_GECKO) @@ -44,29 +42,29 @@ internal class JSDataSource( val appearance = _appearance.asSharedFlow() val dataProvider = _dataProvider.asSharedFlow() - fun setLanguage(language: Language) { - appScope.launch(dispatcher) { + suspend fun setLanguage(language: Language) { + withContext(preferenceCoroutineContext) { jsMethod.setLanguage(language) _language.value = jsMethod.getLanguage() } } - fun setAppearance(appearance: Appearance) { - appScope.launch(dispatcher) { + suspend fun setAppearance(appearance: Appearance) { + withContext(preferenceCoroutineContext) { jsMethod.setTheme(appearance) _appearance.value = jsMethod.getTheme() } } - fun setDataProvider(dataProvider: DataProvider) { - appScope.launch(dispatcher) { + suspend fun setDataProvider(dataProvider: DataProvider) { + withContext(preferenceCoroutineContext) { jsMethod.setTrendingDataSource(dataProvider) _dataProvider.value = jsMethod.getTrendingDataSource() } } - fun initData() { - appScope.launch(dispatcher) { + suspend fun initData() { + withContext(preferenceCoroutineContext) { awaitAll( async { _language.value = jsMethod.getLanguage() }, async { _appearance.value = jsMethod.getTheme() }, diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt index 29a306b7..4368fff1 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/data/SettingDataSource.kt @@ -27,11 +27,10 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext private val PaymentPasswordKey = stringPreferencesKey("payment_password") private val BackupPasswordKey = stringPreferencesKey("backup_password") @@ -43,8 +42,7 @@ val Context.settingsDataStore: DataStore by preferencesDataStore(na class SettingDataSource( private val dataStore: DataStore, - private val appScope: CoroutineScope, - private val dispatcher: CoroutineDispatcher, + private val preferenceCoroutineContext: CoroutineContext, ) { val biometricEnabled: Flow get() = dataStore.data.map { @@ -70,48 +68,48 @@ class SettingDataSource( it[RegisterPhone] ?: "" } - fun setPaymentPassword(value: String) { - appScope.launch(dispatcher) { + suspend fun setPaymentPassword(value: String) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[PaymentPasswordKey] = value } } } - fun setBackupPassword(value: String) { - appScope.launch(dispatcher) { + suspend fun setBackupPassword(value: String) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[BackupPasswordKey] = value } } } - fun setShouldShowLegalScene(value: Boolean) { - appScope.launch(dispatcher) { + suspend fun setShouldShowLegalScene(value: Boolean) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[ShouldShowLegalSceneKey] = value } } } - fun setBiometricEnabled(value: Boolean) { - appScope.launch(dispatcher) { + suspend fun setBiometricEnabled(value: Boolean) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[BiometricEnabledKey] = value } } } - fun setRegisterEmail(value: String) { - appScope.launch(dispatcher) { + suspend fun setRegisterEmail(value: String) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[RegisterEmail] = value } } } - fun setRegisterPhone(value: String) { - appScope.launch(dispatcher) { + suspend fun setRegisterPhone(value: String) { + withContext(preferenceCoroutineContext) { dataStore.edit { it[RegisterPhone] = value } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/ISettingsRepository.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/ISettingsRepository.kt index 9ca7f557..cf5d8730 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/ISettingsRepository.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/ISettingsRepository.kt @@ -37,14 +37,14 @@ interface ISettingsRepository { val shouldShowLegalScene: Flow val email: Flow val phone: Flow - fun setBiometricEnabled(value: Boolean) - fun setLanguage(language: Language) - fun setAppearance(appearance: Appearance) - fun setDataProvider(dataProvider: DataProvider) - fun setPaymentPassword(value: String) - fun setBackupPassword(value: String) + suspend fun setBiometricEnabled(value: Boolean) + suspend fun setLanguage(language: Language) + suspend fun setAppearance(appearance: Appearance) + suspend fun setDataProvider(dataProvider: DataProvider) + suspend fun setPaymentPassword(value: String) + suspend fun setBackupPassword(value: String) suspend fun generateBackupMeta(): BackupMeta - fun provideBackupMeta(file: BackupMetaFile): BackupMeta + suspend fun provideBackupMeta(file: BackupMetaFile): BackupMeta suspend fun restoreBackup(value: BackupMetaFile) suspend fun createBackup( noPosts: Boolean = false, @@ -55,7 +55,7 @@ interface ISettingsRepository { hasPrivateKeyOnly: Boolean = false, ): BackupMetaFile - fun setShouldShowLegalScene(value: Boolean) - fun saveEmail(value: String) - fun savePhone(value: String) + suspend fun setShouldShowLegalScene(value: Boolean) + suspend fun saveEmail(value: String) + suspend fun savePhone(value: String) } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt index 7e68512a..10200f49 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt @@ -40,12 +40,9 @@ import com.dimension.maskbook.setting.model.mapping.toIndexedDBProfile import com.dimension.maskbook.setting.model.mapping.toIndexedDBRelation import com.dimension.maskbook.setting.model.mapping.toWalletData import com.dimension.maskbook.wallet.export.WalletServices -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext internal class SettingsRepository( - private val dispatcher: CoroutineDispatcher, private val personaServices: PersonaServices, private val settingDataSource: SettingDataSource, private val jsDataSource: JSDataSource, @@ -70,31 +67,31 @@ internal class SettingsRepository( override val phone: Flow get() = settingDataSource.phone - override fun setBiometricEnabled(value: Boolean) { + override suspend fun setBiometricEnabled(value: Boolean) { settingDataSource.setBiometricEnabled(value) } - override fun setLanguage(language: Language) { + override suspend fun setLanguage(language: Language) { jsDataSource.setLanguage(language) } - override fun setAppearance(appearance: Appearance) { + override suspend fun setAppearance(appearance: Appearance) { jsDataSource.setAppearance(appearance) } - override fun setDataProvider(dataProvider: DataProvider) { + override suspend fun setDataProvider(dataProvider: DataProvider) { jsDataSource.setDataProvider(dataProvider) } - override fun setPaymentPassword(value: String) { + override suspend fun setPaymentPassword(value: String) { settingDataSource.setPaymentPassword(value) } - override fun setBackupPassword(value: String) { + override suspend fun setBackupPassword(value: String) { settingDataSource.setBackupPassword(value) } - override fun setShouldShowLegalScene(value: Boolean) { + override suspend fun setShouldShowLegalScene(value: Boolean) { settingDataSource.setShouldShowLegalScene(value) } @@ -110,7 +107,7 @@ internal class SettingsRepository( } } - override fun provideBackupMeta(file: BackupMetaFile): BackupMeta { + override suspend fun provideBackupMeta(file: BackupMetaFile): BackupMeta { return BackupMeta( personas = file.personas.size, associatedAccount = file.personas.sumOf { it.linkedProfiles.size }, @@ -124,18 +121,16 @@ internal class SettingsRepository( } override suspend fun restoreBackup(value: BackupMetaFile) { - withContext(dispatcher) { - val persona = value.personas.map { it.toIndexedDBPersona() } - personaServices.restorePersonaBackup(persona) - val profile = value.profiles.map { it.toIndexedDBProfile() } - personaServices.restoreProfileBackup(profile) - val relation = value.relations.map { it.toIndexedDBRelation() } - personaServices.restoreRelationBackup(relation) - val post = value.posts.map { it.toIndexDbPost() } - personaServices.restorePostBackup(post) - val wallet = value.wallets.map { it.toWalletData() } - walletServices.restoreWalletBackup(wallet) - } + val persona = value.personas.map { it.toIndexedDBPersona() } + personaServices.restorePersonaBackup(persona) + val profile = value.profiles.map { it.toIndexedDBProfile() } + personaServices.restoreProfileBackup(profile) + val relation = value.relations.map { it.toIndexedDBRelation() } + personaServices.restoreRelationBackup(relation) + val post = value.posts.map { it.toIndexDbPost() } + personaServices.restorePostBackup(post) + val wallet = value.wallets.map { it.toWalletData() } + walletServices.restoreWalletBackup(wallet) } override suspend fun createBackup( @@ -146,42 +141,41 @@ internal class SettingsRepository( noRelations: Boolean, hasPrivateKeyOnly: Boolean ): BackupMetaFile { - return withContext(dispatcher) { - val personas = if (noPersonas) { - emptyList() - } else { - backupPersona(hasPrivateKeyOnly) - } - val profile = if (noProfiles) { - emptyList() - } else { - backProfiles() - } - val wallets = if (noWallets) { - emptyList() - } else { - backupWallets() - } - val posts = if (noPosts) { - emptyList() - } else { - backupPosts() - } - val relations = if (noRelations) { - emptyList() - } else { - backupRelations() - } - BackupMetaFile( - personas = personas, - wallets = wallets, - posts = posts, - profiles = profile, - meta = BackupMetaFile.Meta.Default, - grantedHostPermissions = emptyList(), - relations = relations, - ) + + val personas = if (noPersonas) { + emptyList() + } else { + backupPersona(hasPrivateKeyOnly) + } + val profile = if (noProfiles) { + emptyList() + } else { + backProfiles() + } + val wallets = if (noWallets) { + emptyList() + } else { + backupWallets() } + val posts = if (noPosts) { + emptyList() + } else { + backupPosts() + } + val relations = if (noRelations) { + emptyList() + } else { + backupRelations() + } + return BackupMetaFile( + personas = personas, + wallets = wallets, + posts = posts, + profiles = profile, + meta = BackupMetaFile.Meta.Default, + grantedHostPermissions = emptyList(), + relations = relations, + ) } private suspend fun backupRelations(): List { @@ -214,11 +208,11 @@ internal class SettingsRepository( } } - override fun saveEmail(value: String) { + override suspend fun saveEmail(value: String) { settingDataSource.setRegisterEmail(value) } - override fun savePhone(value: String) { + override suspend fun savePhone(value: String) { settingDataSource.setRegisterPhone(value) } } 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..ee40e9d5 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 @@ -25,8 +25,11 @@ 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 kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class AppearanceSettingsViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, ) : ViewModel() { val appearance by lazy { @@ -34,6 +37,8 @@ class AppearanceSettingsViewModel( } fun setAppearance(appearance: Appearance) { - repository.setAppearance(appearance = appearance) + viewModelScope.launch(viewModelCoroutineContext) { + repository.setAppearance(appearance = appearance) + } } } 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 f5055385..8865a7d2 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 @@ -27,8 +27,11 @@ 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.launch +import kotlin.coroutines.CoroutineContext class BackupPasswordSettingsViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, ) : ViewModel() { @@ -93,6 +96,8 @@ class BackupPasswordSettingsViewModel( } fun confirm() { - repository.setBackupPassword(newPassword.value) + viewModelScope.launch(viewModelCoroutineContext) { + repository.setBackupPassword(newPassword.value) + } } } 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..d5dada47 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 @@ -25,8 +25,11 @@ 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 kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class DataSourceSettingsViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, ) : ViewModel() { val dataProvider by lazy { @@ -34,6 +37,8 @@ class DataSourceSettingsViewModel( } fun setDataProvider(dataProvider: DataProvider) { - repository.setDataProvider(dataProvider = dataProvider) + viewModelScope.launch(viewModelCoroutineContext) { + repository.setDataProvider(dataProvider = dataProvider) + } } } 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..5cf541af 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 @@ -25,8 +25,11 @@ 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 kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class LanguageSettingsViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository ) : ViewModel() { val language by lazy { @@ -34,6 +37,8 @@ class LanguageSettingsViewModel( } fun setLanguage(language: Language) { - repository.setLanguage(language = language) + viewModelScope.launch(viewModelCoroutineContext) { + repository.setLanguage(language = language) + } } } 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 1ea76091..8925b705 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 @@ -27,8 +27,11 @@ 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.launch +import kotlin.coroutines.CoroutineContext class PaymentPasswordSettingsViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, ) : ViewModel() { @@ -89,6 +92,8 @@ class PaymentPasswordSettingsViewModel( } fun confirm() { - repository.setPaymentPassword(newPassword.value) + viewModelScope.launch(viewModelCoroutineContext) { + repository.setPaymentPassword(newPassword.value) + } } } From 99ebeea083160df9164e14a000281a5a15a038f6 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 19 Apr 2022 16:13:03 +0800 Subject: [PATCH 5/7] fix build --- .../com/dimension/maskbook/common/CommonSetup.kt | 5 +++-- .../maskbook/common/di/scope/CoroutinesQualifiers.kt | 4 ++-- .../common/viewmodel/BiometricEnableViewModel.kt | 12 +++++++++++- .../viewmodel/SetUpPaymentPasswordViewModel.kt | 7 ++++++- .../com/dimension/maskbook/persona/PersonaSetup.kt | 4 ++-- .../maskbook/persona/ui/scenes/PersonaInfoScene.kt | 7 ++++++- .../com/dimension/maskbook/setting/SettingSetup.kt | 8 ++++++-- .../setting/repository/SettingsRepository.kt | 10 ++++++---- .../maskbook/setting/ui/scenes/SettingsScene.kt | 5 +---- .../setting/viewmodel/BackupLocalViewModel.kt | 4 +++- .../setting/viewmodel/BackupMergeConfirmViewModel.kt | 4 +++- .../dimension/maskbook/wallet/route/WalletsRoute.kt | 9 ++++++++- 12 files changed, 57 insertions(+), 22 deletions(-) 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 65b1ba2a..d5e8b230 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt @@ -23,6 +23,7 @@ package com.dimension.maskbook.common import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.di.module.coroutinesModule +import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.manager.ImageLoaderManager import com.dimension.maskbook.common.manager.KeyStoreManager import com.dimension.maskbook.common.util.BiometricAuthenticator @@ -43,8 +44,8 @@ object CommonSetup : ModuleSetup { single { KeyStoreManager(get()) } single { ImageLoaderManager(get()) } - viewModel { BiometricEnableViewModel(get(), get()) } + viewModel { BiometricEnableViewModel(get(viewModelCoroutineContext), get(), get()) } viewModel { BiometricViewModel(get(), get()) } - viewModel { SetUpPaymentPasswordViewModel(get()) } + viewModel { SetUpPaymentPasswordViewModel(get(viewModelCoroutineContext), get()) } } } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt index d1f6ced4..047487a6 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt @@ -27,7 +27,7 @@ val ioDispatcher = named("IoDispatcher") val mainDispatcher = named("MainDispatcher") val mainImmediateDispatcher = named("MainImmediateDispatcher") -val preferenceCoroutineContext = defaultDispatcher -val viewModelCoroutineContext = defaultDispatcher +val preferenceCoroutineContext = named("PreferenceCoroutineContext") +val viewModelCoroutineContext = named("ViewModelCoroutineContext") val appScope = named("AppScope") 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..b38355c4 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 @@ -22,10 +22,14 @@ package com.dimension.maskbook.common.viewmodel import android.content.Context import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.util.BiometricAuthenticator import com.dimension.maskbook.setting.export.SettingServices +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class BiometricEnableViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val biometricAuthenticator: BiometricAuthenticator, private val repository: SettingServices, ) : ViewModel() { @@ -40,7 +44,7 @@ class BiometricEnableViewModel( context = context, onSuccess = { onEnable.invoke() - repository.setBiometricEnabled(true) + setBiometricEnabled(true) }, title = title, subTitle = subTitle, @@ -48,5 +52,11 @@ class BiometricEnableViewModel( ) } + fun setBiometricEnabled(enabled: Boolean) { + viewModelScope.launch(viewModelCoroutineContext) { + repository.setBiometricEnabled(enabled) + } + } + fun isSupported(context: Context) = biometricAuthenticator.canAuthenticate(context = context) } 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 879b6756..dacefb18 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 @@ -27,8 +27,11 @@ 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 kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class SetUpPaymentPasswordViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: SettingServices, ) : ViewModel() { private val _newPassword = MutableStateFlow("") @@ -55,6 +58,8 @@ class SetUpPaymentPasswordViewModel( } fun confirm() { - repository.setPaymentPassword(newPassword.value) + viewModelScope.launch(viewModelCoroutineContext) { + repository.setPaymentPassword(newPassword.value) + } } } 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 35e4dc81..48a11379 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt @@ -28,9 +28,9 @@ import androidx.room.Room import com.dimension.maskbook.common.LocalBackupAccount import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope -import com.dimension.maskbook.common.di.scope.defaultDispatcher import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.mainDispatcher +import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.persona.data.JSMethod import com.dimension.maskbook.persona.data.JSMethodV2 @@ -138,7 +138,7 @@ object PersonaSetup : ModuleSetup { single { PreferenceRepository( get().personaDataStore, - get(defaultDispatcher), + get(preferenceCoroutineContext), ) } 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 80a94d52..22faf998 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 @@ -49,6 +49,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -84,6 +85,7 @@ 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 kotlinx.coroutines.launch import org.koin.androidx.compose.get import org.koin.androidx.compose.getViewModel import kotlin.math.absoluteValue @@ -136,6 +138,7 @@ fun PersonaInfoScene( } val context = LocalContext.current + val scope = rememberCoroutineScope() val viewModel: ContactsViewModel = getViewModel() val contactItems by viewModel.items.collectAsState() @@ -256,7 +259,9 @@ fun PersonaInfoScene( .padding(horizontal = 22.5f.dp, vertical = 24.dp) .align(Alignment.BottomCenter), onClose = { - preferenceRepository.setShowContactsTipDialog(false) + scope.launch { + preferenceRepository.setShowContactsTipDialog(false) + } }, text = { Text( 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 c2b552d7..291bcf50 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt @@ -84,6 +84,7 @@ object SettingSetup : ModuleSetup { } single { SettingsRepository( + get(ioDispatcher), get(), get(), get(), @@ -127,13 +128,16 @@ object SettingSetup : ModuleSetup { viewModel { DataSourceSettingsViewModel(get(viewModelCoroutineContext), get()) } viewModel { PaymentPasswordSettingsViewModel(get(viewModelCoroutineContext), get()) } viewModel { BackupPasswordSettingsViewModel(get(viewModelCoroutineContext), get()) } - viewModel { BackupLocalViewModel(get(), get()) } + viewModel { BackupLocalViewModel(get(viewModelCoroutineContext), get(), get()) } viewModel { EmailSetupViewModel(get(), get()) } viewModel { PhoneSetupViewModel(get(), get()) } viewModel { EmailBackupViewModel(get()) } viewModel { PhoneBackupViewModel(get()) } viewModel { (onDone: () -> Unit, url: String, account: String) -> - BackupMergeConfirmViewModel(get(), get(), get().contentResolver, onDone, url, account) + BackupMergeConfirmViewModel( + get(viewModelCoroutineContext), + get(), get(), get().contentResolver, onDone, url, account + ) } viewModel { BackupCloudViewModel(get()) } viewModel { BackupCloudExecuteViewModel(get(), get(), get()) } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt index 10200f49..175b1540 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt @@ -40,9 +40,12 @@ import com.dimension.maskbook.setting.model.mapping.toIndexedDBProfile import com.dimension.maskbook.setting.model.mapping.toIndexedDBRelation import com.dimension.maskbook.setting.model.mapping.toWalletData import com.dimension.maskbook.wallet.export.WalletServices +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext internal class SettingsRepository( + private val ioDispatcher: CoroutineDispatcher, private val personaServices: PersonaServices, private val settingDataSource: SettingDataSource, private val jsDataSource: JSDataSource, @@ -120,7 +123,7 @@ internal class SettingsRepository( ) } - override suspend fun restoreBackup(value: BackupMetaFile) { + override suspend fun restoreBackup(value: BackupMetaFile) = withContext(ioDispatcher) { val persona = value.personas.map { it.toIndexedDBPersona() } personaServices.restorePersonaBackup(persona) val profile = value.profiles.map { it.toIndexedDBProfile() } @@ -140,8 +143,7 @@ internal class SettingsRepository( noProfiles: Boolean, noRelations: Boolean, hasPrivateKeyOnly: Boolean - ): BackupMetaFile { - + ): BackupMetaFile = withContext(ioDispatcher) { val personas = if (noPersonas) { emptyList() } else { @@ -167,7 +169,7 @@ internal class SettingsRepository( } else { backupRelations() } - return BackupMetaFile( + BackupMetaFile( personas = personas, wallets = wallets, posts = posts, 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 02ea686f..1aee9a4f 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 @@ -175,7 +175,6 @@ fun SettingsScene( !biometricEnabled, context, biometricEnableViewModel, - repository ) }) }, @@ -184,7 +183,6 @@ fun SettingsScene( !biometricEnabled, context, biometricEnableViewModel, - repository ) }, ) @@ -291,7 +289,6 @@ private fun enableBiometric( enable: Boolean, context: Context, viewModel: BiometricEnableViewModel, - repository: ISettingsRepository ) { if (enable) { viewModel.enable( @@ -300,7 +297,7 @@ private fun enableBiometric( negativeButton = R.string.common_controls_cancel, ) } else { - repository.setBiometricEnabled(enable) + viewModel.setBiometricEnabled(false) } } 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..81e63c09 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 @@ -32,8 +32,10 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class BackupLocalViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, private val backupRepository: BackupRepository, ) : ViewModel() { @@ -72,7 +74,7 @@ class BackupLocalViewModel( _paymentPassword.value = value } - fun save(it: Uri, withWallet: Boolean) = viewModelScope.launch { + fun save(it: Uri, withWallet: Boolean) = viewModelScope.launch(viewModelCoroutineContext) { _state.value = State.Loading try { val password = repository.backupPassword.firstOrNull() ?: run { 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 28bbe8f5..c9b9d4c8 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 @@ -30,8 +30,10 @@ import com.dimension.maskbook.setting.repository.BackupRepository import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext class BackupMergeConfirmViewModel( + private val viewModelCoroutineContext: CoroutineContext, private val backupRepository: BackupRepository, private val settingsRepository: ISettingsRepository, private val contentResolver: ContentResolver, @@ -51,7 +53,7 @@ class BackupMergeConfirmViewModel( _passwordValid.value = Validator.isValidBackupPasswordFormat(value) } - fun confirm() = viewModelScope.launch { + fun confirm() = viewModelScope.launch(viewModelCoroutineContext) { _loading.value = true try { contentResolver.openInputStream(Uri.parse(url))?.use { 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..995b460c 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 @@ -30,6 +30,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext @@ -97,6 +98,7 @@ 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 kotlinx.coroutines.launch import org.koin.androidx.compose.get import org.koin.androidx.compose.getViewModel import org.koin.core.parameter.parametersOf @@ -568,8 +570,11 @@ fun WalletIntroHostLegal( 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 biometricEnableViewModel: BiometricEnableViewModel = getViewModel() val context = LocalContext.current + val scope = rememberCoroutineScope() + val next: () -> Unit = { val route = if (password.isNullOrEmpty()) { WalletRoute.WalletIntroHostPassword(type.name) @@ -594,7 +599,9 @@ fun WalletIntroHostLegal( LegalScene( onBack = onBack, onAccept = { - repo.setShouldShowLegalScene(false) + scope.launch { + repo.setShouldShowLegalScene(false) + } }, onBrowseAgreement = { context.startActivity( From 7ec0ea884d22eb197eb355a0333de291a9864d40 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 19 Apr 2022 16:19:35 +0800 Subject: [PATCH 6/7] remove viewModelCoroutineContext --- .../com/dimension/maskbook/common/CommonSetup.kt | 5 ++--- .../maskbook/common/di/module/CoroutinesModule.kt | 4 ---- .../common/di/scope/CoroutinesQualifiers.kt | 1 - .../common/viewmodel/BiometricEnableViewModel.kt | 4 +--- .../viewmodel/SetUpPaymentPasswordViewModel.kt | 4 +--- .../com/dimension/maskbook/entry/EntrySetup.kt | 3 +-- .../maskbook/entry/viewModel/IntroViewModel.kt | 4 +--- .../com/dimension/maskbook/labs/LabsSetup.kt | 5 ++--- .../maskbook/labs/viewmodel/LabsViewModel.kt | 4 +--- .../labs/viewmodel/PluginSettingsViewModel.kt | 8 ++------ .../com/dimension/maskbook/setting/SettingSetup.kt | 14 ++++++-------- .../viewmodel/AppearanceSettingsViewModel.kt | 4 +--- .../setting/viewmodel/BackupLocalViewModel.kt | 4 +--- .../viewmodel/BackupMergeConfirmViewModel.kt | 4 +--- .../viewmodel/BackupPasswordSettingsViewModel.kt | 4 +--- .../viewmodel/DataSourceSettingsViewModel.kt | 4 +--- .../setting/viewmodel/LanguageSettingsViewModel.kt | 4 +--- .../viewmodel/PaymentPasswordSettingsViewModel.kt | 4 +--- 18 files changed, 24 insertions(+), 60 deletions(-) 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 d5e8b230..65b1ba2a 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/CommonSetup.kt @@ -23,7 +23,6 @@ package com.dimension.maskbook.common import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import com.dimension.maskbook.common.di.module.coroutinesModule -import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.manager.ImageLoaderManager import com.dimension.maskbook.common.manager.KeyStoreManager import com.dimension.maskbook.common.util.BiometricAuthenticator @@ -44,8 +43,8 @@ object CommonSetup : ModuleSetup { single { KeyStoreManager(get()) } single { ImageLoaderManager(get()) } - viewModel { BiometricEnableViewModel(get(viewModelCoroutineContext), get(), get()) } + viewModel { BiometricEnableViewModel(get(), get()) } viewModel { BiometricViewModel(get(), get()) } - viewModel { SetUpPaymentPasswordViewModel(get(viewModelCoroutineContext), get()) } + viewModel { SetUpPaymentPasswordViewModel(get()) } } } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt index 9476836d..be4bb17b 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt @@ -26,7 +26,6 @@ import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.mainDispatcher import com.dimension.maskbook.common.di.scope.mainImmediateDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext -import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.util.coroutineExceptionHandler import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -44,9 +43,6 @@ internal fun Module.coroutinesModule() { single(preferenceCoroutineContext) { NonCancellable + Dispatchers.Default } - single(viewModelCoroutineContext) { - coroutineExceptionHandler + Dispatchers.Default - } single(appScope) { CoroutineScope( diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt index 047487a6..0807a99c 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt @@ -28,6 +28,5 @@ val mainDispatcher = named("MainDispatcher") val mainImmediateDispatcher = named("MainImmediateDispatcher") val preferenceCoroutineContext = named("PreferenceCoroutineContext") -val viewModelCoroutineContext = named("ViewModelCoroutineContext") val appScope = named("AppScope") 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 b38355c4..5b2eb694 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 @@ -26,10 +26,8 @@ import androidx.lifecycle.viewModelScope import com.dimension.maskbook.common.util.BiometricAuthenticator import com.dimension.maskbook.setting.export.SettingServices import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class BiometricEnableViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val biometricAuthenticator: BiometricAuthenticator, private val repository: SettingServices, ) : ViewModel() { @@ -53,7 +51,7 @@ class BiometricEnableViewModel( } fun setBiometricEnabled(enabled: Boolean) { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setBiometricEnabled(enabled) } } 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 dacefb18..bda6f266 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 @@ -28,10 +28,8 @@ import com.dimension.maskbook.setting.export.SettingServices import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class SetUpPaymentPasswordViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: SettingServices, ) : ViewModel() { private val _newPassword = MutableStateFlow("") @@ -58,7 +56,7 @@ class SetUpPaymentPasswordViewModel( } fun confirm() { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setPaymentPassword(newPassword.value) } } 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 662d3284..6002efbb 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt @@ -27,7 +27,6 @@ import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext -import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.route.Navigator import com.dimension.maskbook.entry.data.JSMethod import com.dimension.maskbook.entry.repository.EntryRepository @@ -58,7 +57,7 @@ object EntrySetup : ModuleSetup { single { JSMethod(get()) } - viewModel { IntroViewModel(get(viewModelCoroutineContext), get()) } + viewModel { IntroViewModel(get()) } } override fun onExtensionReady(koin: Koin) { diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt index e620cce8..794a2a7d 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt @@ -24,15 +24,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.dimension.maskbook.entry.repository.EntryRepository import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class IntroViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: EntryRepository, ) : ViewModel() { fun setShouldShowEntry(value: Boolean) { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setShouldShowEntry(value) } } 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 05c09e4d..a10f578b 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt @@ -27,7 +27,6 @@ import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext -import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.labs.data.JSMethod import com.dimension.maskbook.labs.data.RedPacketMethod @@ -74,8 +73,8 @@ object LabsSetup : ModuleSetup { single { LabsTabScreen() } bind TabScreen::class - viewModel { LabsViewModel(get(viewModelCoroutineContext), get(), get()) } - viewModel { PluginSettingsViewModel(get(viewModelCoroutineContext), get(), get(), get()) } + viewModel { LabsViewModel(get(), get()) } + viewModel { PluginSettingsViewModel(get(), get(), get()) } viewModel { (dataRaw: String, requestRaw: String?) -> LuckDropViewModel(dataRaw, requestRaw, get(), get()) } } 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 eb4c5643..420e56b6 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 @@ -33,7 +33,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext data class AppDisplayData( val key: AppKey, @@ -79,13 +78,12 @@ private val displayDataList = listOf( ) class LabsViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: IAppRepository, private val walletRepository: WalletServices, ) : ViewModel() { init { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.init() } } 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 91c3b6ab..41050ba5 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 @@ -30,10 +30,8 @@ import com.dimension.maskbook.labs.export.model.AppKey import com.dimension.maskbook.labs.repository.IAppRepository import com.dimension.maskbook.labs.repository.IPreferenceRepository import com.dimension.maskbook.wallet.export.WalletServices -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext data class PluginDisplayData( val key: AppKey, @@ -96,7 +94,6 @@ private val displayDataList = listOf( ) class PluginSettingsViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: IAppRepository, private val walletRepository: WalletServices, private val preferenceRepository: IPreferenceRepository, @@ -111,7 +108,6 @@ class PluginSettingsViewModel( ) } } - .flowOn(viewModelCoroutineContext) .asStateIn(viewModelScope, emptyList()) } @@ -120,7 +116,7 @@ class PluginSettingsViewModel( } fun setEnabled(key: AppKey, enabled: Boolean) { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setEnabled(key, enabled) } } @@ -131,7 +127,7 @@ class PluginSettingsViewModel( } fun setShowPluginSettingsTipDialog(bool: Boolean) { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { preferenceRepository.setShowPluginSettingsTipDialog(bool) } } 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 291bcf50..de955730 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt @@ -29,7 +29,6 @@ import com.dimension.maskbook.common.di.scope.appScope import com.dimension.maskbook.common.di.scope.defaultDispatcher import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext -import com.dimension.maskbook.common.di.scope.viewModelCoroutineContext import com.dimension.maskbook.common.retrofit.retrofit import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.setting.data.JSDataSource @@ -123,19 +122,18 @@ object SettingSetup : ModuleSetup { ) } - viewModel { LanguageSettingsViewModel(get(viewModelCoroutineContext), get()) } - viewModel { AppearanceSettingsViewModel(get(viewModelCoroutineContext), get()) } - viewModel { DataSourceSettingsViewModel(get(viewModelCoroutineContext), get()) } - viewModel { PaymentPasswordSettingsViewModel(get(viewModelCoroutineContext), get()) } - viewModel { BackupPasswordSettingsViewModel(get(viewModelCoroutineContext), get()) } - viewModel { BackupLocalViewModel(get(viewModelCoroutineContext), get(), get()) } + viewModel { LanguageSettingsViewModel(get()) } + viewModel { AppearanceSettingsViewModel(get()) } + viewModel { DataSourceSettingsViewModel(get()) } + viewModel { PaymentPasswordSettingsViewModel(get()) } + viewModel { BackupPasswordSettingsViewModel(get()) } + viewModel { BackupLocalViewModel(get(), get()) } viewModel { EmailSetupViewModel(get(), get()) } viewModel { PhoneSetupViewModel(get(), get()) } viewModel { EmailBackupViewModel(get()) } viewModel { PhoneBackupViewModel(get()) } viewModel { (onDone: () -> Unit, url: String, account: String) -> BackupMergeConfirmViewModel( - get(viewModelCoroutineContext), get(), get(), get().contentResolver, onDone, url, account ) } 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 ee40e9d5..8e4a38c0 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 @@ -26,10 +26,8 @@ import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.model.Appearance import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class AppearanceSettingsViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, ) : ViewModel() { val appearance by lazy { @@ -37,7 +35,7 @@ class AppearanceSettingsViewModel( } fun setAppearance(appearance: Appearance) { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setAppearance(appearance = appearance) } } 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 81e63c09..43d2c1ec 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 @@ -32,10 +32,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class BackupLocalViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, private val backupRepository: BackupRepository, ) : ViewModel() { @@ -74,7 +72,7 @@ class BackupLocalViewModel( _paymentPassword.value = value } - fun save(it: Uri, withWallet: Boolean) = viewModelScope.launch(viewModelCoroutineContext) { + fun save(it: Uri, withWallet: Boolean) = viewModelScope.launch { _state.value = State.Loading try { val password = repository.backupPassword.firstOrNull() ?: run { 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 c9b9d4c8..28bbe8f5 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 @@ -30,10 +30,8 @@ import com.dimension.maskbook.setting.repository.BackupRepository import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class BackupMergeConfirmViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val backupRepository: BackupRepository, private val settingsRepository: ISettingsRepository, private val contentResolver: ContentResolver, @@ -53,7 +51,7 @@ class BackupMergeConfirmViewModel( _passwordValid.value = Validator.isValidBackupPasswordFormat(value) } - fun confirm() = viewModelScope.launch(viewModelCoroutineContext) { + fun confirm() = viewModelScope.launch { _loading.value = true try { contentResolver.openInputStream(Uri.parse(url))?.use { 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 8865a7d2..ef620752 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 @@ -28,10 +28,8 @@ import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class BackupPasswordSettingsViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, ) : ViewModel() { @@ -96,7 +94,7 @@ class BackupPasswordSettingsViewModel( } fun confirm() { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setBackupPassword(newPassword.value) } } 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 d5dada47..a6725d56 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 @@ -26,10 +26,8 @@ import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.model.DataProvider import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class DataSourceSettingsViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, ) : ViewModel() { val dataProvider by lazy { @@ -37,7 +35,7 @@ class DataSourceSettingsViewModel( } fun setDataProvider(dataProvider: DataProvider) { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setDataProvider(dataProvider = dataProvider) } } 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 5cf541af..5d99a971 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 @@ -26,10 +26,8 @@ import com.dimension.maskbook.common.ext.asStateIn import com.dimension.maskbook.setting.export.model.Language import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class LanguageSettingsViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository ) : ViewModel() { val language by lazy { @@ -37,7 +35,7 @@ class LanguageSettingsViewModel( } fun setLanguage(language: Language) { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setLanguage(language = language) } } 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 8925b705..4eedae3d 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 @@ -28,10 +28,8 @@ import com.dimension.maskbook.setting.repository.ISettingsRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch -import kotlin.coroutines.CoroutineContext class PaymentPasswordSettingsViewModel( - private val viewModelCoroutineContext: CoroutineContext, private val repository: ISettingsRepository, ) : ViewModel() { @@ -92,7 +90,7 @@ class PaymentPasswordSettingsViewModel( } fun confirm() { - viewModelScope.launch(viewModelCoroutineContext) { + viewModelScope.launch { repository.setPaymentPassword(newPassword.value) } } From 4485045d3846bfa520af31d1062411b1aa70c976 Mon Sep 17 00:00:00 2001 From: seiko Date: Tue, 19 Apr 2022 17:11:11 +0800 Subject: [PATCH 7/7] add repositoryCoroutineContext --- .../common/gecko/WebContentController.kt | 6 +- .../common/di/module/CoroutinesModule.kt | 4 + .../common/di/scope/CoroutinesQualifiers.kt | 1 + .../dimension/maskbook/entry/EntrySetup.kt | 6 +- ...yRepository.kt => PreferenceRepository.kt} | 2 +- .../com/dimension/maskbook/entry/ui/Router.kt | 4 +- .../entry/viewModel/IntroViewModel.kt | 4 +- .../maskbook/extension/ExtensionSetup.kt | 12 +- .../repository/ExtensionRepository.kt | 39 ++-- .../com/dimension/maskbook/labs/LabsSetup.kt | 10 +- .../maskbook/labs/repository/AppRepository.kt | 5 +- .../persona/export/PersonaServices.kt | 4 +- .../maskbook/persona/PersonaServicesImpl.kt | 4 +- .../maskbook/persona/PersonaSetup.kt | 3 +- .../persona/repository/IPersonaRepository.kt | 12 +- .../persona/repository/PersonaRepository.kt | 39 ++-- .../ui/scenes/avatar/PersonaAvatarModal.kt | 7 +- .../persona/ui/tab/PersonasTabScreen.kt | 9 +- .../viewmodel/RenamePersonaViewModel.kt | 4 +- .../viewmodel/avatar/SetAvatarViewModel.kt | 6 +- .../social/DisconnectSocialViewModel.kt | 6 +- .../social/UserNameModalViewModel.kt | 11 +- .../maskbook/setting/SettingSetup.kt | 6 +- .../setting/repository/BackupRepository.kt | 182 +++++++++--------- .../setting/repository/SettingsRepository.kt | 58 +++--- .../dimension/maskbook/wallet/WalletSetup.kt | 2 - .../wallet/viewmodel/WelcomeViewModel.kt | 42 ---- 27 files changed, 242 insertions(+), 246 deletions(-) rename entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/{EntryRepository.kt => PreferenceRepository.kt} (98%) delete mode 100644 wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/WelcomeViewModel.kt diff --git a/common/gecko/src/androidMain/kotlin/com/dimension/maskbook/common/gecko/WebContentController.kt b/common/gecko/src/androidMain/kotlin/com/dimension/maskbook/common/gecko/WebContentController.kt index b3bc6bc2..e71ce3d6 100644 --- a/common/gecko/src/androidMain/kotlin/com/dimension/maskbook/common/gecko/WebContentController.kt +++ b/common/gecko/src/androidMain/kotlin/com/dimension/maskbook/common/gecko/WebContentController.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest @@ -66,7 +67,7 @@ private class MessageHolder : MessageHandler { private val _message = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE) val message = _message.asSharedFlow() private val _port = MutableStateFlow(null) - val connected = _port.map { it != null } + val connected = _port.asStateFlow().map { it != null } override fun onPortConnected(port: Port) { _port.tryEmit(port) } @@ -111,7 +112,8 @@ class WebContentController( Log.i(TAG, "onBackgroundMessage: $it") it } - val isExtensionConnected = _backgroundMessageHolder.connected + val isExtensionConnected get() = _backgroundMessageHolder.connected + private val runtime by lazy { GeckoRuntime.create(context) } diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt index be4bb17b..1adaab4c 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/module/CoroutinesModule.kt @@ -26,6 +26,7 @@ import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.mainDispatcher import com.dimension.maskbook.common.di.scope.mainImmediateDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext +import com.dimension.maskbook.common.di.scope.repositoryCoroutineContext import com.dimension.maskbook.common.util.coroutineExceptionHandler import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -43,6 +44,9 @@ internal fun Module.coroutinesModule() { single(preferenceCoroutineContext) { NonCancellable + Dispatchers.Default } + single(repositoryCoroutineContext) { + coroutineExceptionHandler + Dispatchers.Default + } single(appScope) { CoroutineScope( diff --git a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt index 0807a99c..87e053fb 100644 --- a/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt +++ b/common/src/androidMain/kotlin/com/dimension/maskbook/common/di/scope/CoroutinesQualifiers.kt @@ -28,5 +28,6 @@ val mainDispatcher = named("MainDispatcher") val mainImmediateDispatcher = named("MainImmediateDispatcher") val preferenceCoroutineContext = named("PreferenceCoroutineContext") +val repositoryCoroutineContext = named("RepositoryCoroutineContext") val appScope = named("AppScope") 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 6002efbb..e61ece26 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/EntrySetup.kt @@ -29,7 +29,7 @@ import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext import com.dimension.maskbook.common.route.Navigator import com.dimension.maskbook.entry.data.JSMethod -import com.dimension.maskbook.entry.repository.EntryRepository +import com.dimension.maskbook.entry.repository.PreferenceRepository import com.dimension.maskbook.entry.repository.entryDataStore import com.dimension.maskbook.entry.ui.scene.generatedRoute import com.dimension.maskbook.entry.viewModel.IntroViewModel @@ -49,7 +49,7 @@ object EntrySetup : ModuleSetup { override fun dependencyInject() = module { single { - EntryRepository( + PreferenceRepository( get(preferenceCoroutineContext), get().entryDataStore ) @@ -63,9 +63,9 @@ object EntrySetup : ModuleSetup { override fun onExtensionReady(koin: Koin) { val appScope = koin.get(appScope) val ioDispatcher = koin.get(ioDispatcher) - val jsMethod = koin.get() appScope.launch(ioDispatcher) { + val jsMethod = koin.get() merge( jsMethod.openCreateWalletView(), jsMethod.openDashboardView(), diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/PreferenceRepository.kt similarity index 98% rename from entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt rename to entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/PreferenceRepository.kt index f6fa28a2..28399954 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/EntryRepository.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/repository/PreferenceRepository.kt @@ -34,7 +34,7 @@ import kotlin.coroutines.CoroutineContext private val ShouldShowEntryKey = booleanPreferencesKey("ShouldShowEntry") val Context.entryDataStore: DataStore by preferencesDataStore(name = "entry") -class EntryRepository( +class PreferenceRepository( private val preferenceCoroutineScope: CoroutineContext, private val dataStore: DataStore, ) { 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 59e09ffd..5b592a24 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 @@ -34,7 +34,7 @@ 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 +import com.dimension.maskbook.entry.repository.PreferenceRepository import com.dimension.maskbook.entry.route.EntryRoute import com.dimension.maskbook.extension.ExtensionSetup import com.dimension.maskbook.labs.LabsSetup @@ -92,7 +92,7 @@ fun Router( } private suspend fun getInitialRoute(): String { - val repository = KoinPlatformTools.defaultContext().get().get() + val repository = KoinPlatformTools.defaultContext().get().get() val shouldShowEntry = repository.shouldShowEntry.firstOrNull() ?: true if (shouldShowEntry) { return EntryRoute.Intro diff --git a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt index 794a2a7d..fd1a8941 100644 --- a/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt +++ b/entry/src/androidMain/kotlin/com/dimension/maskbook/entry/viewModel/IntroViewModel.kt @@ -22,11 +22,11 @@ package com.dimension.maskbook.entry.viewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.dimension.maskbook.entry.repository.EntryRepository +import com.dimension.maskbook.entry.repository.PreferenceRepository import kotlinx.coroutines.launch class IntroViewModel( - private val repository: EntryRepository, + private val repository: PreferenceRepository, ) : ViewModel() { fun setShouldShowEntry(value: Boolean) { 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 c0faa154..23d7829e 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/ExtensionSetup.kt @@ -30,6 +30,7 @@ import androidx.navigation.navDeepLink import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope import com.dimension.maskbook.common.di.scope.ioDispatcher +import com.dimension.maskbook.common.di.scope.repositoryCoroutineContext import com.dimension.maskbook.common.ext.navigateToHome import com.dimension.maskbook.common.gecko.WebContentController import com.dimension.maskbook.common.route.Deeplinks @@ -76,10 +77,10 @@ object ExtensionSetup : ModuleSetup { WebContentController(get(), get(appScope), get(ioDispatcher)) } single { - ExtensionRepository(get()) - } - single { - ExtensionServicesImpl(get(), get(), get()) + ExtensionRepository( + get(repositoryCoroutineContext), + get(), + ) } single { BackgroundMessageChannel(get()) @@ -87,6 +88,9 @@ object ExtensionSetup : ModuleSetup { single { ContentMessageChannel(get()) } + single { + ExtensionServicesImpl(get(), get(), get()) + } } override fun onExtensionReady(koin: Koin) { diff --git a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt index fa441bb4..f56c4416 100644 --- a/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt +++ b/extension/src/androidMain/kotlin/com/dimension/maskbook/extension/repository/ExtensionRepository.kt @@ -24,29 +24,21 @@ import com.dimension.maskbook.common.gecko.WebContentController import com.dimension.maskbook.extension.export.model.Site import com.dimension.maskbook.extension.ext.site import com.dimension.maskbook.extension.ext.url -import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext -@OptIn(InternalCoroutinesApi::class) class ExtensionRepository( + private val repositoryCoroutineContext: CoroutineContext, private val controller: WebContentController, ) { private val _currentSite = MutableStateFlow(Site.Twitter) - val currentSite = _currentSite.asSharedFlow() - suspend fun setCurrentSite(site: Site) { - _currentSite.value = site - // workaround for this case:set current site to Twitter first, then set current site to facebook, - // then go back to twitter tab, currentSite's value is still facebook, if we set current - // site to facebook again, _currentSite won't update due to MutableStateFlow won't emit - // same value twice - if (controller.url.firstOrNull()?.site != _currentSite.value) { - controller.loadUrl(_currentSite.value.url) - } - } - val isExtensionConnected = controller.isExtensionConnected + val currentSite = _currentSite.asStateFlow() + + val isExtensionConnected get() = controller.isExtensionConnected + init { controller.installExtensions( id = "info@dimension.com", @@ -55,12 +47,21 @@ class ExtensionRepository( controller.onNavigate = { onNavigate(it) } + controller.loadUrl(currentSite.value.url) } - suspend fun startCollect() { - isExtensionConnected.first { it } - controller.loadUrl(_currentSite.value.url) + suspend fun setCurrentSite(site: Site) = withContext(repositoryCoroutineContext) { + _currentSite.value = site + // workaround for this case:set current site to Twitter first, then set current site to facebook, + // then go back to twitter tab, currentSite's value is still facebook, if we set current + // site to facebook again, _currentSite won't update due to MutableStateFlow won't emit + // same value twice + if (controller.url.firstOrNull()?.site != _currentSite.value) { + controller.loadUrl(_currentSite.value.url) + } + } + suspend fun startCollect() { _currentSite.collect { controller.loadUrl(it.url) } 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 a10f578b..2f0563ee 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/LabsSetup.kt @@ -27,6 +27,7 @@ import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext +import com.dimension.maskbook.common.di.scope.repositoryCoroutineContext import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.labs.data.JSMethod import com.dimension.maskbook.labs.data.RedPacketMethod @@ -55,15 +56,18 @@ object LabsSetup : ModuleSetup { } override fun dependencyInject() = module { - single { - AppRepository(get()) - } single { PreferenceRepository( get().labsDataStore, get(preferenceCoroutineContext), ) } + single { + AppRepository( + get(repositoryCoroutineContext), + get(), + ) + } single { JSMethod(get()) } diff --git a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt index eaae8524..b30037f4 100644 --- a/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt +++ b/labs/src/androidMain/kotlin/com/dimension/maskbook/labs/repository/AppRepository.kt @@ -25,8 +25,11 @@ import com.dimension.maskbook.labs.export.model.AppData import com.dimension.maskbook.labs.export.model.AppKey import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext internal class AppRepository( + private val repositoryCoroutineContext: CoroutineContext, private val jsMethod: JSMethod, ) : IAppRepository { @@ -44,7 +47,7 @@ internal class AppRepository( } } - override suspend fun setEnabled(appKey: AppKey, enabled: Boolean) { + override suspend fun setEnabled(appKey: AppKey, enabled: Boolean) = withContext(repositoryCoroutineContext) { jsMethod.setPluginStatus(appKey.id, enabled) refreshApps() } diff --git a/persona/export/src/commonMain/kotlin/com/dimension/maskbook/persona/export/PersonaServices.kt b/persona/export/src/commonMain/kotlin/com/dimension/maskbook/persona/export/PersonaServices.kt index 150fe07c..7a7a1993 100644 --- a/persona/export/src/commonMain/kotlin/com/dimension/maskbook/persona/export/PersonaServices.kt +++ b/persona/export/src/commonMain/kotlin/com/dimension/maskbook/persona/export/PersonaServices.kt @@ -30,10 +30,10 @@ import kotlinx.coroutines.flow.Flow interface PersonaServices { val currentPersona: Flow suspend fun hasPersona(): Boolean - fun updateCurrentPersona(value: String) + suspend fun updateCurrentPersona(value: String) suspend fun createPersonaFromMnemonic(value: List, name: String) suspend fun createPersonaFromPrivateKey(value: String, name: String) - fun connectProfile(personaId: String, profileId: String) + suspend fun connectProfile(personaId: String, profileId: String) suspend fun createPersonaBackup(hasPrivateKeyOnly: Boolean): List suspend fun restorePersonaBackup(persona: List) suspend fun createProfileBackup(): List diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaServicesImpl.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaServicesImpl.kt index 790db5af..610997ff 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaServicesImpl.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaServicesImpl.kt @@ -40,7 +40,7 @@ class PersonaServicesImpl( return personaRepository.hasPersona() } - override fun updateCurrentPersona(value: String) { + override suspend fun updateCurrentPersona(value: String) { personaRepository.updateCurrentPersona(value) } @@ -52,7 +52,7 @@ class PersonaServicesImpl( personaRepository.createPersonaFromPrivateKey(value, name) } - override fun connectProfile(personaId: String, profileId: String) { + override suspend fun connectProfile(personaId: String, profileId: String) { personaRepository.connectProfile(personaId, profileId) } 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 48a11379..150308ff 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/PersonaSetup.kt @@ -31,6 +31,7 @@ import com.dimension.maskbook.common.di.scope.appScope import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.mainDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext +import com.dimension.maskbook.common.di.scope.repositoryCoroutineContext import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.persona.data.JSMethod import com.dimension.maskbook.persona.data.JSMethodV2 @@ -124,8 +125,8 @@ object PersonaSetup : ModuleSetup { single { PersonaRepository( get(appScope), + get(repositoryCoroutineContext), get(mainDispatcher), - get(ioDispatcher), get(), get(), get(), get(), get(), get(), get(), diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPersonaRepository.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPersonaRepository.kt index bccade31..cb63e06e 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPersonaRepository.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/IPersonaRepository.kt @@ -41,16 +41,16 @@ interface IPersonaRepository { ) suspend fun logout() suspend fun setCurrentPersona(id: String) - fun updatePersona(id: String, nickname: String) - fun updateCurrentPersona(nickname: String) - fun connectProfile(personaId: String, profileId: String) - fun disconnectProfile(personaId: String, profileId: String) + suspend fun updatePersona(id: String, nickname: String) + suspend fun updateCurrentPersona(nickname: String) + suspend fun connectProfile(personaId: String, profileId: String) + suspend fun disconnectProfile(personaId: String, profileId: String) suspend fun createPersonaFromMnemonic(value: List, name: String) suspend fun createPersonaFromPrivateKey(value: String, name: String) suspend fun backupPrivateKey(id: String): String suspend fun init() - fun setPlatform(platformType: PlatformType) - fun setAvatarForCurrentPersona(avatar: Uri?) + suspend fun setPlatform(platformType: PlatformType) + suspend fun setAvatarForCurrentPersona(avatar: Uri?) suspend fun createPersonaBackup(hasPrivateKeyOnly: Boolean): List suspend fun restorePersonaBackup(list: List) suspend fun createProfileBackup(): List diff --git a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt index e29eadcf..d7924fe7 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/repository/PersonaRepository.kt @@ -52,11 +52,12 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext internal class PersonaRepository( private val appScope: CoroutineScope, + private val repositoryCoroutineContext: CoroutineContext, private val mainDispatcher: CoroutineDispatcher, - private val ioDispatcher: CoroutineDispatcher, private val jsMethod: JSMethod, private val extensionServices: ExtensionServices, private val preferenceRepository: IPreferenceRepository, @@ -111,7 +112,7 @@ internal class PersonaRepository( .filterNot { personaDataSource.hasConnected(it) } .flatMapLatest { profileDataSource.getSocialFlow(it) } .filterNotNull() - .flowOn(ioDispatcher) + .flowOn(repositoryCoroutineContext) .onEach { onDone.invoke(ConnectAccountData(personaId, it)) connectingJob?.cancel() @@ -135,7 +136,7 @@ internal class PersonaRepository( } override suspend fun setCurrentPersona(id: String) { - withContext(ioDispatcher) { + withContext(repositoryCoroutineContext) { if (id.isEmpty() || personaDataSource.getPersona(id) != null) { preferenceRepository.setCurrentPersonaIdentifier(id) jsMethod.setCurrentPersonaIdentifier(id) @@ -144,7 +145,7 @@ internal class PersonaRepository( } override suspend fun logout() { - withContext(ioDispatcher) { + withContext(repositoryCoroutineContext) { val deletePersona = currentPersona.firstOrNull() ?: return@withContext // set current persona first ,avoid currentPersona emmit null if there has other personas val newCurrentPersona = personaDataSource.getPersonaList().firstOrNull { @@ -157,35 +158,35 @@ internal class PersonaRepository( } } - override fun updatePersona(id: String, nickname: String) { - appScope.launch(ioDispatcher) { + override suspend fun updatePersona(id: String, nickname: String) { + withContext(repositoryCoroutineContext) { personaDataSource.updateNickName(id, nickname) jsMethod.updatePersonaInfo(id, nickname) } } - override fun updateCurrentPersona(nickname: String) { - appScope.launch(ioDispatcher) { - val id = currentPersona.firstOrNull()?.identifier ?: return@launch + override suspend fun updateCurrentPersona(nickname: String) { + withContext(repositoryCoroutineContext) { + val id = currentPersona.firstOrNull()?.identifier ?: return@withContext personaDataSource.updateNickName(id, nickname) jsMethod.updatePersonaInfo(id, nickname) } } - override fun connectProfile(personaId: String, profileId: String) { - appScope.launch(ioDispatcher) { + override suspend fun connectProfile(personaId: String, profileId: String) { + withContext(repositoryCoroutineContext) { jsMethod.connectProfile(personaId, profileId) } } - override fun disconnectProfile(personaId: String, profileId: String) { - appScope.launch(ioDispatcher) { + override suspend fun disconnectProfile(personaId: String, profileId: String) { + withContext(repositoryCoroutineContext) { jsMethod.disconnectProfile(profileId) } } override suspend fun createPersonaFromMnemonic(value: List, name: String) { - withContext(ioDispatcher) { + withContext(repositoryCoroutineContext) { val mnemonic = value.joinToString(" ") if (personaDataSource.containsMnemonic(mnemonic)) { throw PersonaAlreadyExitsError() @@ -195,7 +196,7 @@ internal class PersonaRepository( } override suspend fun createPersonaFromPrivateKey(value: String, name: String) { - withContext(ioDispatcher) { + withContext(repositoryCoroutineContext) { if (personaDataSource.containsPrivateKey(value)) throw PersonaAlreadyExitsError() jsMethod.restoreFromPrivateKey(privateKey = value, nickname = name) } @@ -205,14 +206,14 @@ internal class PersonaRepository( return jsMethod.backupPrivateKey(id) ?: "" } - override fun setPlatform(platformType: PlatformType) { - appScope.launch { + override suspend fun setPlatform(platformType: PlatformType) { + withContext(repositoryCoroutineContext) { extensionServices.setSite(platformType.toSite()) } } - override fun setAvatarForCurrentPersona(avatar: Uri?) { - appScope.launch(ioDispatcher) { + override suspend fun setAvatarForCurrentPersona(avatar: Uri?) { + withContext(repositoryCoroutineContext) { currentPersona.firstOrNull()?.let { personaData -> personaDataSource.updateAvatar(personaData.identifier, avatar?.toString()) } 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..2e3ad98c 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 @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -37,6 +38,7 @@ 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 kotlinx.coroutines.launch import org.koin.androidx.compose.get @NavGraphDestination( @@ -50,6 +52,7 @@ fun PersonaAvatarModal( @Back onBack: () -> Unit, ) { val repository = get() + val scope = rememberCoroutineScope() MaskModal { Column( verticalArrangement = Arrangement.spacedBy(16.dp) @@ -65,7 +68,9 @@ fun PersonaAvatarModal( ) MaskListItemButton( onClick = { - repository.setAvatarForCurrentPersona(null) + scope.launch { + repository.setAvatarForCurrentPersona(null) + } onBack.invoke() }, text = { 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 0bcbf96b..a16fe946 100644 --- a/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt +++ b/persona/src/androidMain/kotlin/com/dimension/maskbook/persona/ui/tab/PersonasTabScreen.kt @@ -23,6 +23,7 @@ package com.dimension.maskbook.persona.ui.tab import android.net.Uri import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavController import com.dimension.maskbook.common.ext.navigateToExtension import com.dimension.maskbook.common.route.CommonRoute @@ -35,6 +36,7 @@ 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 kotlinx.coroutines.launch import org.koin.androidx.compose.get class PersonasTabScreen : TabScreen { @@ -46,6 +48,7 @@ class PersonasTabScreen : TabScreen { @Composable override fun Content(navController: NavController) { val repository = get() + val scope = rememberCoroutineScope() PersonaScene( onBack = { navController.navigateToExtension(null) @@ -84,8 +87,10 @@ class PersonasTabScreen : TabScreen { }, onSocialItemClick = { _, social -> social.network.toPlatform()?.let { - repository.setPlatform(it) - navController.navigateToExtension() + scope.launch { + repository.setPlatform(it) + navController.navigateToExtension() + } } }, onAddPersonaAvatar = { 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..0d147d34 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 @@ -48,6 +48,8 @@ class RenamePersonaViewModel( } fun confirm() { - personaRepository.updatePersona(personaId, _name.value) + viewModelScope.launch { + personaRepository.updatePersona(personaId, _name.value) + } } } 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..3ebb1bb8 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 @@ -22,8 +22,10 @@ package com.dimension.maskbook.persona.viewmodel.avatar import android.net.Uri import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.dimension.maskbook.persona.repository.IPersonaRepository import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch class SetAvatarViewModel( private val repository: IPersonaRepository, @@ -33,6 +35,8 @@ class SetAvatarViewModel( } fun setAvatar(avatar: Uri) { - repository.setAvatarForCurrentPersona(avatar) + viewModelScope.launch { + repository.setAvatarForCurrentPersona(avatar) + } } } 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..4beeb3ec 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 @@ -21,12 +21,16 @@ package com.dimension.maskbook.persona.viewmodel.social import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.dimension.maskbook.persona.repository.IPersonaRepository +import kotlinx.coroutines.launch class DisconnectSocialViewModel( private val repository: IPersonaRepository, ) : ViewModel() { fun disconnectProfile(personaId: String, socialId: String) { - repository.disconnectProfile(personaId, socialId) + viewModelScope.launch { + repository.disconnectProfile(personaId, socialId) + } } } 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..ac87182b 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 @@ -26,6 +26,7 @@ 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 kotlinx.coroutines.launch class UserNameModalViewModel( private val personaRepository: IPersonaRepository, @@ -40,9 +41,11 @@ class UserNameModalViewModel( } fun done(personaId: String, profileName: String) { - personaRepository.connectProfile( - personaId, - socialProfile.copy(userId = profileName).toString(), - ) + viewModelScope.launch { + personaRepository.connectProfile( + personaId, + socialProfile.copy(userId = profileName).toString(), + ) + } } } 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 de955730..0905e517 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/SettingSetup.kt @@ -26,9 +26,9 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.navigation import com.dimension.maskbook.common.ModuleSetup import com.dimension.maskbook.common.di.scope.appScope -import com.dimension.maskbook.common.di.scope.defaultDispatcher import com.dimension.maskbook.common.di.scope.ioDispatcher import com.dimension.maskbook.common.di.scope.preferenceCoroutineContext +import com.dimension.maskbook.common.di.scope.repositoryCoroutineContext import com.dimension.maskbook.common.retrofit.retrofit import com.dimension.maskbook.common.ui.tab.TabScreen import com.dimension.maskbook.setting.data.JSDataSource @@ -83,7 +83,7 @@ object SettingSetup : ModuleSetup { } single { SettingsRepository( - get(ioDispatcher), + get(repositoryCoroutineContext), get(), get(), get(), @@ -92,10 +92,10 @@ object SettingSetup : ModuleSetup { } single { BackupRepository( + get(repositoryCoroutineContext), get(), get().cacheDir, get().contentResolver, - get(defaultDispatcher), ) } single { diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt index 566a82f3..94014581 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/BackupRepository.kt @@ -35,8 +35,6 @@ import com.dimension.maskbook.setting.services.model.Scenario import com.dimension.maskbook.setting.services.model.SendCodeBody import com.dimension.maskbook.setting.services.model.UploadBody import com.dimension.maskbook.setting.services.model.ValidateCodeBody -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext import kotlinx.serialization.builtins.serializer import okhttp3.OkHttpClient @@ -51,68 +49,61 @@ import java.util.UUID import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec +import kotlin.coroutines.CoroutineContext class BackupRepository( + private val repositoryCoroutineContext: CoroutineContext, private val backupServices: BackupServices, private val cacheDir: File, private val contentResolver: ContentResolver, - private val dispatcher: CoroutineDispatcher, ) { - suspend fun sendPhoneCode(phone: String) { - withContext(dispatcher) { - backupServices.sendCode( - SendCodeBody( - account_type = AccountType.phone, - account = phone, - Scenario.backup, - Locale.en, - ) + suspend fun sendPhoneCode(phone: String) = withContext(repositoryCoroutineContext) { + backupServices.sendCode( + SendCodeBody( + account_type = AccountType.phone, + account = phone, + Scenario.backup, + Locale.en, ) - } + ) } - suspend fun sendEmailCode(email: String) { - withContext(dispatcher) { - backupServices.sendCode( - SendCodeBody( - account_type = AccountType.email, - account = email, - Scenario.backup, - Locale.en, - ) + suspend fun sendEmailCode(email: String) = withContext(repositoryCoroutineContext) { + backupServices.sendCode( + SendCodeBody( + account_type = AccountType.email, + account = email, + Scenario.backup, + Locale.en, ) - } + ) } - suspend fun validatePhoneCode(phone: String, code: String) { - withContext(dispatcher) { - backupServices.validateCode( - ValidateCodeBody( - code = code, - account = phone, - account_type = AccountType.phone, - ) + suspend fun validatePhoneCode(phone: String, code: String) = withContext(repositoryCoroutineContext) { + backupServices.validateCode( + ValidateCodeBody( + code = code, + account = phone, + account_type = AccountType.phone, ) - } + ) } - suspend fun validateEmailCode(email: String, code: String) { - withContext(dispatcher) { - backupServices.validateCode( - ValidateCodeBody( - code = code, - account = email, - account_type = AccountType.email, - ) + suspend fun validateEmailCode(email: String, code: String) = withContext(repositoryCoroutineContext) { + backupServices.validateCode( + ValidateCodeBody( + code = code, + account = email, + account_type = AccountType.email, ) - } + ) } suspend fun encryptBackup( password: String, account: String, - content: BackupMetaFile - ): ByteArray = coroutineScope { + content: BackupMetaFile, + ): ByteArray = withContext(repositoryCoroutineContext) { val computedPassword = account.lowercase() + password val iv = SecureRandom().generateSeed(16) val gen = PKCS5S2ParametersGenerator(SHA256Digest()) @@ -130,61 +121,62 @@ class BackupRepository( ).toByteArray() } - suspend fun decryptBackup(password: String, account: String, data: ByteArray): BackupMetaFile = - coroutineScope { - val computedPassword = account.lowercase() + password - val remoteBackupData = RemoteBackupData.fromByteArray(data) - val gen = PKCS5S2ParametersGenerator(SHA256Digest()) - gen.init( - msgPack.encodeToByteArray(String.serializer(), computedPassword), - remoteBackupData.pbkdf2IV, - 10000 - ) - val derivedKey = gen.generateDerivedParameters(256) as KeyParameter - val key = SecretKeySpec(derivedKey.key, "AES") - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(remoteBackupData.paramIV)) - cipher.doFinal(remoteBackupData.encrypted) - .let { msgPack.decodeFromByteArray(BackupMetaFile.serializer(), it) } - } + suspend fun decryptBackup( + password: String, + account: String, + data: ByteArray, + ): BackupMetaFile = withContext(repositoryCoroutineContext) { + val computedPassword = account.lowercase() + password + val remoteBackupData = RemoteBackupData.fromByteArray(data) + val gen = PKCS5S2ParametersGenerator(SHA256Digest()) + gen.init( + msgPack.encodeToByteArray(String.serializer(), computedPassword), + remoteBackupData.pbkdf2IV, + 10000 + ) + val derivedKey = gen.generateDerivedParameters(256) as KeyParameter + val key = SecretKeySpec(derivedKey.key, "AES") + val cipher = Cipher.getInstance("AES/GCM/NoPadding") + cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(remoteBackupData.paramIV)) + cipher.doFinal(remoteBackupData.encrypted) + .let { msgPack.decodeFromByteArray(BackupMetaFile.serializer(), it) } + } - suspend fun downloadBackupWithEmail(email: String, code: String) = - withContext(dispatcher) { - val response = backupServices.download( - ValidateCodeBody( - code = code, - account = email, - account_type = AccountType.email, - ) - ) - requireNotNull(response.download_url) - BackupFileMeta( - url = downloadFile(response.download_url).toUri().toString(), - size = response.size, - uploaded_at = (response.uploaded_at ?: 0) * 1000, - abstract = response.abstract, + suspend fun downloadBackupWithEmail(email: String, code: String) = withContext(repositoryCoroutineContext) { + val response = backupServices.download( + ValidateCodeBody( + code = code, + account = email, + account_type = AccountType.email, ) - } + ) + requireNotNull(response.download_url) + BackupFileMeta( + url = downloadFile(response.download_url).toUri().toString(), + size = response.size, + uploaded_at = (response.uploaded_at ?: 0) * 1000, + abstract = response.abstract, + ) + } - suspend fun downloadBackupWithPhone(phone: String, code: String) = - withContext(dispatcher) { - val response = backupServices.download( - ValidateCodeBody( - code = code, - account = phone, - account_type = AccountType.phone, - ) - ) - requireNotNull(response.download_url) - BackupFileMeta( - url = downloadFile(response.download_url).toUri().toString(), - size = response.size, - uploaded_at = (response.uploaded_at ?: 0) * 1000, - abstract = response.abstract, + suspend fun downloadBackupWithPhone(phone: String, code: String) = withContext(repositoryCoroutineContext) { + val response = backupServices.download( + ValidateCodeBody( + code = code, + account = phone, + account_type = AccountType.phone, ) - } + ) + requireNotNull(response.download_url) + BackupFileMeta( + url = downloadFile(response.download_url).toUri().toString(), + size = response.size, + uploaded_at = (response.uploaded_at ?: 0) * 1000, + abstract = response.abstract, + ) + } - private suspend fun downloadFile(url: String) = withContext(dispatcher) { + private suspend fun downloadFile(url: String) = withContext(repositoryCoroutineContext) { val stream = OkHttpClient.Builder() .build() .newCall( @@ -212,7 +204,7 @@ class BackupRepository( account: String, abstract: String, content: BackupMetaFile, - ) = withContext(dispatcher) { + ) = withContext(repositoryCoroutineContext) { val response = backupServices.upload( UploadBody( code = code, @@ -233,7 +225,7 @@ class BackupRepository( } suspend fun saveLocality(it: Uri, meta: BackupMetaFile, password: String, account: String) = - coroutineScope { + withContext(repositoryCoroutineContext) { contentResolver.openOutputStream(it)?.use { it.write(encryptBackup(password, account, meta)) } diff --git a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt index 175b1540..4bcddd26 100644 --- a/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt +++ b/setting/src/androidMain/kotlin/com/dimension/maskbook/setting/repository/SettingsRepository.kt @@ -40,12 +40,12 @@ import com.dimension.maskbook.setting.model.mapping.toIndexedDBProfile import com.dimension.maskbook.setting.model.mapping.toIndexedDBRelation import com.dimension.maskbook.setting.model.mapping.toWalletData import com.dimension.maskbook.wallet.export.WalletServices -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext internal class SettingsRepository( - private val ioDispatcher: CoroutineDispatcher, + private val repositoryCoroutineContext: CoroutineContext, private val personaServices: PersonaServices, private val settingDataSource: SettingDataSource, private val jsDataSource: JSDataSource, @@ -70,36 +70,36 @@ internal class SettingsRepository( override val phone: Flow get() = settingDataSource.phone - override suspend fun setBiometricEnabled(value: Boolean) { + override suspend fun setBiometricEnabled(value: Boolean) = withContext(repositoryCoroutineContext) { settingDataSource.setBiometricEnabled(value) } - override suspend fun setLanguage(language: Language) { + override suspend fun setLanguage(language: Language) = withContext(repositoryCoroutineContext) { jsDataSource.setLanguage(language) } - override suspend fun setAppearance(appearance: Appearance) { + override suspend fun setAppearance(appearance: Appearance) = withContext(repositoryCoroutineContext) { jsDataSource.setAppearance(appearance) } - override suspend fun setDataProvider(dataProvider: DataProvider) { + override suspend fun setDataProvider(dataProvider: DataProvider) = withContext(repositoryCoroutineContext) { jsDataSource.setDataProvider(dataProvider) } - override suspend fun setPaymentPassword(value: String) { + override suspend fun setPaymentPassword(value: String) = withContext(repositoryCoroutineContext) { settingDataSource.setPaymentPassword(value) } - override suspend fun setBackupPassword(value: String) { + override suspend fun setBackupPassword(value: String) = withContext(repositoryCoroutineContext) { settingDataSource.setBackupPassword(value) } - override suspend fun setShouldShowLegalScene(value: Boolean) { + override suspend fun setShouldShowLegalScene(value: Boolean) = withContext(repositoryCoroutineContext) { settingDataSource.setShouldShowLegalScene(value) } - override suspend fun generateBackupMeta(): BackupMeta { - return createBackup( + override suspend fun generateBackupMeta(): BackupMeta = withContext(repositoryCoroutineContext) { + createBackup( noPosts = false, noWallets = false, noPersonas = false, @@ -110,8 +110,10 @@ internal class SettingsRepository( } } - override suspend fun provideBackupMeta(file: BackupMetaFile): BackupMeta { - return BackupMeta( + override suspend fun provideBackupMeta( + file: BackupMetaFile, + ): BackupMeta = withContext(repositoryCoroutineContext) { + BackupMeta( personas = file.personas.size, associatedAccount = file.personas.sumOf { it.linkedProfiles.size }, encryptedPost = file.posts.size, @@ -123,7 +125,7 @@ internal class SettingsRepository( ) } - override suspend fun restoreBackup(value: BackupMetaFile) = withContext(ioDispatcher) { + override suspend fun restoreBackup(value: BackupMetaFile) = withContext(repositoryCoroutineContext) { val persona = value.personas.map { it.toIndexedDBPersona() } personaServices.restorePersonaBackup(persona) val profile = value.profiles.map { it.toIndexedDBProfile() } @@ -143,7 +145,7 @@ internal class SettingsRepository( noProfiles: Boolean, noRelations: Boolean, hasPrivateKeyOnly: Boolean - ): BackupMetaFile = withContext(ioDispatcher) { + ): BackupMetaFile = withContext(repositoryCoroutineContext) { val personas = if (noPersonas) { emptyList() } else { @@ -180,41 +182,43 @@ internal class SettingsRepository( ) } - private suspend fun backupRelations(): List { - return personaServices.createRelationsBackup().map { + private suspend fun backupRelations(): List = withContext(repositoryCoroutineContext) { + personaServices.createRelationsBackup().map { it.toBackupRelation() } } - private suspend fun backupPosts(): List { - return personaServices.createPostsBackup().map { + private suspend fun backupPosts(): List = withContext(repositoryCoroutineContext) { + personaServices.createPostsBackup().map { it.toBackupPost() } } - private suspend fun backupWallets(): List { - return walletServices.createWalletBackup().map { + private suspend fun backupWallets(): List = withContext(repositoryCoroutineContext) { + walletServices.createWalletBackup().map { it.toBackupWallet() } } - private suspend fun backProfiles(): List { - return personaServices.createProfileBackup().map { + private suspend fun backProfiles(): List = withContext(repositoryCoroutineContext) { + personaServices.createProfileBackup().map { it.toBackupProfile() } } - private suspend fun backupPersona(hasPrivateKeyOnly: Boolean): List { - return personaServices.createPersonaBackup(hasPrivateKeyOnly).map { + private suspend fun backupPersona( + hasPrivateKeyOnly: Boolean + ): List = withContext(repositoryCoroutineContext) { + personaServices.createPersonaBackup(hasPrivateKeyOnly).map { it.toBackupPersona() } } - override suspend fun saveEmail(value: String) { + override suspend fun saveEmail(value: String) = withContext(repositoryCoroutineContext) { settingDataSource.setRegisterEmail(value) } - override suspend fun savePhone(value: String) { + override suspend fun savePhone(value: String) = withContext(repositoryCoroutineContext) { settingDataSource.setRegisterPhone(value) } } 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 2648589e..f7cd608f 100644 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt +++ b/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/WalletSetup.kt @@ -77,7 +77,6 @@ import com.dimension.maskbook.wallet.usecase.SendTransactionUseCase import com.dimension.maskbook.wallet.usecase.SendWalletCollectibleUseCase import com.dimension.maskbook.wallet.usecase.SetCurrentChainUseCase import com.dimension.maskbook.wallet.usecase.VerifyPaymentPasswordUseCase -import com.dimension.maskbook.wallet.viewmodel.WelcomeViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.TokenDetailViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.TouchIdEnableViewModel import com.dimension.maskbook.wallet.viewmodel.wallets.UnlockWalletViewModel @@ -298,7 +297,6 @@ private fun Module.provideUseCase() { } private fun Module.provideViewModel() { - viewModel { WelcomeViewModel(get()) } viewModel { (wallet: String) -> CreateWalletRecoveryKeyViewModel(wallet, get()) } viewModel { TouchIdEnableViewModel() } viewModel { (wallet: String) -> ImportWalletKeystoreViewModel(wallet, get()) } 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 deleted file mode 100644 index a8d4ba07..00000000 --- a/wallet/src/androidMain/kotlin/com/dimension/maskbook/wallet/viewmodel/WelcomeViewModel.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Mask-Android - * - * Copyright (C) 2022 DimensionDev and Contributors - * - * This file is part of Mask-Android. - * - * Mask-Android is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Mask-Android is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Mask-Android. If not, see . - */ -package 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 - -class WelcomeViewModel( - private val personaServices: PersonaServices, -) : ViewModel() { - private val _persona = MutableStateFlow("") - val persona = _persona.asStateIn(viewModelScope, "") - - fun setPersona(text: String) { - _persona.value = text - } - - fun onConfirm() { - personaServices.updateCurrentPersona(_persona.value) - } -}