Skip to content

Feature/manage coroutineScope #207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"[email protected]",
"resource://android/assets/extensions/borderify/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@ 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
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
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
Expand Down Expand Up @@ -64,7 +67,7 @@ private class MessageHolder : MessageHandler {
private val _message = MutableSharedFlow<JSONObject>(extraBufferCapacity = Int.MAX_VALUE)
val message = _message.asSharedFlow()
private val _port = MutableStateFlow<Port?>(null)
val connected = _port.map { it != null }
val connected = _port.asStateFlow().map { it != null }
override fun onPortConnected(port: Port) {
_port.tryEmit(port)
}
Expand All @@ -91,6 +94,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<BrowserState?>(null)
Expand All @@ -108,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)
}
Expand Down Expand Up @@ -189,12 +194,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)
}
)
}
Expand Down Expand Up @@ -233,7 +238,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,23 @@ 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 {
override fun NavGraphBuilder.route(navController: NavController) {
}

override fun dependencyInject() = module {
single(named(IoScopeName)) {
CoroutineScope(SupervisorJob() + Dispatchers.IO + coroutineExceptionHandler)
}
coroutinesModule()

single { BiometricAuthenticator() }
single { KeyStoreManager(get()) }
single { ImageLoaderManager(get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@
*/
package com.dimension.maskbook.common

const val IoScopeName = "IoScope"

const val LocalBackupAccount = ""
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
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.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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.SupervisorJob
import org.koin.core.module.Module

internal fun Module.coroutinesModule() {
single(defaultDispatcher) { Dispatchers.Default }
single(ioDispatcher) { Dispatchers.IO }
single<CoroutineDispatcher>(mainDispatcher) { Dispatchers.Main }
single<CoroutineDispatcher>(mainImmediateDispatcher) { Dispatchers.Main.immediate }

single(preferenceCoroutineContext) {
NonCancellable + Dispatchers.Default
}
single(repositoryCoroutineContext) {
coroutineExceptionHandler + Dispatchers.Default
}

single(appScope) {
CoroutineScope(
coroutineExceptionHandler + SupervisorJob()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Mask-Android
*
* Copyright (C) 2022 DimensionDev and Contributors
*
* This file is part of Mask-Android.
*
* Mask-Android is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Mask-Android is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Mask-Android. If not, see <http://www.gnu.org/licenses/>.
*/
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 preferenceCoroutineContext = named("PreferenceCoroutineContext")
val repositoryCoroutineContext = named("RepositoryCoroutineContext")

val appScope = named("AppScope")
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ 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

class BiometricEnableViewModel(
private val biometricAuthenticator: BiometricAuthenticator,
Expand All @@ -40,13 +42,19 @@ class BiometricEnableViewModel(
context = context,
onSuccess = {
onEnable.invoke()
repository.setBiometricEnabled(true)
setBiometricEnabled(true)
},
title = title,
subTitle = subTitle,
negativeButtonText = negativeButton,
)
}

fun setBiometricEnabled(enabled: Boolean) {
viewModelScope.launch {
repository.setBiometricEnabled(enabled)
}
}

fun isSupported(context: Context) = biometricAuthenticator.canAuthenticate(context = context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ 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

class SetUpPaymentPasswordViewModel(
private val repository: SettingServices,
Expand Down Expand Up @@ -55,6 +56,8 @@ class SetUpPaymentPasswordViewModel(
}

fun confirm() {
repository.setPaymentPassword(newPassword.value)
viewModelScope.launch {
repository.setPaymentPassword(newPassword.value)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,44 +24,57 @@ 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.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
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.androidx.viewmodel.dsl.viewModel
import org.koin.core.Koin
import org.koin.dsl.module
import org.koin.mp.KoinPlatformTools

object EntrySetup : ModuleSetup {
override fun NavGraphBuilder.route(navController: NavController) {
generatedRoute(navController)
}

override fun dependencyInject() = module {
single { EntryRepository(get<Context>().entryDataStore) }
single { JSMethod(get()) }
single {
PreferenceRepository(
get(preferenceCoroutineContext),
get<Context>().entryDataStore
)
}
single {
JSMethod(get())
}
viewModel { IntroViewModel(get()) }
}

override fun onExtensionReady() {
KoinPlatformTools.defaultContext().get().get<JSMethod>().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<CoroutineScope>(appScope)
val ioDispatcher = koin.get<CoroutineDispatcher>(ioDispatcher)

appScope.launch(ioDispatcher) {
val jsMethod = koin.get<JSMethod>()
merge(
jsMethod.openCreateWalletView(),
jsMethod.openDashboardView(),
jsMethod.openAppsView(),
jsMethod.openSettingsView(),
).filter { uri ->
uri.isNotEmpty()
}.collect { uri ->
Navigator.deeplink(uri)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,25 @@ 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.CoroutineScope
import kotlinx.coroutines.Dispatchers
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<Preferences> by preferencesDataStore(name = "entry")

class EntryRepository(
class PreferenceRepository(
private val preferenceCoroutineScope: CoroutineContext,
private val dataStore: DataStore<Preferences>,
) {
private val scope = CoroutineScope(Dispatchers.IO)
val shouldShowEntry: Flow<Boolean>
get() = dataStore.data.map {
it[ShouldShowEntryKey] ?: true
}

fun setShouldShowEntry(shouldShowEntry: Boolean) {
scope.launch {
suspend fun setShouldShowEntry(shouldShowEntry: Boolean) {
withContext(preferenceCoroutineScope) {
dataStore.edit {
it[ShouldShowEntryKey] = shouldShowEntry
}
Expand Down
Loading