diff --git a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/OpenOtpApp.kt b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/OpenOtpApp.kt index 53b0837..0d02986 100644 --- a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/OpenOtpApp.kt +++ b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/OpenOtpApp.kt @@ -1,14 +1,20 @@ package ml.dev.kotlin.openotp import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.slide import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.serialization.builtins.ListSerializer +import ml.dev.kotlin.openotp.component.LinkedAccountsSyncState.NothingToSync import ml.dev.kotlin.openotp.component.OpenOtpAppComponent import ml.dev.kotlin.openotp.component.OpenOtpAppComponent.Child import ml.dev.kotlin.openotp.component.UserLinkedAccountsModel @@ -16,7 +22,10 @@ import ml.dev.kotlin.openotp.component.UserPreferencesModel import ml.dev.kotlin.openotp.otp.OtpData import ml.dev.kotlin.openotp.ui.screen.* import ml.dev.kotlin.openotp.ui.theme.OpenOtpTheme -import ml.dev.kotlin.openotp.util.* +import ml.dev.kotlin.openotp.util.BindBiometryAuthenticatorEffect +import ml.dev.kotlin.openotp.util.BiometryAuthenticator +import ml.dev.kotlin.openotp.util.OnceLaunchedEffect +import ml.dev.kotlin.openotp.util.StateFlowSettings import org.koin.compose.koinInject import org.koin.core.context.startKoin import org.koin.core.module.Module @@ -65,6 +74,8 @@ internal fun OpenOtpApp(component: OpenOtpAppComponent) { internal val USER_OTP_CODE_DATA_MODULE_QUALIFIER: StringQualifier = named("userOtpCodeDataModule") +internal val LINKED_ACCOUNTS_SYNC_STATE_MODULE_QUALIFIER: StringQualifier = named("linkedAccountsSyncStateModule") + internal val USER_PREFERENCES_MODULE_QUALIFIER: StringQualifier = named("userPreferencesModule") internal val USER_LINKED_ACCOUNTS_MODULE_QUALIFIER: StringQualifier = named("userLinkedAccountsModule") @@ -75,6 +86,7 @@ internal fun initOpenOtpKoin(appDeclaration: KoinAppDeclaration = {}) { appDeclaration() modules(module { userOtpCodeDataModule() + linkedAccountsSyncStateModule() userPreferencesModule() userLinkedAccountsModule() snackbarHostStateModule() @@ -93,6 +105,12 @@ private fun Module.userOtpCodeDataModule() { } } +private fun Module.linkedAccountsSyncStateModule() { + single(LINKED_ACCOUNTS_SYNC_STATE_MODULE_QUALIFIER) { + MutableStateFlow(NothingToSync) + } +} + private fun Module.userPreferencesModule() { single(USER_PREFERENCES_MODULE_QUALIFIER) { StateFlowSettings( diff --git a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/AbstractBackupComponent.kt b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/AbstractBackupComponent.kt new file mode 100644 index 0000000..85713ee --- /dev/null +++ b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/AbstractBackupComponent.kt @@ -0,0 +1,103 @@ +package ml.dev.kotlin.openotp.component + +import com.arkivanov.decompose.ComponentContext +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableStateFlow +import ml.dev.kotlin.openotp.LINKED_ACCOUNTS_SYNC_STATE_MODULE_QUALIFIER +import ml.dev.kotlin.openotp.USER_LINKED_ACCOUNTS_MODULE_QUALIFIER +import ml.dev.kotlin.openotp.USER_OTP_CODE_DATA_MODULE_QUALIFIER +import ml.dev.kotlin.openotp.backup.* +import ml.dev.kotlin.openotp.backup.SerializedStoredOtpCodeDataBackup.Companion.toSerializedBackup +import ml.dev.kotlin.openotp.otp.StoredOtpCodeData +import ml.dev.kotlin.openotp.shared.OpenOtpResources +import ml.dev.kotlin.openotp.util.StateFlowSettings +import ml.dev.kotlin.openotp.util.unit +import org.koin.core.component.get + +abstract class AbstractBackupComponent( + componentContext: ComponentContext, +) : AbstractComponent(componentContext) { + + protected val _userOtpCodeData: StateFlowSettings = + get(USER_OTP_CODE_DATA_MODULE_QUALIFIER) + + protected val _linkedAccountsSyncState: MutableStateFlow = + get(LINKED_ACCOUNTS_SYNC_STATE_MODULE_QUALIFIER) + + protected val _userLinkedAccounts: StateFlowSettings = + get(USER_LINKED_ACCOUNTS_MODULE_QUALIFIER) + + protected fun refreshBackup(download: Boolean = false) { + val linkedAccounts = _userLinkedAccounts.stateFlow.value + val syncAccountsCount = UserLinkedAccountType.entries.count { it.isLinked(linkedAccounts) } + + if (syncAccountsCount == 0) { + _linkedAccountsSyncState.value = LinkedAccountsSyncState.NothingToSync + return + } + _linkedAccountsSyncState.value = LinkedAccountsSyncState.Refreshing + + scope.launch { + var success = true + try { + if (download) { + downloadBackup(linkedAccounts) + } + val backup = _userOtpCodeData.stateFlow.value.toSerializedBackup() + uploadBackup(linkedAccounts, backup) + } catch (_: Exception) { + success = false + } finally { + if (!success) _linkedAccountsSyncState.value = LinkedAccountsSyncState.NotSynced + } + } + } + + private suspend fun uploadBackup( + linkedAccounts: UserLinkedAccountsModel, + backup: SerializedStoredOtpCodeDataBackup, + ) { + supervisorScope { + val jobs = mutableListOf>() + for (accountType in UserLinkedAccountType.entries) { + val service = accountType.createAuthenticatedService(linkedAccounts) ?: continue + jobs += async { service.uploadBackup(backup) } + } + val results = jobs.awaitAll() + when { + results.isEmpty() -> Unit + + results.all { it } -> { + _linkedAccountsSyncState.value = LinkedAccountsSyncState.Synced + toast(stringResource(OpenOtpResources.strings.synced_all)) + } + + results.any { it } -> { + _linkedAccountsSyncState.value = LinkedAccountsSyncState.Synced + toast(stringResource(OpenOtpResources.strings.failed_some)) + } + + else -> { + _linkedAccountsSyncState.value = LinkedAccountsSyncState.NotSynced + toast(stringResource(OpenOtpResources.strings.failed_all)) + } + } + } + } + + private suspend fun downloadBackup( + linkedAccounts: UserLinkedAccountsModel, + ) { + val currentCodeData = _userOtpCodeData.stateFlow.value + val updatedCodeData = supervisorScope { + val jobs = mutableListOf>() + for (accountType in UserLinkedAccountType.entries) { + val service = accountType.createAuthenticatedService(linkedAccounts) ?: continue + jobs += async { service.downloadBackup() } + } + val downloadedData = jobs.awaitAll().filterNotNull() + downloadedData.merge(current = currentCodeData) + } + _userOtpCodeData.updateInScope { updatedCodeData }.unit() + } +} diff --git a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/AddTotpProviderComponent.kt b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/AddTotpProviderComponent.kt index 022849d..652c3cb 100644 --- a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/AddTotpProviderComponent.kt +++ b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/AddTotpProviderComponent.kt @@ -4,13 +4,10 @@ import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.value.Value import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update -import ml.dev.kotlin.openotp.USER_OTP_CODE_DATA_MODULE_QUALIFIER import ml.dev.kotlin.openotp.otp.* import ml.dev.kotlin.openotp.shared.OpenOtpResources -import ml.dev.kotlin.openotp.util.StateFlowSettings import ml.dev.kotlin.openotp.util.isValidBase32Secret import ml.dev.kotlin.openotp.util.unit -import org.koin.core.component.get interface AddOtpProviderComponent { @@ -49,9 +46,7 @@ abstract class AddOtpProviderComponentImpl( componentContext: ComponentContext, private val navigateOnSaveClicked: () -> Unit, private val navigateOnCancelClicked: () -> Unit, -) : AbstractComponent(componentContext), AddOtpProviderComponent { - - protected val secureStorage: StateFlowSettings = get(USER_OTP_CODE_DATA_MODULE_QUALIFIER) +) : AbstractBackupComponent(componentContext), AddOtpProviderComponent { protected fun notifyInvalid(fieldName: String) { toast(message = stringResource(OpenOtpResources.strings.invalid_field_name_provided_formatted, fieldName)) @@ -137,7 +132,7 @@ class AddTotpProviderComponentImpl( val config = TotpConfig(period, digits, algorithm) val codeData = TotpData(issuer, accountName, secret, config) - secureStorage + _userOtpCodeData .updateInScope { it + codeData } .invokeOnCompletion { super.onSaveClicked() @@ -229,7 +224,7 @@ class AddHotpProviderComponentImpl( val config = HotpConfig(digits, algorithm) val codeData = HotpData(issuer, accountName, secret, counter, config) - secureStorage + _userOtpCodeData .updateInScope { it + codeData } .invokeOnCompletion { super.onSaveClicked() diff --git a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/LinkAccountComponent.kt b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/LinkAccountComponent.kt index b76928d..0c2abcc 100644 --- a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/LinkAccountComponent.kt +++ b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/LinkAccountComponent.kt @@ -6,10 +6,7 @@ import com.arkivanov.decompose.value.Value import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import ml.dev.kotlin.openotp.USER_LINKED_ACCOUNTS_MODULE_QUALIFIER import ml.dev.kotlin.openotp.backup.OAuth2AccountService -import ml.dev.kotlin.openotp.util.StateFlowSettings -import org.koin.core.component.get interface LinkAccountComponent { @@ -28,10 +25,7 @@ class LinkAccountComponentImpl( private val accountType: UserLinkedAccountType, componentContext: ComponentContext, private val navigateOnCancel: () -> Unit, -) : AbstractComponent(componentContext), LinkAccountComponent { - - private val userLinkedAccounts: StateFlowSettings = - get(USER_LINKED_ACCOUNTS_MODULE_QUALIFIER) +) : AbstractBackupComponent(componentContext), LinkAccountComponent { private val requestedPermissions: MutableStateFlow = MutableStateFlow(null) @@ -70,7 +64,7 @@ class LinkAccountComponentImpl( requestedPermissions.authenticateUser(code.value) .onSuccess { authenticated -> - userLinkedAccounts + _userLinkedAccounts .updateInScope(this, authenticated::updateUserLinkedAccounts) .invokeOnCompletion { _isLoadingAppPermissions.update { false } diff --git a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/MainComponent.kt b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/MainComponent.kt index 4f364db..532ea62 100644 --- a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/MainComponent.kt +++ b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/MainComponent.kt @@ -7,19 +7,13 @@ import androidx.compose.ui.text.toLowerCase import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.value.Value import com.arkivanov.essenty.backhandler.BackCallback -import kotlinx.coroutines.* +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.updateAndGet -import ml.dev.kotlin.openotp.USER_LINKED_ACCOUNTS_MODULE_QUALIFIER -import ml.dev.kotlin.openotp.USER_OTP_CODE_DATA_MODULE_QUALIFIER +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import ml.dev.kotlin.openotp.USER_PREFERENCES_MODULE_QUALIFIER -import ml.dev.kotlin.openotp.backup.SerializedStoredOtpCodeDataBackup.Companion.toSerializedBackup -import ml.dev.kotlin.openotp.backup.StoredOtpCodeDataBackup -import ml.dev.kotlin.openotp.backup.downloadBackup -import ml.dev.kotlin.openotp.backup.merge -import ml.dev.kotlin.openotp.backup.uploadBackup -import ml.dev.kotlin.openotp.component.LinkedAccountsSyncState.* import ml.dev.kotlin.openotp.otp.* import ml.dev.kotlin.openotp.shared.OpenOtpResources import ml.dev.kotlin.openotp.ui.component.DragDropListData @@ -70,7 +64,7 @@ class MainComponentImpl( private val navigateOnAddProvider: () -> Unit, private val navigateSettings: () -> Unit, private val updateTimeDelay: Duration = 10.milliseconds, -) : AbstractComponent(componentContext), MainComponent { +) : AbstractBackupComponent(componentContext), MainComponent { private data class CameraPermissionRequest( val count: Int = 0, @@ -79,15 +73,9 @@ class MainComponentImpl( private val appContext: OpenOtpAppComponentContext = get() - private val userOtpCodeData: StateFlowSettings = - get(USER_OTP_CODE_DATA_MODULE_QUALIFIER) - private val userPreferences: StateFlowSettings = get(USER_PREFERENCES_MODULE_QUALIFIER) - private val userLinkedAccounts: StateFlowSettings = - get(USER_LINKED_ACCOUNTS_MODULE_QUALIFIER) - private val _timestamp: MutableStateFlow = MutableStateFlow(currentEpochMilliseconds()) override val timestamp: Value = _timestamp.asValue() @@ -101,7 +89,7 @@ class MainComponentImpl( _cameraPermissionRequest.map { it.changed }.asValue() override val codeData: Value = combine( - userOtpCodeData.stateFlow, + _userOtpCodeData.stateFlow, userPreferences.stateFlow.map { it.sortOtpDataBy }, userPreferences.stateFlow.map { it.sortOtpDataReversed }, userPreferences.stateFlow.map { it.sortOtpDataNullsFirst }, @@ -112,8 +100,7 @@ class MainComponentImpl( private val _isSearchActive: MutableStateFlow = MutableStateFlow(false) override val isSearchActive: Value = _isSearchActive.asValue() - private val _isLinkAccountsSynced: MutableStateFlow = MutableStateFlow(NothingToSync) - override val linkedAccountsSyncState: Value = _isLinkAccountsSynced.asValue() + override val linkedAccountsSyncState: Value = _linkedAccountsSyncState.asValue() override val isDragAndDropEnabled: Value = userPreferences.stateFlow.map { it.sortOtpDataBy == SortOtpDataBy.Dont }.asValue() @@ -134,7 +121,7 @@ class MainComponentImpl( } } scope.launch { - userLinkedAccounts.stateFlow.collect { + _userLinkedAccounts.stateFlow.collect { onRefresh() } } @@ -150,14 +137,17 @@ class MainComponentImpl( } override fun onOtpCodeDataRemove(otpData: OtpData): Boolean { - userOtpCodeData.updateInScope { before -> - before.filter { it != otpData } - }.unit() + _userOtpCodeData + .updateInScope { before -> + before.filter { it != otpData } + }.invokeOnCompletion { + refreshBackup(download = false) + } return true } override fun onOtpCodeDataReordered(currentIndex: Int, updatedIndex: Int) { - userOtpCodeData.updateInScope { + _userOtpCodeData.updateInScope { buildList { addAll(it) set(currentIndex, it[updatedIndex]) @@ -168,7 +158,7 @@ class MainComponentImpl( override fun onOtpCodeDataRestart(otpData: OtpData) = when (otpData) { is TotpData -> Unit - is HotpData -> userOtpCodeData.updateInScope { before -> + is HotpData -> _userOtpCodeData.updateInScope { before -> val updated = otpData.increaseCounter() before.map { if (it != otpData) it else updated } }.unit() @@ -203,79 +193,8 @@ class MainComponentImpl( } override fun onRefresh() { - val linkedAccounts = userLinkedAccounts.stateFlow.value - val syncAccountsCount = UserLinkedAccountType.entries.count { it.isLinked(linkedAccounts) } - - if (syncAccountsCount == 0) { - _isLinkAccountsSynced.value = NothingToSync - return - } - _isLinkAccountsSynced.value = Refreshing - - scope.launch { - var success = true - try { - downloadBackup(linkedAccounts) - uploadBackup(linkedAccounts) - } catch (_: Exception) { - success = false - } finally { - if (!success) _isLinkAccountsSynced.value = NotSynced - } - } + refreshBackup(download = true) } - - private suspend fun uploadBackup(linkedAccounts: UserLinkedAccountsModel) { - val backup = userOtpCodeData.stateFlow.value.toSerializedBackup() - - supervisorScope { - val jobs = mutableListOf>() - for (accountType in UserLinkedAccountType.entries) { - val service = accountType.createAuthenticatedService(linkedAccounts) ?: continue - jobs += async { service.uploadBackup(backup) } - } - val results = jobs.awaitAll() - when { - results.isEmpty() -> Unit - - results.all { it } -> { - _isLinkAccountsSynced.value = Synced - toast(stringResource(OpenOtpResources.strings.synced_all)) - } - - results.any { it } -> { - _isLinkAccountsSynced.value = Synced - toast(stringResource(OpenOtpResources.strings.failed_some)) - } - - else -> { - _isLinkAccountsSynced.value = NotSynced - toast(stringResource(OpenOtpResources.strings.failed_all)) - } - } - } - } - - private suspend fun downloadBackup(linkedAccounts: UserLinkedAccountsModel) { - val currentCodeData = userOtpCodeData.stateFlow.value - val updatedCodeData = supervisorScope { - val jobs = mutableListOf>() - for (accountType in UserLinkedAccountType.entries) { - val service = accountType.createAuthenticatedService(linkedAccounts) ?: continue - jobs += async { service.downloadBackup() } - } - val downloadedData = jobs.awaitAll().filterNotNull() - downloadedData.merge(current = currentCodeData) - } - userOtpCodeData.updateInScope { updatedCodeData } - } -} - -enum class LinkedAccountsSyncState { - Synced, Refreshing, NotSynced, NothingToSync; - - val isSyncAvailable: Boolean get() = this != NothingToSync - val isRefreshing: Boolean get() = this == Refreshing } private const val MAX_CAMERA_PERMISSION_SILENT_REQUESTS: Int = 2 diff --git a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/OpenOtpAppComponent.kt b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/OpenOtpAppComponent.kt index 2937212..4b2c6bc 100644 --- a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/OpenOtpAppComponent.kt +++ b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/OpenOtpAppComponent.kt @@ -151,3 +151,11 @@ class OpenOtpAppComponentImpl( data class LinkAccount(val accountType: UserLinkedAccountType) : Config } } + +enum class LinkedAccountsSyncState { + Synced, Refreshing, NotSynced, NothingToSync; + + val isSyncAvailable: Boolean get() = this != NothingToSync + + val isRefreshing: Boolean get() = this == Refreshing +} diff --git a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/ScanQRCodeComponent.kt b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/ScanQRCodeComponent.kt index e25ce02..2c6f049 100644 --- a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/ScanQRCodeComponent.kt +++ b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/ScanQRCodeComponent.kt @@ -5,13 +5,10 @@ import com.eygraber.uri.Uri import `in`.procyk.compose.camera.qr.QRResult import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch -import ml.dev.kotlin.openotp.USER_OTP_CODE_DATA_MODULE_QUALIFIER import ml.dev.kotlin.openotp.otp.* import ml.dev.kotlin.openotp.shared.OpenOtpResources -import ml.dev.kotlin.openotp.util.StateFlowSettings import ml.dev.kotlin.openotp.util.isValidBase32Secret import ml.dev.kotlin.openotp.util.letFalse -import org.koin.core.component.get interface ScanQRCodeComponent { @@ -23,9 +20,7 @@ interface ScanQRCodeComponent { class ScanQRCodeComponentImpl( componentContext: ComponentContext, private val navigateOnCancel: (message: String?) -> Unit, -) : AbstractComponent(componentContext), ScanQRCodeComponent { - - private val userOtpCodeData: StateFlowSettings = get(USER_OTP_CODE_DATA_MODULE_QUALIFIER) +) : AbstractBackupComponent(componentContext), ScanQRCodeComponent { override fun onQRCodeScanned(result: QRResult): Boolean = when (result) { is QRResult.QRError -> navigateOnCancel(invalidQRCodeMessage).letFalse() @@ -36,7 +31,7 @@ class ScanQRCodeComponentImpl( navigateOnCancel(invalidQRCodeMessage) } else { val updated = nonNullOtpData.map { otpData -> - userOtpCodeData.updateInScope { it + otpData } + _userOtpCodeData.updateInScope { it + otpData } } scope .launch { updated.awaitAll() } diff --git a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/SettingsComponent.kt b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/SettingsComponent.kt index 9458146..a44af0c 100644 --- a/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/SettingsComponent.kt +++ b/shared/src/commonMain/kotlin/ml/dev/kotlin/openotp/component/SettingsComponent.kt @@ -3,7 +3,6 @@ package ml.dev.kotlin.openotp.component import androidx.compose.runtime.Composable import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.value.Value -import ml.dev.kotlin.openotp.USER_LINKED_ACCOUNTS_MODULE_QUALIFIER import ml.dev.kotlin.openotp.USER_PREFERENCES_MODULE_QUALIFIER import ml.dev.kotlin.openotp.component.SettingsComponentImpl.LinkedAccountState import ml.dev.kotlin.openotp.shared.OpenOtpResources @@ -46,14 +45,11 @@ class SettingsComponentImpl( componentContext: ComponentContext, private val navigateOnLinkAccount: (UserLinkedAccountType) -> Unit, private val navigateOnExit: () -> Unit, -) : AbstractComponent(componentContext), SettingsComponent { +) : AbstractBackupComponent(componentContext), SettingsComponent { private val userPreferences: StateFlowSettings = get(USER_PREFERENCES_MODULE_QUALIFIER) - private val userLinkedAccounts: StateFlowSettings = - get(USER_LINKED_ACCOUNTS_MODULE_QUALIFIER) - private val authenticator: BiometryAuthenticator = get() override val theme: Value = @@ -83,7 +79,7 @@ class SettingsComponentImpl( override val isAuthenticationAvailable: Boolean get() = authenticator.isBiometricAvailable() - override val linkedAccountsStates: Value> = userLinkedAccounts + override val linkedAccountsStates: Value> = _userLinkedAccounts .stateFlow .map { linkedAccounts -> UserLinkedAccountType.entries.map { accountType -> @@ -138,8 +134,7 @@ class SettingsComponentImpl( get() = stringResource(OpenOtpResources.strings.unlink_account_button_name) override fun onClick() { - userLinkedAccounts - .updateInScope { accountType.reset(it) } + _userLinkedAccounts.updateInScope { accountType.reset(it) } } } @@ -148,7 +143,7 @@ class SettingsComponentImpl( get() = stringResource(OpenOtpResources.strings.link_account_button_name) override fun onClick() { - userLinkedAccounts + _userLinkedAccounts .updateInScope { accountType.reset(it) } .invokeOnCompletion { navigateOnLinkAccount(accountType)