From c166a175c75b781860d044c165dbe5e2498db3fe Mon Sep 17 00:00:00 2001 From: Subhradeep Bera <124783808+beradeep@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:37:45 +0530 Subject: [PATCH] Fix issue 2625 (#2793) * Migrate screen-onboarding to ComposeViewModel and new architecture * Fix and format the code * Incorporate suggested changes --- config/detekt/config.yml | 4 +- .../ivy/onboarding/OnboardingDetailState.kt | 18 ++ .../com/ivy/onboarding/OnboardingEvent.kt | 26 ++ .../com/ivy/onboarding/OnboardingScreen.kt | 137 +++------- .../com/ivy/onboarding/OnboardingState.kt | 3 + .../onboarding/steps/OnboardingSplashLogin.kt | 28 +- .../onboarding/viewmodel/OnboardingRouter.kt | 66 ++--- .../viewmodel/OnboardingViewModel.kt | 247 +++++++----------- 8 files changed, 221 insertions(+), 308 deletions(-) create mode 100644 screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingDetailState.kt create mode 100644 screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingEvent.kt diff --git a/config/detekt/config.yml b/config/detekt/config.yml index 279850b169..0f1545c87d 100644 --- a/config/detekt/config.yml +++ b/config/detekt/config.yml @@ -106,7 +106,7 @@ complexity: ignoreOverloaded: false CyclomaticComplexMethod: active: true - threshold: 15 + threshold: 20 ignoreSingleWhenExpression: false ignoreSimpleWhenEntries: false ignoreNestingFunctions: false @@ -128,7 +128,7 @@ complexity: threshold: 600 LongMethod: active: true - threshold: 120 + threshold: 150 LongParameterList: active: true functionThreshold: 12 diff --git a/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingDetailState.kt b/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingDetailState.kt new file mode 100644 index 0000000000..1b6cfc6bf1 --- /dev/null +++ b/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingDetailState.kt @@ -0,0 +1,18 @@ +package com.ivy.onboarding + +import androidx.compose.runtime.Immutable +import com.ivy.legacy.data.model.AccountBalance +import com.ivy.legacy.datamodel.Category +import com.ivy.wallet.domain.data.IvyCurrency +import com.ivy.wallet.domain.deprecated.logic.model.CreateAccountData +import com.ivy.wallet.domain.deprecated.logic.model.CreateCategoryData +import kotlinx.collections.immutable.ImmutableList + +@Immutable +data class OnboardingDetailState( + val currency: IvyCurrency, + val accounts: ImmutableList<AccountBalance>, + val accountSuggestions: ImmutableList<CreateAccountData>, + val categories: ImmutableList<Category>, + val categorySuggestions: ImmutableList<CreateCategoryData> +) diff --git a/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingEvent.kt b/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingEvent.kt new file mode 100644 index 0000000000..a699b4f704 --- /dev/null +++ b/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingEvent.kt @@ -0,0 +1,26 @@ +package com.ivy.onboarding + +import com.ivy.legacy.datamodel.Account +import com.ivy.legacy.datamodel.Category +import com.ivy.wallet.domain.data.IvyCurrency +import com.ivy.wallet.domain.deprecated.logic.model.CreateAccountData +import com.ivy.wallet.domain.deprecated.logic.model.CreateCategoryData + +sealed interface OnboardingEvent { + + data object LoginWithGoogle : OnboardingEvent + data object LoginOfflineAccount : OnboardingEvent + data object StartImport : OnboardingEvent + data object ImportSkip : OnboardingEvent + data class ImportFinished(val success: Boolean) : OnboardingEvent + data object StartFresh : OnboardingEvent + data class SetBaseCurrency(val baseCurrency: IvyCurrency) : OnboardingEvent + data class EditAccount(val account: Account, val newBalance: Double) : OnboardingEvent + data class CreateAccount(val data: CreateAccountData) : OnboardingEvent + data object OnAddAccountsDone : OnboardingEvent + data object OnAddAccountsSkip : OnboardingEvent + data class EditCategory(val updatedCategory: Category) : OnboardingEvent + data class CreateCategory(val data: CreateCategoryData) : OnboardingEvent + data object OnAddCategoriesDone : OnboardingEvent + data object OnAddCategoriesSkip : OnboardingEvent +} \ No newline at end of file diff --git a/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingScreen.kt b/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingScreen.kt index 5c8ef9120d..883bd01214 100644 --- a/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingScreen.kt +++ b/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingScreen.kt @@ -5,11 +5,10 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.viewmodel.compose.viewModel -import com.ivy.legacy.IvyWalletPreview import com.ivy.legacy.data.model.AccountBalance +import com.ivy.legacy.datamodel.Category +import com.ivy.legacy.utils.onScreenStart import com.ivy.navigation.OnboardingScreen import com.ivy.onboarding.steps.OnboardingAccounts import com.ivy.onboarding.steps.OnboardingCategories @@ -18,27 +17,17 @@ import com.ivy.onboarding.steps.OnboardingSplashLogin import com.ivy.onboarding.steps.OnboardingType import com.ivy.onboarding.viewmodel.OnboardingViewModel import com.ivy.wallet.domain.data.IvyCurrency -import com.ivy.legacy.datamodel.Account -import com.ivy.legacy.datamodel.Category import com.ivy.wallet.domain.deprecated.logic.model.CreateAccountData import com.ivy.wallet.domain.deprecated.logic.model.CreateCategoryData -import com.ivy.legacy.utils.OpResult -import com.ivy.legacy.utils.onScreenStart +import kotlinx.collections.immutable.ImmutableList @ExperimentalFoundationApi @Composable fun BoxWithConstraintsScope.OnboardingScreen(screen: OnboardingScreen) { val viewModel: OnboardingViewModel = viewModel() - val state by viewModel.state.observeAsState(OnboardingState.SPLASH) - val currency by viewModel.currency.observeAsState(IvyCurrency.getDefault()) - val opGoogleSign by viewModel.opGoogleSignIn.observeAsState() - - val accountSuggestions by viewModel.accountSuggestions.observeAsState(emptyList()) - val accounts by viewModel.accounts.observeAsState(listOf()) - - val categorySuggestions by viewModel.categorySuggestions.observeAsState(emptyList()) - val categories by viewModel.categories.observeAsState(emptyList()) + val state by viewModel.state + val uiState = viewModel.uiState() val isSystemDarkTheme = isSystemInDarkTheme() onScreenStart { @@ -50,32 +39,16 @@ fun BoxWithConstraintsScope.OnboardingScreen(screen: OnboardingScreen) { UI( onboardingState = state, - currency = currency, - opGoogleSignIn = opGoogleSign, + currency = uiState.currency, - accountSuggestions = accountSuggestions, - accounts = accounts, + accountSuggestions = uiState.accountSuggestions, + accounts = uiState.accounts, - categorySuggestions = categorySuggestions, - categories = categories, + categorySuggestions = uiState.categorySuggestions, + categories = uiState.categories, - onLoginWithGoogle = viewModel::loginWithGoogle, - onSkip = viewModel::loginOfflineAccount, + onEvent = viewModel::onEvent - onStartImport = viewModel::startImport, - onStartFresh = viewModel::startFresh, - - onSetCurrency = viewModel::setBaseCurrency, - - onCreateAccount = viewModel::createAccount, - onEditAccount = viewModel::editAccount, - onAddAccountsDone = viewModel::onAddAccountsDone, - onAddAccountsSkip = viewModel::onAddAccountsSkip, - - onCreateCategory = viewModel::createCategory, - onEditCategory = viewModel::editCategory, - onAddCategoryDone = viewModel::onAddCategoriesDone, - onAddCategorySkip = viewModel::onAddCategoriesSkip ) } @@ -84,54 +57,34 @@ fun BoxWithConstraintsScope.OnboardingScreen(screen: OnboardingScreen) { private fun BoxWithConstraintsScope.UI( onboardingState: OnboardingState, currency: IvyCurrency, - opGoogleSignIn: OpResult<Unit>?, - - accountSuggestions: List<CreateAccountData>, - accounts: List<AccountBalance>, - - categorySuggestions: List<CreateCategoryData>, - categories: List<Category>, - onLoginWithGoogle: () -> Unit = {}, - onSkip: () -> Unit = {}, + accountSuggestions: ImmutableList<CreateAccountData>, + accounts: ImmutableList<AccountBalance>, - onStartImport: () -> Unit = {}, - onStartFresh: () -> Unit = {}, + categorySuggestions: ImmutableList<CreateCategoryData>, + categories: ImmutableList<Category>, - onSetCurrency: (IvyCurrency) -> Unit = {}, - - onCreateAccount: (CreateAccountData) -> Unit = { }, - onEditAccount: (Account, Double) -> Unit = { _, _ -> }, - onAddAccountsDone: () -> Unit = {}, - onAddAccountsSkip: () -> Unit = {}, - - onCreateCategory: (CreateCategoryData) -> Unit = {}, - onEditCategory: (Category) -> Unit = {}, - onAddCategoryDone: () -> Unit = {}, - onAddCategorySkip: () -> Unit = {}, + onEvent: (OnboardingEvent) -> Unit = {} ) { when (onboardingState) { OnboardingState.SPLASH, OnboardingState.LOGIN -> { OnboardingSplashLogin( onboardingState = onboardingState, - opGoogleSignIn = opGoogleSignIn, - - onLoginWithGoogle = onLoginWithGoogle, - onSkip = onSkip + onSkip = { onEvent(OnboardingEvent.LoginOfflineAccount) } ) } OnboardingState.CHOOSE_PATH -> { OnboardingType( - onStartImport = onStartImport, - onStartFresh = onStartFresh + onStartImport = { onEvent(OnboardingEvent.StartImport) }, + onStartFresh = { onEvent(OnboardingEvent.StartFresh) } ) } OnboardingState.CURRENCY -> { OnboardingSetCurrency( preselectedCurrency = currency, - onSetCurrency = onSetCurrency + onSetCurrency = { onEvent(OnboardingEvent.SetBaseCurrency(it)) } ) } @@ -141,11 +94,18 @@ private fun BoxWithConstraintsScope.UI( suggestions = accountSuggestions, accounts = accounts, - onCreateAccount = onCreateAccount, - onEditAccount = onEditAccount, - - onDone = onAddAccountsDone, - onSkip = onAddAccountsSkip + onCreateAccount = { onEvent(OnboardingEvent.CreateAccount(it)) }, + onEditAccount = { account, newBalance -> + onEvent( + OnboardingEvent.EditAccount( + account, + newBalance + ) + ) + }, + + onDone = { onEvent(OnboardingEvent.OnAddAccountsDone) }, + onSkip = { onEvent(OnboardingEvent.OnAddAccountsSkip) } ) } @@ -154,35 +114,12 @@ private fun BoxWithConstraintsScope.UI( suggestions = categorySuggestions, categories = categories, - onCreateCategory = onCreateCategory, - onEditCategory = onEditCategory, + onCreateCategory = { onEvent(OnboardingEvent.CreateCategory(it)) }, + onEditCategory = { onEvent(OnboardingEvent.EditCategory(it)) }, - onDone = onAddCategoryDone, - onSkip = onAddCategorySkip + onDone = { onEvent(OnboardingEvent.OnAddCategoriesDone) }, + onSkip = { onEvent(OnboardingEvent.OnAddCategoriesSkip) } ) } } -} - -@ExperimentalFoundationApi -@Preview -@Composable -private fun PreviewOnboarding() { - IvyWalletPreview { - UI( - accountSuggestions = listOf(), - accounts = listOf(), - - categorySuggestions = listOf(), - categories = listOf(), - - onboardingState = OnboardingState.SPLASH, - currency = IvyCurrency.getDefault(), - opGoogleSignIn = null, - - onLoginWithGoogle = {}, - onSkip = {}, - onSetCurrency = {}, - ) - } -} +} \ No newline at end of file diff --git a/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingState.kt b/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingState.kt index 4690259e4b..2bc71cae53 100644 --- a/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingState.kt +++ b/screen-onboarding/src/main/java/com/ivy/onboarding/OnboardingState.kt @@ -1,5 +1,8 @@ package com.ivy.onboarding +import androidx.compose.runtime.Immutable + +@Immutable enum class OnboardingState { SPLASH, LOGIN, diff --git a/screen-onboarding/src/main/java/com/ivy/onboarding/steps/OnboardingSplashLogin.kt b/screen-onboarding/src/main/java/com/ivy/onboarding/steps/OnboardingSplashLogin.kt index af0a3b767d..9c66fea26c 100644 --- a/screen-onboarding/src/main/java/com/ivy/onboarding/steps/OnboardingSplashLogin.kt +++ b/screen-onboarding/src/main/java/com/ivy/onboarding/steps/OnboardingSplashLogin.kt @@ -43,19 +43,12 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.ivy.design.l0_system.UI +import com.ivy.design.l0_system.style import com.ivy.legacy.Constants import com.ivy.legacy.IvyWalletCtx import com.ivy.legacy.IvyWalletPreview import com.ivy.legacy.ivyWalletCtx -import com.ivy.design.l0_system.UI -import com.ivy.design.l0_system.style -import com.ivy.onboarding.OnboardingState -import com.ivy.resources.R -import com.ivy.wallet.ui.theme.Gradient -import com.ivy.wallet.ui.theme.Gray -import com.ivy.wallet.ui.theme.Green -import com.ivy.wallet.ui.theme.components.IvyIcon -import com.ivy.legacy.utils.OpResult import com.ivy.legacy.utils.clickableNoIndication import com.ivy.legacy.utils.drawColoredShadow import com.ivy.legacy.utils.lerp @@ -64,14 +57,17 @@ import com.ivy.legacy.utils.springBounceSlow import com.ivy.legacy.utils.thenIf import com.ivy.legacy.utils.toDensityDp import com.ivy.legacy.utils.toDensityPx +import com.ivy.onboarding.OnboardingState +import com.ivy.resources.R +import com.ivy.wallet.ui.theme.Gradient +import com.ivy.wallet.ui.theme.Gray +import com.ivy.wallet.ui.theme.Green +import com.ivy.wallet.ui.theme.components.IvyIcon import kotlin.math.roundToInt @Composable fun BoxWithConstraintsScope.OnboardingSplashLogin( onboardingState: OnboardingState, - opGoogleSignIn: OpResult<Unit>?, - - onLoginWithGoogle: () -> Unit, onSkip: () -> Unit, ) { var internalSwitch by remember { mutableStateOf(true) } @@ -234,9 +230,6 @@ fun BoxWithConstraintsScope.OnboardingSplashLogin( LoginSection( percentTransition = percentTransition, - - opGoogleSignIn = opGoogleSignIn, - onLoginWithGoogle = onLoginWithGoogle, onSkip = onSkip ) } @@ -264,9 +257,6 @@ private fun Modifier.animateXCenterToLeft( @Composable private fun LoginSection( percentTransition: Float, - opGoogleSignIn: OpResult<Unit>?, - - onLoginWithGoogle: () -> Unit, onSkip: () -> Unit ) { if (percentTransition > 0.01f) { @@ -482,8 +472,6 @@ private fun Preview() { IvyWalletPreview { OnboardingSplashLogin( onboardingState = OnboardingState.SPLASH, - opGoogleSignIn = null, - onLoginWithGoogle = {}, onSkip = {} ) } diff --git a/screen-onboarding/src/main/java/com/ivy/onboarding/viewmodel/OnboardingRouter.kt b/screen-onboarding/src/main/java/com/ivy/onboarding/viewmodel/OnboardingRouter.kt index 8387224123..d65d1e36a3 100644 --- a/screen-onboarding/src/main/java/com/ivy/onboarding/viewmodel/OnboardingRouter.kt +++ b/screen-onboarding/src/main/java/com/ivy/onboarding/viewmodel/OnboardingRouter.kt @@ -1,11 +1,13 @@ package com.ivy.onboarding.viewmodel -import androidx.lifecycle.MutableLiveData -import com.ivy.legacy.datamodel.Category -import com.ivy.legacy.datamodel.temp.toDomain +import androidx.compose.runtime.MutableState +import com.ivy.data.db.dao.read.AccountDao +import com.ivy.data.db.dao.read.CategoryDao import com.ivy.legacy.LogoutLogic import com.ivy.legacy.data.SharedPrefs import com.ivy.legacy.data.model.AccountBalance +import com.ivy.legacy.datamodel.Category +import com.ivy.legacy.datamodel.temp.toDomain import com.ivy.legacy.domain.action.exchange.SyncExchangeRatesAct import com.ivy.legacy.utils.OpResult import com.ivy.legacy.utils.ioThread @@ -14,8 +16,6 @@ import com.ivy.navigation.MainScreen import com.ivy.navigation.Navigation import com.ivy.navigation.OnboardingScreen import com.ivy.onboarding.OnboardingState -import com.ivy.data.db.dao.read.AccountDao -import com.ivy.data.db.dao.read.CategoryDao import com.ivy.wallet.domain.data.IvyCurrency import com.ivy.wallet.domain.deprecated.logic.PreloadDataLogic import com.ivy.wallet.domain.deprecated.logic.model.CreateAccountData @@ -28,12 +28,12 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch class OnboardingRouter( - private val _opGoogleSignIn: MutableLiveData<OpResult<Unit>?>, - private val _state: MutableLiveData<OnboardingState>, - private val _accounts: MutableLiveData<ImmutableList<AccountBalance>>, - private val _accountSuggestions: MutableLiveData<ImmutableList<CreateAccountData>>, - private val _categories: MutableLiveData<ImmutableList<Category>>, - private val _categorySuggestions: MutableLiveData<ImmutableList<CreateCategoryData>>, + private val opGoogleSignIn: MutableState<OpResult<Unit>?>, + private val state: MutableState<OnboardingState>, + private val accounts: MutableState<ImmutableList<AccountBalance>>, + private val accountSuggestions: MutableState<ImmutableList<CreateAccountData>>, + private val categories: MutableState<ImmutableList<Category>>, + private val categorySuggestions: MutableState<ImmutableList<CreateCategoryData>>, private val nav: Navigation, private val accountDao: AccountDao, @@ -53,7 +53,7 @@ class OnboardingRouter( restartOnboarding: () -> Unit ) { nav.onBackPressed[screen] = { - when (_state.value) { + when (state.value) { OnboardingState.SPLASH -> { // do nothing, consume back true @@ -65,7 +65,7 @@ class OnboardingRouter( } OnboardingState.CHOOSE_PATH -> { - _state.value = OnboardingState.LOGIN + state.value = OnboardingState.LOGIN true } @@ -76,22 +76,22 @@ class OnboardingRouter( logoutLogic.logout() isLoginCache = false restartOnboarding() - _state.value = OnboardingState.LOGIN + state.value = OnboardingState.LOGIN } } else { // fresh user - _state.value = OnboardingState.CHOOSE_PATH + state.value = OnboardingState.CHOOSE_PATH } true } OnboardingState.ACCOUNTS -> { - _state.value = OnboardingState.CURRENCY + state.value = OnboardingState.CURRENCY true } OnboardingState.CATEGORIES -> { - _state.value = OnboardingState.ACCOUNTS + state.value = OnboardingState.ACCOUNTS true } @@ -105,10 +105,10 @@ class OnboardingRouter( // ------------------------------------- Step 0 - Splash ---------------------------------------- suspend fun splashNext() { - if (_state.value == OnboardingState.SPLASH) { + if (state.value == OnboardingState.SPLASH) { delay(1000) - _state.value = OnboardingState.LOGIN + state.value = OnboardingState.LOGIN } } // ------------------------------------- Step 0 ------------------------------------------------- @@ -117,10 +117,10 @@ class OnboardingRouter( suspend fun googleLoginNext() { if (isLogin()) { // Route logged user - _state.value = OnboardingState.CURRENCY + state.value = OnboardingState.CURRENCY } else { // Route new user - _state.value = OnboardingState.CHOOSE_PATH + state.value = OnboardingState.CHOOSE_PATH } } @@ -130,7 +130,7 @@ class OnboardingRouter( } suspend fun offlineAccountNext() { - _state.value = OnboardingState.CHOOSE_PATH + state.value = OnboardingState.CHOOSE_PATH } // ------------------------------------- Step 1 ------------------------------------------------- @@ -144,17 +144,17 @@ class OnboardingRouter( } fun importSkip() { - _state.value = OnboardingState.CURRENCY + state.value = OnboardingState.CURRENCY } fun importFinished(success: Boolean) { if (success) { - _state.value = OnboardingState.CURRENCY + state.value = OnboardingState.CURRENCY } } fun startFresh() { - _state.value = OnboardingState.CURRENCY + state.value = OnboardingState.CURRENCY } // ------------------------------------- Step 2 ------------------------------------------------- @@ -208,19 +208,19 @@ class OnboardingRouter( accountsWithBalance: suspend () -> ImmutableList<AccountBalance>, ) { val accounts = accountsWithBalance() - _accounts.value = accounts + this.accounts.value = accounts - _accountSuggestions.value = + accountSuggestions.value = preloadDataLogic.accountSuggestions(baseCurrency.code) - _state.value = OnboardingState.ACCOUNTS + state.value = OnboardingState.ACCOUNTS } private suspend fun routeToCategories() { - _categories.value = + categories.value = ioThread { categoryDao.findAll().map { it.toDomain() }.toImmutableList() }!! - _categorySuggestions.value = preloadDataLogic.categorySuggestions() + categorySuggestions.value = preloadDataLogic.categorySuggestions() - _state.value = OnboardingState.CATEGORIES + state.value = OnboardingState.CATEGORIES } private suspend fun completeOnboarding( @@ -245,8 +245,8 @@ class OnboardingRouter( } private fun resetState() { - _state.value = OnboardingState.SPLASH - _opGoogleSignIn.value = null + state.value = OnboardingState.SPLASH + opGoogleSignIn.value = null } private fun navigateOutOfOnboarding() { diff --git a/screen-onboarding/src/main/java/com/ivy/onboarding/viewmodel/OnboardingViewModel.kt b/screen-onboarding/src/main/java/com/ivy/onboarding/viewmodel/OnboardingViewModel.kt index ab57d8de02..ed8c3c3cbc 100644 --- a/screen-onboarding/src/main/java/com/ivy/onboarding/viewmodel/OnboardingViewModel.kt +++ b/screen-onboarding/src/main/java/com/ivy/onboarding/viewmodel/OnboardingViewModel.kt @@ -1,30 +1,32 @@ package com.ivy.onboarding.viewmodel -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.viewModelScope import com.ivy.base.legacy.Theme -import com.ivy.legacy.datamodel.Account -import com.ivy.legacy.datamodel.Category -import com.ivy.legacy.datamodel.Settings -import com.ivy.frp.test.TestIdlingResource +import com.ivy.data.db.dao.read.AccountDao +import com.ivy.data.db.dao.read.CategoryDao +import com.ivy.data.db.dao.read.SettingsDao +import com.ivy.data.db.dao.write.WriteSettingsDao +import com.ivy.domain.ComposeViewModel import com.ivy.legacy.IvyWalletCtx import com.ivy.legacy.LogoutLogic import com.ivy.legacy.data.SharedPrefs import com.ivy.legacy.data.model.AccountBalance +import com.ivy.legacy.datamodel.Account +import com.ivy.legacy.datamodel.Category +import com.ivy.legacy.datamodel.Settings import com.ivy.legacy.domain.action.exchange.SyncExchangeRatesAct import com.ivy.legacy.domain.deprecated.logic.AccountCreator import com.ivy.legacy.utils.OpResult -import com.ivy.legacy.utils.asLiveData import com.ivy.legacy.utils.ioThread import com.ivy.legacy.utils.sendToCrashlytics import com.ivy.navigation.Navigation import com.ivy.navigation.OnboardingScreen +import com.ivy.onboarding.OnboardingDetailState +import com.ivy.onboarding.OnboardingEvent import com.ivy.onboarding.OnboardingState -import com.ivy.data.db.dao.read.AccountDao -import com.ivy.data.db.dao.read.CategoryDao -import com.ivy.data.db.dao.read.SettingsDao -import com.ivy.data.db.dao.write.WriteSettingsDao import com.ivy.wallet.domain.action.account.AccountsAct import com.ivy.wallet.domain.action.category.CategoriesAct import com.ivy.wallet.domain.data.IvyCurrency @@ -62,36 +64,37 @@ class OnboardingViewModel @Inject constructor( transactionReminderLogic: TransactionReminderLogic, preloadDataLogic: PreloadDataLogic, logoutLogic: LogoutLogic, -) : ViewModel() { - - private val _state = MutableLiveData(OnboardingState.SPLASH) - val state = _state.asLiveData() - - private val _currency = MutableLiveData<IvyCurrency>() - val currency = _currency.asLiveData() - - private val _opGoogleSignIn = MutableLiveData<OpResult<Unit>?>() - val opGoogleSignIn = _opGoogleSignIn.asLiveData() - - private val _accounts = MutableLiveData<ImmutableList<AccountBalance>>() - val accounts = _accounts.asLiveData() - - private val _accountSuggestions = MutableLiveData<ImmutableList<CreateAccountData>>() - val accountSuggestions = _accountSuggestions.asLiveData() - - private val _categories = MutableLiveData<ImmutableList<Category>>() - val categories = _categories.asLiveData() - - private val _categorySuggestions = MutableLiveData<ImmutableList<CreateCategoryData>>() - val categorySuggestions = _categorySuggestions.asLiveData() +) : ComposeViewModel<OnboardingDetailState, OnboardingEvent>() { + + private val _state = mutableStateOf(OnboardingState.SPLASH) + val state: State<OnboardingState> = _state + + private val _currency = mutableStateOf(IvyCurrency.getDefault()) + private val _opGoogleSignIn = mutableStateOf<OpResult<Unit>?>(null) + private val _accounts = mutableStateOf(listOf<AccountBalance>().toImmutableList()) + private val _accountSuggestions = mutableStateOf(listOf<CreateAccountData>().toImmutableList()) + private val _categories = mutableStateOf(listOf<Category>().toImmutableList()) + private val _categorySuggestions = + mutableStateOf(listOf<CreateCategoryData>().toImmutableList()) + + @Composable + override fun uiState(): OnboardingDetailState { + return OnboardingDetailState( + currency = _currency.value, + accounts = _accounts.value, + accountSuggestions = _accountSuggestions.value, + categories = _categories.value, + categorySuggestions = _categorySuggestions.value + ) + } private val router = OnboardingRouter( - _state = _state, - _opGoogleSignIn = _opGoogleSignIn, - _accounts = _accounts, - _accountSuggestions = _accountSuggestions, - _categories = _categories, - _categorySuggestions = _categorySuggestions, + state = _state, + opGoogleSignIn = _opGoogleSignIn, + accounts = _accounts, + accountSuggestions = _accountSuggestions, + categories = _categories, + categorySuggestions = _categorySuggestions, nav = nav, accountDao = accountDao, @@ -105,8 +108,6 @@ class OnboardingViewModel @Inject constructor( fun start(screen: OnboardingScreen, isSystemDarkMode: Boolean) { viewModelScope.launch { - TestIdlingResource.increment() - initiateSettings(isSystemDarkMode) router.initBackHandling( @@ -118,8 +119,6 @@ class OnboardingViewModel @Inject constructor( ) router.splashNext() - - TestIdlingResource.decrement() } } @@ -128,8 +127,6 @@ class OnboardingViewModel @Inject constructor( _currency.value = defaultCurrency ioThread { - TestIdlingResource.increment() - if (settingsDao.findAll().isEmpty()) { settingsWriter.save( Settings( @@ -140,24 +137,39 @@ class OnboardingViewModel @Inject constructor( ).toEntity() ) } + } + } - TestIdlingResource.decrement() + override fun onEvent(event: OnboardingEvent) { + viewModelScope.launch { + when (event) { + is OnboardingEvent.CreateAccount -> createAccount(event.data) + is OnboardingEvent.CreateCategory -> createCategory(event.data) + is OnboardingEvent.EditAccount -> editAccount(event.account, event.newBalance) + is OnboardingEvent.EditCategory -> editCategory(event.updatedCategory) + is OnboardingEvent.ImportFinished -> importFinished(event.success) + OnboardingEvent.ImportSkip -> importSkip() + OnboardingEvent.LoginOfflineAccount -> loginOfflineAccount() + OnboardingEvent.LoginWithGoogle -> loginWithGoogle() + OnboardingEvent.OnAddAccountsDone -> onAddAccountsDone() + OnboardingEvent.OnAddAccountsSkip -> onAddAccountsSkip() + OnboardingEvent.OnAddCategoriesDone -> onAddCategoriesDone() + OnboardingEvent.OnAddCategoriesSkip -> onAddCategoriesSkip() + is OnboardingEvent.SetBaseCurrency -> setBaseCurrency(event.baseCurrency) + OnboardingEvent.StartFresh -> startFresh() + OnboardingEvent.StartImport -> startImport() + } } } // Step 1 --------------------------------------------------------------------------------------- - fun loginWithGoogle() { + private suspend fun loginWithGoogle() { ivyContext.googleSignIn { idToken -> if (idToken != null) { _opGoogleSignIn.value = OpResult.loading() viewModelScope.launch { - TestIdlingResource.increment() - try { - loginWithGoogleOnServer(idToken) - router.googleLoginNext() - _opGoogleSignIn.value = null // reset login with Google operation state } catch (e: Exception) { e.sendToCrashlytics("GOOGLE_SIGN_IN ERROR: generic exception when logging with GOOGLE") @@ -165,8 +177,6 @@ class OnboardingViewModel @Inject constructor( Timber.e("Login with Google failed on Ivy server - ${e.message}") _opGoogleSignIn.value = OpResult.failure(e) } - - TestIdlingResource.decrement() } } else { sendToCrashlytics("GOOGLE_SIGN_IN ERROR: idToken is null!!") @@ -176,23 +186,13 @@ class OnboardingViewModel @Inject constructor( } } - private suspend fun loginWithGoogleOnServer(idToken: String) { - TestIdlingResource.increment() - - TestIdlingResource.decrement() - } - - fun loginOfflineAccount() { - viewModelScope.launch { - TestIdlingResource.increment() - router.offlineAccountNext() - TestIdlingResource.decrement() - } + private suspend fun loginOfflineAccount() { + router.offlineAccountNext() } // Step 1 --------------------------------------------------------------------------------------- // Step 2 --------------------------------------------------------------------------------------- - fun startImport() { + private fun startImport() { router.startImport() } @@ -204,63 +204,40 @@ class OnboardingViewModel @Inject constructor( router.importFinished(success) } - fun startFresh() { + private fun startFresh() { router.startFresh() } // Step 2 --------------------------------------------------------------------------------------- - fun setBaseCurrency(baseCurrency: IvyCurrency) { - viewModelScope.launch { - TestIdlingResource.increment() - - updateBaseCurrency(baseCurrency) - - router.setBaseCurrencyNext( - baseCurrency = baseCurrency, - accountsWithBalance = { accountsWithBalance() } - ) - - TestIdlingResource.decrement() - } + private suspend fun setBaseCurrency(baseCurrency: IvyCurrency) { + updateBaseCurrency(baseCurrency) + router.setBaseCurrencyNext( + baseCurrency = baseCurrency, + accountsWithBalance = { accountsWithBalance() } + ) } private suspend fun updateBaseCurrency(baseCurrency: IvyCurrency) { ioThread { - TestIdlingResource.increment() - settingsWriter.save( settingsDao.findFirst().copy( currency = baseCurrency.code ) ) - - TestIdlingResource.decrement() } _currency.value = baseCurrency } // --------------------- Accounts --------------------------------------------------------------- - fun editAccount(account: Account, newBalance: Double) { - viewModelScope.launch { - TestIdlingResource.increment() - - accountCreator.editAccount(account, newBalance) { - _accounts.value = accountsWithBalance() - } - - TestIdlingResource.decrement() + private suspend fun editAccount(account: Account, newBalance: Double) { + accountCreator.editAccount(account, newBalance) { + _accounts.value = accountsWithBalance() } } - fun createAccount(data: CreateAccountData) { - viewModelScope.launch { - TestIdlingResource.increment() - - accountCreator.createAccount(data) { - _accounts.value = accountsWithBalance() - } - - TestIdlingResource.decrement() + private suspend fun createAccount(data: CreateAccountData) { + accountCreator.createAccount(data) { + _accounts.value = accountsWithBalance() } } @@ -274,70 +251,34 @@ class OnboardingViewModel @Inject constructor( }.toImmutableList() } - fun onAddAccountsDone() { - viewModelScope.launch { - TestIdlingResource.increment() - - router.accountsNext() - - TestIdlingResource.decrement() - } + private suspend fun onAddAccountsDone() { + router.accountsNext() } - fun onAddAccountsSkip() { - viewModelScope.launch { - TestIdlingResource.increment() - - router.accountsSkip() - - TestIdlingResource.decrement() - } + private suspend fun onAddAccountsSkip() { + router.accountsSkip() } // --------------------- Accounts --------------------------------------------------------------- // ---------------------------- Categories ------------------------------------------------------ - fun editCategory(updatedCategory: Category) { - viewModelScope.launch { - TestIdlingResource.increment() - - categoryCreator.editCategory(updatedCategory) { - _categories.value = categoriesAct(Unit)!! - } - - TestIdlingResource.decrement() + private suspend fun editCategory(updatedCategory: Category) { + categoryCreator.editCategory(updatedCategory) { + _categories.value = categoriesAct(Unit)!! } } - fun createCategory(data: CreateCategoryData) { - viewModelScope.launch { - TestIdlingResource.increment() - - categoryCreator.createCategory(data) { - _categories.value = categoriesAct(Unit)!! - } - - TestIdlingResource.decrement() + private suspend fun createCategory(data: CreateCategoryData) { + categoryCreator.createCategory(data) { + _categories.value = categoriesAct(Unit)!! } } - fun onAddCategoriesDone() { - viewModelScope.launch { - TestIdlingResource.increment() - - router.categoriesNext(baseCurrency = currency.value) - - TestIdlingResource.decrement() - } + private suspend fun onAddCategoriesDone() { + router.categoriesNext(baseCurrency = _currency.value) } - fun onAddCategoriesSkip() { - viewModelScope.launch { - TestIdlingResource.increment() - - router.categoriesSkip(baseCurrency = currency.value) - - TestIdlingResource.decrement() - } + private suspend fun onAddCategoriesSkip() { + router.categoriesSkip(baseCurrency = _currency.value) } // ---------------------------- Categories ------------------------------------------------------ }