diff --git a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt index a87376db9e..05284a83e6 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.di.common import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.di.feature.* +import com.anytypeio.anytype.di.feature.auth.DeletedAccountModule import com.anytypeio.anytype.di.feature.cover.UnsplashModule import com.anytypeio.anytype.di.feature.relations.* import com.anytypeio.anytype.di.feature.sets.CreateFilterModule @@ -27,6 +28,10 @@ class ComponentManager(private val main: MainComponent) { main.authComponentBuilder().authModule(AuthModule).build() } + val deletedAccountComponent = Component { + main.deletedAccountBuilder().module(DeletedAccountModule).build() + } + val startLoginComponent = Component { authComponent .get() diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/MainEntryDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/MainEntryDI.kt index 97b398987e..a3a8af4b3d 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/MainEntryDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/MainEntryDI.kt @@ -2,8 +2,12 @@ package com.anytypeio.anytype.di.feature import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_utils.di.scope.PerScreen +import com.anytypeio.anytype.domain.account.AccountStatusChannel +import com.anytypeio.anytype.domain.account.InterceptAccountStatus import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount +import com.anytypeio.anytype.domain.auth.interactor.Logout import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.config.FlavourConfigProvider import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.device.PathProvider @@ -39,12 +43,16 @@ object MainEntryModule { launchAccount: LaunchAccount, analytics: Analytics, observeWallpaper: ObserveWallpaper, - restoreWallpaper: RestoreWallpaper + restoreWallpaper: RestoreWallpaper, + interceptAccountStatus: InterceptAccountStatus, + logout: Logout ): MainViewModelFactory = MainViewModelFactory( launchAccount = launchAccount, analytics = analytics, observeWallpaper = observeWallpaper, - restoreWallpaper = restoreWallpaper + restoreWallpaper = restoreWallpaper, + interceptAccountStatus = interceptAccountStatus, + logout = logout ) @JvmStatic @@ -63,12 +71,33 @@ object MainEntryModule { @JvmStatic @PerScreen @Provides - fun provideObserveWallpaperUseCase() : ObserveWallpaper = ObserveWallpaper() + fun provideObserveWallpaperUseCase(): ObserveWallpaper = ObserveWallpaper() @JvmStatic @PerScreen @Provides fun provideRestoreWallpaperUseCase( repo: UserSettingsRepository - ) : RestoreWallpaper = RestoreWallpaper(repo) + ): RestoreWallpaper = RestoreWallpaper(repo) + + @JvmStatic + @PerScreen + @Provides + fun provideInterceptAccountStatus( + channel: AccountStatusChannel + ): InterceptAccountStatus = InterceptAccountStatus( + channel = channel, + dispatchers = AppCoroutineDispatchers( + io = Dispatchers.IO, + computation = Dispatchers.Default, + main = Dispatchers.Main + ) + ) + + @JvmStatic + @PerScreen + @Provides + fun provideLogoutUseCase(repo: AuthRepository): Logout = Logout( + repo = repo + ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/auth/DeletedAccountDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/auth/DeletedAccountDI.kt new file mode 100644 index 0000000000..263d08fa50 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/auth/DeletedAccountDI.kt @@ -0,0 +1,60 @@ +package com.anytypeio.anytype.di.feature.auth + +import com.anytypeio.anytype.core_utils.di.scope.PerScreen +import com.anytypeio.anytype.domain.account.DateHelper +import com.anytypeio.anytype.domain.account.RestoreAccount +import com.anytypeio.anytype.domain.auth.interactor.Logout +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.ext.DefaultDateHelper +import com.anytypeio.anytype.presentation.auth.account.DeletedAccountViewModel +import com.anytypeio.anytype.ui.auth.account.DeletedAccountFragment +import dagger.Module +import dagger.Provides +import dagger.Subcomponent + +@Subcomponent(modules = [DeletedAccountModule::class]) +@PerScreen +interface DeletedAccountSubcomponent { + @Subcomponent.Builder + interface Builder { + fun module(module: DeletedAccountModule): Builder + fun build(): DeletedAccountSubcomponent + } + + fun inject(fragment: DeletedAccountFragment) +} + +@Module +object DeletedAccountModule { + @JvmStatic + @Provides + @PerScreen + fun provideViewModelFactory( + restoreAccount: RestoreAccount, + logout: Logout, + helper: DateHelper + ): DeletedAccountViewModel.Factory = DeletedAccountViewModel.Factory( + restoreAccount = restoreAccount, + logout = logout, + helper = helper + ) + + @JvmStatic + @Provides + @PerScreen + fun provideRestoreAccount(repo: AuthRepository): RestoreAccount = RestoreAccount( + repo = repo + ) + + @JvmStatic + @Provides + @PerScreen + fun provideLogout(repo: AuthRepository): Logout = Logout( + repo = repo + ) + + @JvmStatic + @Provides + @PerScreen + fun provideDateHelper(): DateHelper = DefaultDateHelper() +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/settings/AccountAndDataDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/settings/AccountAndDataDI.kt index b76ca770f0..335a417275 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/settings/AccountAndDataDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/settings/AccountAndDataDI.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.di.feature.settings import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_utils.di.scope.PerScreen +import com.anytypeio.anytype.domain.account.DeleteAccount import com.anytypeio.anytype.domain.auth.interactor.Logout import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.block.repo.BlockRepository @@ -32,9 +33,11 @@ object AccountAndDataModule { @PerScreen fun provideViewModelFactory( clearFileCache: ClearFileCache, + deleteAccount: DeleteAccount, analytics: Analytics ): AccountAndDataViewModel.Factory = AccountAndDataViewModel.Factory( clearFileCache = clearFileCache, + deleteAccount = deleteAccount, analytics = analytics ) @@ -47,4 +50,9 @@ object AccountAndDataModule { @Provides @PerScreen fun logout(repo: AuthRepository): Logout = Logout(repo) + + @JvmStatic + @Provides + @PerScreen + fun deleteAccount(repo: AuthRepository): DeleteAccount = DeleteAccount(repo) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/EventModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/EventModule.kt index e8da9d33b7..3d056063dc 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/EventModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/EventModule.kt @@ -1,19 +1,19 @@ package com.anytypeio.anytype.di.main +import com.anytypeio.anytype.data.auth.account.AccountStatusDataChannel +import com.anytypeio.anytype.data.auth.account.AccountStatusRemoteChannel import com.anytypeio.anytype.data.auth.event.EventDataChannel import com.anytypeio.anytype.data.auth.event.EventRemoteChannel import com.anytypeio.anytype.data.auth.event.SubscriptionDataChannel import com.anytypeio.anytype.data.auth.event.SubscriptionEventRemoteChannel import com.anytypeio.anytype.data.auth.status.ThreadStatusDataChannel import com.anytypeio.anytype.data.auth.status.ThreadStatusRemoteChannel +import com.anytypeio.anytype.domain.account.AccountStatusChannel import com.anytypeio.anytype.domain.event.interactor.EventChannel import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.status.ThreadStatusChannel import com.anytypeio.anytype.middleware.EventProxy -import com.anytypeio.anytype.middleware.interactor.EventHandler -import com.anytypeio.anytype.middleware.interactor.MiddlewareEventChannel -import com.anytypeio.anytype.middleware.interactor.MiddlewareSubscriptionEventChannel -import com.anytypeio.anytype.middleware.interactor.ThreadStatusMiddlewareChannel +import com.anytypeio.anytype.middleware.interactor.* import dagger.Module import dagger.Provides import javax.inject.Singleton @@ -63,6 +63,31 @@ object EventModule { proxy: EventProxy ): ThreadStatusRemoteChannel = ThreadStatusMiddlewareChannel(events = proxy) + @JvmStatic + @Provides + @Singleton + fun provideAccountStatusChannel( + channel: AccountStatusDataChannel + ) : AccountStatusChannel = channel + + @JvmStatic + @Provides + @Singleton + fun provideAccountStatusDataChannel( + channel: AccountStatusRemoteChannel + ) = AccountStatusDataChannel( + remote = channel + ) + + @JvmStatic + @Provides + @Singleton + fun provideAccountStatusRemoteChannel( + proxy: EventProxy + ) : AccountStatusRemoteChannel = AccountStatusMiddlewareChannel( + events = proxy + ) + @JvmStatic @Provides @Singleton diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt index 38b78e7297..2077b8735a 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.di.main import com.anytypeio.anytype.app.AndroidApplication import com.anytypeio.anytype.di.feature.* +import com.anytypeio.anytype.di.feature.auth.DeletedAccountSubcomponent import com.anytypeio.anytype.di.feature.settings.AboutAppSubComponent import com.anytypeio.anytype.di.feature.settings.AccountAndDataSubComponent import com.anytypeio.anytype.di.feature.settings.LogoutWarningSubComponent @@ -28,7 +29,6 @@ import javax.inject.Singleton interface MainComponent { fun inject(app: AndroidApplication) - fun authComponentBuilder(): AuthSubComponent.Builder fun splashComponentBuilder(): SplashSubComponent.Builder fun homeDashboardComponentBuilder(): HomeDashboardSubComponent.Builder fun editorComponentBuilder(): EditorSubComponent.Builder @@ -48,6 +48,13 @@ interface MainComponent { fun wallpaperSelectComponent(): WallpaperSelectSubComponent.Builder fun createObjectComponent(): CreateObjectSubComponent.Builder + //region Auth + + fun authComponentBuilder(): AuthSubComponent.Builder + fun deletedAccountBuilder() : DeletedAccountSubcomponent.Builder + + //endregion + //region Settings fun aboutAppComponent() : AboutAppSubComponent.Builder diff --git a/app/src/main/java/com/anytypeio/anytype/ext/DefaultDateHelper.kt b/app/src/main/java/com/anytypeio/anytype/ext/DefaultDateHelper.kt new file mode 100644 index 0000000000..84e79b66d6 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ext/DefaultDateHelper.kt @@ -0,0 +1,15 @@ +package com.anytypeio.anytype.ext + +import com.anytypeio.anytype.core_utils.date.isToday +import com.anytypeio.anytype.core_utils.date.isTomorrow +import com.anytypeio.anytype.domain.account.DateHelper + +class DefaultDateHelper : DateHelper { + override fun isToday(millis: Long): Boolean { + return millis.isToday() + } + + override fun isTomorrow(millis: Long): Boolean { + return millis.isTomorrow() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt b/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt index 1f2f9aefb8..ced97deeb3 100644 --- a/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt +++ b/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.presentation.settings.EditorSettings import com.anytypeio.anytype.ui.archive.ArchiveFragment import com.anytypeio.anytype.ui.auth.Keys import com.anytypeio.anytype.ui.auth.account.CreateAccountFragment.Companion.ARGS_CODE +import com.anytypeio.anytype.ui.auth.account.DeletedAccountFragment import com.anytypeio.anytype.ui.dashboard.DashboardFragment import com.anytypeio.anytype.ui.editor.EditorFragment import com.anytypeio.anytype.ui.navigation.PageNavigationFragment @@ -238,4 +239,18 @@ class Navigator : AppNavigation { override fun openUpdateAppScreen() { navController?.navigate(R.id.alertUpdateAppFragment) } + + override fun deletedAccountScreen(deadline: Long) { + navController?.navigate( + R.id.deletedAccountNavigation, + bundleOf(DeletedAccountFragment.DEADLINE_KEY to deadline), + navOptions { + popUpTo = R.id.main_navigation + } + ) + } + + override fun logout() { + navController?.navigate(R.id.actionLogout) + } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/auth/account/DeleteAccountWarning.kt b/app/src/main/java/com/anytypeio/anytype/ui/auth/account/DeleteAccountWarning.kt new file mode 100644 index 0000000000..d3be95fc27 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/auth/account/DeleteAccountWarning.kt @@ -0,0 +1,46 @@ +package com.anytypeio.anytype.ui.auth.account + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.material.MaterialTheme +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_ui.foundation.Warning +import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment +import com.anytypeio.anytype.ui.settings.typography + +class DeleteAccountWarning : BaseBottomSheetComposeFragment() { + + var onDeletionAccepted: () -> Unit = {} + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme(typography = typography) { + Warning( + actionButtonText = stringResource(R.string.delete), + cancelButtonText = stringResource(R.string.cancel), + title = stringResource(R.string.are_you_sure_to_delete_account), + subtitle = stringResource(R.string.deleted_account_warning_msg), + onNegativeClick = { dismiss() }, + onPositiveClick = { onDeletionAccepted() }, + isInProgress = false + ) + } + } + } + } + + + override fun injectDependencies() {} + override fun releaseDependencies() {} +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/auth/account/DeletedAccountFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/auth/account/DeletedAccountFragment.kt new file mode 100644 index 0000000000..2cc107ec1b --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/auth/account/DeletedAccountFragment.kt @@ -0,0 +1,244 @@ +package com.anytypeio.anytype.ui.auth.account + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.animation.core.FloatTweenSpec +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.findNavController +import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_ui.foundation.Divider +import com.anytypeio.anytype.core_utils.ext.arg +import com.anytypeio.anytype.core_utils.ext.toast +import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment +import com.anytypeio.anytype.di.common.componentManager +import com.anytypeio.anytype.presentation.auth.account.DeletedAccountViewModel +import com.anytypeio.anytype.presentation.auth.account.DeletedAccountViewModel.DeletionDate +import com.anytypeio.anytype.ui.settings.typography +import com.anytypeio.anytype.ui_settings.account.Action +import com.anytypeio.anytype.ui_settings.account.ActionWithProgressBar +import kotlinx.coroutines.launch +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class DeletedAccountFragment : BaseComposeFragment() { + + private val deadline: Long get() = arg(DEADLINE_KEY) + + @Inject + lateinit var factory: DeletedAccountViewModel.Factory + private val vm by viewModels { factory } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + DeletedAccountScreen( + progress = vm.progress.collectAsState().value, + date = vm.date.collectAsState().value, + onLogoutAndClearDataClicked = { vm.onLogoutAndClearDataClicked() }, + onCancelDeletionClicked = { vm.cancelDeletionClicked() }, + isLoggingOutInProgress = vm.isLoggingOut.collectAsState().value + ) + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + vm.toasts.collect { toast(it) } + } + launch { + vm.commands.collect { command -> + when (command) { + DeletedAccountViewModel.Command.Resume -> { + findNavController().navigate(R.id.main_navigation) + } + DeletedAccountViewModel.Command.Logout -> { + findNavController().navigate(R.id.main_navigation) + } + } + } + } + } + } + } + + override fun onStart() { + super.onStart() + vm.onStart( + nowInMillis = System.currentTimeMillis(), + deadlineInMillis = TimeUnit.SECONDS.toMillis(deadline) + ) + } + + override fun injectDependencies() { + componentManager().deletedAccountComponent.get().inject(this) + } + + override fun releaseDependencies() { + componentManager().deletedAccountComponent.release() + } + + companion object { + const val DEADLINE_KEY = "arg.deleted-account.date" + } +} + +@Composable +fun DeletedAccountScreen( + progress: Float, + date: DeletionDate, + onCancelDeletionClicked: () -> Unit, + onLogoutAndClearDataClicked: () -> Unit, + isLoggingOutInProgress: Boolean +) { + MaterialTheme(typography = typography) { + Box(contentAlignment = Alignment.BottomCenter) { + Card( + modifier = Modifier.padding( + start = 10.dp, + end = 10.dp, + bottom = 16.dp + ), + shape = RoundedCornerShape(16.dp), + backgroundColor = colorResource(R.color.background_secondary) + ) { + Column { + Chart( + chartColor = colorResource(R.color.anytype_text_red), + progress = progress + ) + Text( + text = when (date) { + is DeletionDate.Later -> { + stringResource( + R.string.planned_for_deletion_in_n_days, + date.days + ) + } + DeletionDate.Today -> { + stringResource( + R.string.planned_for_deletion_today + ) + } + DeletionDate.Tomorrow -> { + stringResource( + R.string.planned_for_deletion_tomorrow + ) + } + DeletionDate.Unknown -> { + stringResource( + R.string.planned_for_deletion_unknown + ) + } + }, + color = colorResource(R.color.text_primary), + style = MaterialTheme.typography.h2, + modifier = Modifier.padding( + start = 20.dp, + end = 20.dp + ) + ) + Text( + text = stringResource(R.string.deleted_account_msg), + color = colorResource(R.color.text_primary), + modifier = Modifier.padding( + top = 12.dp, + start = 20.dp, + end = 20.dp, + bottom = 14.dp + ), + fontSize = 15.sp + ) + Action( + name = stringResource(R.string.cancel_deletion), + color = colorResource(R.color.anytype_text_red), + onClick = onCancelDeletionClicked + ) + Divider() + ActionWithProgressBar( + name = stringResource(R.string.logout_and_clear_local_data), + color = colorResource(R.color.anytype_text_red), + onClick = onLogoutAndClearDataClicked, + isInProgress = isLoggingOutInProgress + ) + Divider() + Spacer(Modifier.height(22.dp)) + } + } + } + } +} + +@Composable +fun Chart( + chartColor: Color, + progress: Float = 0.0f +) { + val sweepAngleValueAnimation by animateFloatAsState( + targetValue = 360f * progress, + animationSpec = FloatTweenSpec(duration = 300) + ) + Box( + modifier = Modifier + .padding( + start = 20.dp, + top = 20.dp, + bottom = 20.dp + ) + .height(52.dp) + .width(52.dp) + .border( + shape = CircleShape, + width = 1.5.dp, + color = colorResource(R.color.shape_primary) + ) + ) { + Canvas( + modifier = Modifier + .align(Alignment.Center) + .height(36.dp) + .width(36.dp) + ) { + drawArc( + startAngle = -90f, + sweepAngle = sweepAngleValueAnimation, + color = chartColor, + useCenter = true + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/auth/account/SelectAccountFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/auth/account/SelectAccountFragment.kt index 5b89945145..261b02f1aa 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/auth/account/SelectAccountFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/auth/account/SelectAccountFragment.kt @@ -45,14 +45,10 @@ class SelectAccountFragment : NavigationFragment(R } vm.state.observe(viewLifecycleOwner) { state -> profileAdapter.update(state) } - vm.error.observe(viewLifecycleOwner) { observeError(it) } + vm.error.observe(viewLifecycleOwner) { toast(it) } vm.observeNavigation().observe(viewLifecycleOwner, navObserver) } - private fun observeError(msg: String) { - requireActivity().toast(msg) - } - override fun injectDependencies() { componentManager().selectAccountComponent.get().inject(this) } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt index 7b395ba758..10907e52ba 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt @@ -61,6 +61,7 @@ abstract class NavigationFragment( is Command.OpenPageSearch -> navigation.openPageSearch() is Command.OpenCreateSetScreen -> navigation.openCreateSetScreen(command.ctx) is Command.OpenUpdateAppScreen -> navigation.openUpdateAppScreen() + is Command.DeletedAccountScreen -> navigation.deletedAccountScreen(command.deadline) else -> Timber.d("Nav command ignored: $command") } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt b/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt index 4458b3d8b5..c93b1a71d2 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/main/MainActivity.kt @@ -9,21 +9,24 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import androidx.fragment.app.FragmentContainerView +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import com.anytypeio.anytype.BuildConfig import com.anytypeio.anytype.R import com.anytypeio.anytype.app.DefaultAppActionManager import com.anytypeio.anytype.core_models.Wallpaper -import com.anytypeio.anytype.core_utils.ext.subscribe import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.navigation.Navigator import com.anytypeio.anytype.presentation.editor.cover.CoverGradient import com.anytypeio.anytype.presentation.main.MainViewModel +import com.anytypeio.anytype.presentation.main.MainViewModel.Command import com.anytypeio.anytype.presentation.main.MainViewModelFactory import com.anytypeio.anytype.presentation.navigation.AppNavigation import com.anytypeio.anytype.presentation.wallpaper.WallpaperColor import com.anytypeio.anytype.ui.editor.CreateObjectFragment +import kotlinx.coroutines.launch import javax.inject.Inject class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Provider { @@ -45,8 +48,26 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), AppNavigation.Pr setupWindowInsets() inject() if (savedInstanceState != null) vm.onRestore() - with(lifecycleScope) { - subscribe(vm.wallpaper) { wallpaper -> setWallpaper(wallpaper) } + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + vm.wallpaper.collect { setWallpaper(it) } + } + launch { + vm.commands.collect { command -> + when(command) { + is Command.ShowDeletedAccountScreen -> { + navigator.deletedAccountScreen( + deadline = command.deadline + ) + } + is Command.LogoutDueToAccountDeletion -> { + navigator.logout() + } + } + } + } + } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/AccountAndDataFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/AccountAndDataFragment.kt index 15e533fe54..2dc90c87bc 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/AccountAndDataFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/AccountAndDataFragment.kt @@ -11,11 +11,13 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.anytypeio.anytype.BuildConfig import com.anytypeio.anytype.R import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment import com.anytypeio.anytype.di.common.componentManager +import com.anytypeio.anytype.ui.auth.account.DeleteAccountWarning import com.anytypeio.anytype.ui.dashboard.ClearCacheAlertFragment import com.anytypeio.anytype.ui.profile.KeychainPhraseDialog import com.anytypeio.anytype.ui_settings.account.AccountAndDataScreen @@ -50,8 +52,7 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() { AccountAndDataScreen( onKeychainPhraseClicked = onKeychainPhraseClicked, onClearFileCachedClicked = { proceedWithClearFileCacheWarning() }, - onDeleteAccountClicked = { toast(resources.getString(R.string.coming_soon)) }, - onResetAccountClicked = { toast(resources.getString(R.string.coming_soon)) }, + onDeleteAccountClicked = { proceedWithAccountDeletion() }, onLogoutClicked = onLogoutClicked, onPinCodeClicked = { toast(resources.getString(R.string.coming_soon)) }, isLogoutInProgress = vm.isLoggingOut.collectAsState().value, @@ -69,6 +70,20 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() { dialog.show(childFragmentManager, null) } + private fun proceedWithAccountDeletion() { + // TODO release this feature when it's ready on all our platforms! + if (BuildConfig.DEBUG) { + val dialog = DeleteAccountWarning() + dialog.onDeletionAccepted = { + dialog.dismiss() + vm.onDeleteAccountClicked() + } + dialog.show(childFragmentManager, null) + } else { + toast(resources.getString(R.string.coming_soon)) + } + } + override fun injectDependencies() { componentManager().accountAndDataComponent.get().inject(this) } diff --git a/app/src/main/res/layout/fragment_select_account.xml b/app/src/main/res/layout/fragment_select_account.xml index 0c88312bc7..e9930ca50d 100644 --- a/app/src/main/res/layout/fragment_select_account.xml +++ b/app/src/main/res/layout/fragment_select_account.xml @@ -3,7 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - xmlns:tools="http://schemas.android.com/tools"> + xmlns:tools="http://schemas.android.com/tools" + tools:background="@color/blue"> - - + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6dadb95f43..57b2a62f5c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -269,4 +269,12 @@ Do the computation of an expensive paragraph of text on a background thread: Retry Limit object types Unsplash + Cancel deletion + Logout and clear local data + This account is planned for deletion in %1$d days + This account is planned for deletion tomorrow + This account is planned for deletion today + This account is planned for deletion in ? days + Are you sure to delete account? + You will be logged out on all other devices. You will have 30 days to recover it. Afterwards it will be deleted permanently. diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/AccountStatus.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/AccountStatus.kt new file mode 100644 index 0000000000..d8f477ed43 --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/AccountStatus.kt @@ -0,0 +1,8 @@ +package com.anytypeio.anytype.core_models + +sealed class AccountStatus { + object Unknown : AccountStatus() + object Active : AccountStatus() + data class PendingDeletion(val deadline: Long) : AccountStatus() + object Deleted : AccountStatus() +} \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/AccountIsDeletedException.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/AccountIsDeletedException.kt new file mode 100644 index 0000000000..d72206440b --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/exceptions/AccountIsDeletedException.kt @@ -0,0 +1,3 @@ +package com.anytypeio.anytype.core_models.exceptions + +class AccountIsDeletedException : Exception() \ No newline at end of file diff --git a/core-ui/src/main/res/values/strings.xml b/core-ui/src/main/res/values/strings.xml index 5312f1a225..1206d7d2f4 100644 --- a/core-ui/src/main/res/values/strings.xml +++ b/core-ui/src/main/res/values/strings.xml @@ -486,4 +486,6 @@ Connect with Limit object type + We\'re sorry to see you go. Once you request your account to be deleted, you have 30 days to cancel this request. After 30 days, your encrypted account data is permanently removed from the backup node, you won\'t be able to sign into Anytype on new devices. + diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/date/DateExt.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/date/DateExt.kt new file mode 100644 index 0000000000..a0691984c6 --- /dev/null +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/date/DateExt.kt @@ -0,0 +1,21 @@ +package com.anytypeio.anytype.core_utils.date + +import android.text.format.DateUtils + +typealias Milliseconds = Long + +fun Milliseconds.isToday() : Boolean { + return DateUtils.isToday(this) +} + +fun Milliseconds.isTomorrow() : Boolean { + return DateUtils.isToday( + this - DateUtils.DAY_IN_MILLIS + ) +} + +fun Milliseconds.isYesterday() : Boolean { + return DateUtils.isToday( + this + DateUtils.DAY_IN_MILLIS + ) +} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/account/AccountStatusDataChannel.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/account/AccountStatusDataChannel.kt new file mode 100644 index 0000000000..4765798556 --- /dev/null +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/account/AccountStatusDataChannel.kt @@ -0,0 +1,11 @@ +package com.anytypeio.anytype.data.auth.account + +import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.domain.account.AccountStatusChannel +import kotlinx.coroutines.flow.Flow + +class AccountStatusDataChannel( + private val remote: AccountStatusRemoteChannel +) : AccountStatusChannel { + override fun observe(): Flow = remote.observe() +} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/account/AccountStatusRemoteChannel.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/account/AccountStatusRemoteChannel.kt new file mode 100644 index 0000000000..51c2ee59b3 --- /dev/null +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/account/AccountStatusRemoteChannel.kt @@ -0,0 +1,8 @@ +package com.anytypeio.anytype.data.auth.account + +import com.anytypeio.anytype.core_models.AccountStatus +import kotlinx.coroutines.flow.Flow + +interface AccountStatusRemoteChannel { + fun observe(): Flow +} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt index 4a1f74ab5f..77bae467e5 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt @@ -1,7 +1,7 @@ package com.anytypeio.anytype.data.auth.repo +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.FlavourConfigEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -12,7 +12,7 @@ class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore { override suspend fun startAccount( id: String, path: String - ): Pair { + ): Triple { throw UnsupportedOperationException() } @@ -20,6 +20,14 @@ class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore { throw UnsupportedOperationException() } + override suspend fun deleteAccount(): AccountStatus { + throw UnsupportedOperationException() + } + + override suspend fun restoreAccount(): AccountStatus { + throw UnsupportedOperationException() + } + override suspend fun recoverAccount() { throw UnsupportedOperationException() } @@ -54,7 +62,7 @@ class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore { override suspend fun getMnemonic() = cache.getMnemonic() - override suspend fun logout() { + override suspend fun logout(clearLocalRepositoryData: Boolean) { cache.logout() } diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt index 0e4d5c47b7..114e642487 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt @@ -1,14 +1,14 @@ package com.anytypeio.anytype.data.auth.repo +import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.FlavourConfig +import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.data.auth.mapper.toDomain import com.anytypeio.anytype.data.auth.mapper.toEntity import com.anytypeio.anytype.data.auth.repo.config.Configurator import com.anytypeio.anytype.domain.auth.model.Account import com.anytypeio.anytype.domain.auth.model.Wallet import com.anytypeio.anytype.domain.auth.repo.AuthRepository -import com.anytypeio.anytype.core_models.FlavourConfig -import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.ObjectType import kotlinx.coroutines.flow.map class AuthDataRepository( @@ -18,10 +18,11 @@ class AuthDataRepository( override suspend fun startAccount( id: String, path: String - ): Pair = factory.remote.startAccount(id, path).let { pair -> - Pair( - first = pair.first.toDomain(), - second = pair.second.toDomain() + ): Triple = factory.remote.startAccount(id, path).let { triple -> + Triple( + first = triple.first.toDomain(), + second = triple.second.toDomain(), + third = triple.third ) } @@ -31,6 +32,9 @@ class AuthDataRepository( invitationCode: String ): Account = factory.remote.createAccount(name, avatarPath, invitationCode).toDomain() + override suspend fun deleteAccount(): AccountStatus = factory.remote.deleteAccount() + override suspend fun restoreAccount(): AccountStatus = factory.remote.restoreAccount() + override suspend fun startLoadingAccounts() { factory.remote.recoverAccount() } @@ -66,10 +70,10 @@ class AuthDataRepository( override suspend fun getMnemonic() = factory.cache.getMnemonic() - override suspend fun logout() { + override suspend fun logout(clearLocalRepositoryData: Boolean) { configurator.release() - factory.remote.logout() - factory.cache.logout() + factory.remote.logout(clearLocalRepositoryData) + factory.cache.logout(clearLocalRepositoryData) } override suspend fun getAccounts() = factory.cache.getAccounts().map { it.toDomain() } diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt index c649066aaa..381c012247 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt @@ -1,7 +1,7 @@ package com.anytypeio.anytype.data.auth.repo +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.FlavourConfigEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -9,10 +9,13 @@ import kotlinx.coroutines.flow.Flow interface AuthDataStore { - suspend fun startAccount(id: String, path: String): Pair + suspend fun startAccount(id: String, path: String): Triple suspend fun createAccount(name: String, avatarPath: String?, invitationCode: String): AccountEntity + suspend fun deleteAccount() : AccountStatus + suspend fun restoreAccount() : AccountStatus + suspend fun recoverAccount() suspend fun saveAccount(account: AccountEntity) @@ -30,7 +33,7 @@ interface AuthDataStore { suspend fun saveMnemonic(mnemonic: String) suspend fun getMnemonic(): String - suspend fun logout() + suspend fun logout(clearLocalRepositoryData: Boolean) suspend fun getAccounts(): List suspend fun setCurrentAccount(id: String) diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt index 89f4349aa1..03e8b5b634 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt @@ -1,15 +1,18 @@ package com.anytypeio.anytype.data.auth.repo +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.FlavourConfigEntity import com.anytypeio.anytype.data.auth.model.WalletEntity import kotlinx.coroutines.flow.Flow interface AuthRemote { - suspend fun startAccount(id: String, path: String): Pair + suspend fun startAccount(id: String, path: String): Triple suspend fun createAccount(name: String, avatarPath: String?, invitationCode: String): AccountEntity + suspend fun deleteAccount() : AccountStatus + suspend fun restoreAccount() : AccountStatus suspend fun recoverAccount() - suspend fun logout() + suspend fun logout(clearLocalRepositoryData: Boolean) fun observeAccounts(): Flow suspend fun createWallet(path: String): WalletEntity diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt index 29f8be1df1..bd530e9e71 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt @@ -1,7 +1,7 @@ package com.anytypeio.anytype.data.auth.repo +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -19,6 +19,10 @@ class AuthRemoteDataStore( invitationCode: String ) = authRemote.createAccount(name, avatarPath, invitationCode) + override suspend fun deleteAccount(): AccountStatus = authRemote.deleteAccount() + + override suspend fun restoreAccount(): AccountStatus = authRemote.restoreAccount() + override suspend fun recoverAccount() { authRemote.recoverAccount() } @@ -48,8 +52,8 @@ class AuthRemoteDataStore( throw UnsupportedOperationException() } - override suspend fun logout() { - authRemote.logout() + override suspend fun logout(clearLocalRepositoryData: Boolean) { + authRemote.logout(clearLocalRepositoryData) } override suspend fun getAccounts(): List { diff --git a/data/src/test/java/com/anytypeio/anytype/data/AuthDataRepositoryTest.kt b/data/src/test/java/com/anytypeio/anytype/data/AuthDataRepositoryTest.kt index c912de1ff0..c5543f1df4 100644 --- a/data/src/test/java/com/anytypeio/anytype/data/AuthDataRepositoryTest.kt +++ b/data/src/test/java/com/anytypeio/anytype/data/AuthDataRepositoryTest.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.data +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.FlavourConfigEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -58,7 +59,9 @@ class AuthDataRepositoryTest { val config = FlavourConfigEntity() authRemote.stub { - onBlocking { startAccount(id = id, path = path) } doReturn Pair(account, config) + onBlocking { startAccount(id = id, path = path) } doReturn Triple( + account, config, AccountStatus.Active + ) } repo.startAccount( @@ -224,11 +227,11 @@ class AuthDataRepositoryTest { onBlocking { logout() } doReturn Unit } - repo.logout() + repo.logout(false) verify(authCache, times(1)).logout() verifyNoMoreInteractions(authCache) - verify(authRemote, times(1)).logout() + verify(authRemote, times(1)).logout(false) verifyNoMoreInteractions(authRemote) verify(configurator, times(1)).release() verifyZeroInteractions(configurator) @@ -238,14 +241,14 @@ class AuthDataRepositoryTest { fun `should not call logout on cache if remote logout is not succeeded`() { authRemote.stub { - onBlocking { logout() } doThrow IllegalStateException() + onBlocking { logout(false) } doThrow IllegalStateException() } runBlocking { try { - repo.logout() + repo.logout(false) } catch (e: Exception) { - verify(authRemote, times(1)).logout() + verify(authRemote, times(1)).logout(false) verifyZeroInteractions(authCache) } } diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/account/AccountStatusChannel.kt b/domain/src/main/java/com/anytypeio/anytype/domain/account/AccountStatusChannel.kt new file mode 100644 index 0000000000..b6c6e69320 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/account/AccountStatusChannel.kt @@ -0,0 +1,8 @@ +package com.anytypeio.anytype.domain.account + +import com.anytypeio.anytype.core_models.AccountStatus +import kotlinx.coroutines.flow.Flow + +interface AccountStatusChannel { + fun observe(): Flow +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/account/DateHelper.kt b/domain/src/main/java/com/anytypeio/anytype/domain/account/DateHelper.kt new file mode 100644 index 0000000000..42a16ae130 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/account/DateHelper.kt @@ -0,0 +1,6 @@ +package com.anytypeio.anytype.domain.account + +interface DateHelper { + fun isToday(millis: Long) : Boolean + fun isTomorrow(millis: Long) : Boolean +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/account/DeleteAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/account/DeleteAccount.kt new file mode 100644 index 0000000000..fcf1c77a2e --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/account/DeleteAccount.kt @@ -0,0 +1,9 @@ +package com.anytypeio.anytype.domain.account + +import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.BaseUseCase + +class DeleteAccount(private val repo: AuthRepository) : BaseUseCase() { + override suspend fun run(params: None) = safe { repo.deleteAccount() } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/account/InterceptAccountStatus.kt b/domain/src/main/java/com/anytypeio/anytype/domain/account/InterceptAccountStatus.kt new file mode 100644 index 0000000000..395ba89dd6 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/account/InterceptAccountStatus.kt @@ -0,0 +1,17 @@ +package com.anytypeio.anytype.domain.account + +import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.BaseUseCase +import com.anytypeio.anytype.domain.base.FlowUseCase +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn + +class InterceptAccountStatus( + private val channel: AccountStatusChannel, + private val dispatchers: AppCoroutineDispatchers +) : FlowUseCase() { + override fun build(params: BaseUseCase.None?): Flow { + return channel.observe().flowOn(dispatchers.io) + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/account/RestoreAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/account/RestoreAccount.kt new file mode 100644 index 0000000000..8c31676664 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/account/RestoreAccount.kt @@ -0,0 +1,9 @@ +package com.anytypeio.anytype.domain.account + +import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.BaseUseCase + +class RestoreAccount(private val repo: AuthRepository) : BaseUseCase() { + override suspend fun run(params: None) = safe { repo.restoreAccount() } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/LaunchAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/LaunchAccount.kt index 2978a06342..8bb284f19c 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/LaunchAccount.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/LaunchAccount.kt @@ -23,7 +23,7 @@ class LaunchAccount( id = repository.getCurrentAccountId(), path = pathProvider.providePath() ).let { pair -> - val (account, config) = pair + val (account, config, status) = pair repository.updateAccount(account) flavourConfigProvider.set( enableDataView = config.enableDataView ?: false, diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/Logout.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/Logout.kt index 406999bc18..f290431779 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/Logout.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/Logout.kt @@ -1,7 +1,6 @@ package com.anytypeio.anytype.domain.auth.interactor import com.anytypeio.anytype.domain.auth.repo.AuthRepository -import com.anytypeio.anytype.domain.base.BaseUseCase.None import com.anytypeio.anytype.domain.base.Interactor /** @@ -9,8 +8,13 @@ import com.anytypeio.anytype.domain.base.Interactor */ class Logout( private val repo: AuthRepository -) : Interactor() { - override suspend fun run(params: None) { - repo.logout() +) : Interactor() { + + override suspend fun run(params: Params) { + repo.logout(params.clearLocalRepositoryData) } + + class Params( + val clearLocalRepositoryData: Boolean = false + ) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/StartAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/StartAccount.kt index 9cea751b18..e2439b7775 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/StartAccount.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/StartAccount.kt @@ -1,9 +1,9 @@ package com.anytypeio.anytype.domain.auth.interactor +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.BaseUseCase -import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.config.FlavourConfigProvider /** @@ -12,10 +12,10 @@ import com.anytypeio.anytype.domain.config.FlavourConfigProvider class StartAccount( private val repository: AuthRepository, private val flavourConfigProvider: FlavourConfigProvider -) : BaseUseCase() { +) : BaseUseCase, StartAccount.Params>() { override suspend fun run(params: Params) = safe { - val (account, config) = repository.startAccount( + val (account, config, status) = repository.startAccount( id = params.id, path = params.path ) @@ -29,7 +29,7 @@ class StartAccount( enableSpaces = config.enableSpaces ?: false ) } - account.id + Pair(account.id, status) } /** diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt index 4e5f34ae8c..14f58c9ba3 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt @@ -1,10 +1,10 @@ package com.anytypeio.anytype.domain.auth.repo -import com.anytypeio.anytype.domain.auth.model.Account -import com.anytypeio.anytype.domain.auth.model.Wallet +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.FlavourConfig import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.ObjectType +import com.anytypeio.anytype.domain.auth.model.Account +import com.anytypeio.anytype.domain.auth.model.Wallet import kotlinx.coroutines.flow.Flow interface AuthRepository { @@ -14,10 +14,13 @@ interface AuthRepository { * @param id user account id * @param path wallet repository path */ - suspend fun startAccount(id: String, path: String): Pair + suspend fun startAccount(id: String, path: String): Triple suspend fun createAccount(name: String, avatarPath: String?, invitationCode: String): Account + suspend fun deleteAccount() : AccountStatus + suspend fun restoreAccount() : AccountStatus + suspend fun startLoadingAccounts() suspend fun saveAccount(account: Account) @@ -40,7 +43,7 @@ interface AuthRepository { suspend fun getMnemonic(): String - suspend fun logout() + suspend fun logout(clearLocalRepositoryData: Boolean) suspend fun getAccounts(): List diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/auth/StartAccountTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/auth/StartAccountTest.kt index bea81d4334..470e9c2970 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/auth/StartAccountTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/auth/StartAccountTest.kt @@ -1,12 +1,13 @@ package com.anytypeio.anytype.domain.auth +import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.CoroutineTestRule +import com.anytypeio.anytype.core_models.FlavourConfig import com.anytypeio.anytype.domain.auth.interactor.StartAccount import com.anytypeio.anytype.domain.auth.model.Account import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.Either -import com.anytypeio.anytype.core_models.CoroutineTestRule import com.anytypeio.anytype.domain.common.MockDataFactory -import com.anytypeio.anytype.core_models.FlavourConfig import com.anytypeio.anytype.domain.config.FlavourConfigProvider import com.nhaarman.mockitokotlin2.* import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -68,7 +69,7 @@ class StartAccountTest { id = id, path = path ) - } doReturn Pair(account, config) + } doReturn Triple(account, config, AccountStatus.Active) } startAccount.run(params) @@ -115,12 +116,12 @@ class StartAccountTest { id = id, path = path ) - } doReturn Pair(account, config) + } doReturn Triple(account, config, AccountStatus.Active) } val result = startAccount.run(params) - assertTrue { result == Either.Right(account.id) } + assertTrue { result == Either.Right(Pair(account.id, AccountStatus.Active)) } } @Test @@ -153,7 +154,7 @@ class StartAccountTest { id = id, path = path ) - } doReturn Pair(account, config) + } doReturn Triple(account, config, AccountStatus.Active) } val result = startAccount.run(params) @@ -165,7 +166,7 @@ class StartAccountTest { enableSpaces = false ) - assertTrue { result == Either.Right(account.id) } + assertTrue { result == Either.Right(Pair(account.id, AccountStatus.Active)) } } @Test @@ -198,7 +199,7 @@ class StartAccountTest { id = id, path = path ) - } doReturn Pair(account, config) + } doReturn Triple(account, config, AccountStatus.Active) } val result = startAccount.run(params) @@ -210,6 +211,6 @@ class StartAccountTest { enableSpaces = false ) - assertTrue { result == Either.Right(account.id) } + assertTrue { result == Either.Right(Pair(account.id, AccountStatus.Active)) } } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt index 99d3df7046..c608c0407f 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.middleware.auth +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.FlavourConfigEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -22,7 +23,7 @@ class AuthMiddleware( override suspend fun startAccount( id: String, path: String - ): Pair { + ): Triple { val response = middleware.selectAccount(id, path) val account = AccountEntity( id = response.id, @@ -35,7 +36,7 @@ class AuthMiddleware( enableChannelSwitch = response.enableChannelSwitch, enableSpaces = response.enableSpaces ) - return Pair(account, flavourConfig) + return Triple(account, flavourConfig, response.accountStatus ?: AccountStatus.Unknown) } override suspend fun createAccount( @@ -52,6 +53,9 @@ class AuthMiddleware( } } + override suspend fun deleteAccount(): AccountStatus = middleware.deleteAccount() + override suspend fun restoreAccount(): AccountStatus = middleware.restoreAccount() + override suspend fun recoverAccount() = withContext(Dispatchers.IO) { middleware.recoverAccount() } @@ -85,8 +89,8 @@ class AuthMiddleware( override suspend fun convertWallet(entropy: String): String = middleware.convertWallet(entropy) - override suspend fun logout() { - middleware.logout() + override suspend fun logout(clearLocalRepositoryData: Boolean) { + middleware.logout(clearLocalRepositoryData) } override suspend fun getVersion(): String { diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/AccountStatusMiddlewareChannel.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/AccountStatusMiddlewareChannel.kt new file mode 100644 index 0000000000..df898925c5 --- /dev/null +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/AccountStatusMiddlewareChannel.kt @@ -0,0 +1,22 @@ +package com.anytypeio.anytype.middleware.interactor + +import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.data.auth.account.AccountStatusRemoteChannel +import com.anytypeio.anytype.middleware.EventProxy +import com.anytypeio.anytype.middleware.mappers.core +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapNotNull + +class AccountStatusMiddlewareChannel( + private val events: EventProxy +) : AccountStatusRemoteChannel { + + override fun observe(): Flow = events.flow().mapNotNull { e -> + val updates = e.messages.filter { m -> + m.accountUpdate != null + } + val lastUpdate = updates.lastOrNull()?.accountUpdate + + lastUpdate?.status?.core() + } +} \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index 1645147a9a..eb93d0159a 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -92,6 +92,32 @@ class Middleware( ) } + @Throws(Exception::class) + fun deleteAccount() : AccountStatus { + val request = Rpc.Account.Delete.Request( + revert = false + ) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.accountDelete(request) + if (BuildConfig.DEBUG) logResponse(response) + val status = response.status + checkNotNull(status) { "Account status was null" } + return status.core() + } + + @Throws(Exception::class) + fun restoreAccount() : AccountStatus { + val request = Rpc.Account.Delete.Request( + revert = true + ) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.accountDelete(request) + if (BuildConfig.DEBUG) logResponse(response) + val status = response.status + checkNotNull(status) { "Account status was null" } + return status.core() + } + @Throws(Exception::class) fun recoverWallet(path: String, mnemonic: String) { val request = Rpc.Wallet.Recover.Request( @@ -114,8 +140,10 @@ class Middleware( } @Throws(Exception::class) - fun logout() { - val request: Rpc.Account.Stop.Request = Rpc.Account.Stop.Request() + fun logout(clearLocalRepositoryData: Boolean) { + val request: Rpc.Account.Stop.Request = Rpc.Account.Stop.Request( + removeData = clearLocalRepositoryData + ) if (BuildConfig.DEBUG) logRequest(request) val response = service.accountStop(request) if (BuildConfig.DEBUG) logResponse(response) @@ -155,7 +183,8 @@ class Middleware( enableDataView = config?.enableDataview, enableDebug = config?.enableDebug, enableChannelSwitch = config?.enableReleaseChannelSwitch, - enableSpaces = config?.enableSpaces + enableSpaces = config?.enableSpaces, + accountStatus = acc.status?.core() ) } diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt index 12ff14a3fc..78785f9558 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt @@ -1,5 +1,8 @@ package com.anytypeio.anytype.middleware.mappers +typealias MAccount = anytype.model.Account +typealias MAccountStatus = anytype.model.Account.Status +typealias MAccountStatusType = anytype.model.Account.StatusType typealias MBlock = anytype.model.Block typealias MBText = anytype.model.Block.Content.Text typealias MBTextStyle = anytype.model.Block.Content.Text.Style diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt index 60cdcb8bf5..7b916d453a 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToCoreModelMappers.kt @@ -557,4 +557,13 @@ fun MDVRestriction.toCoreModel(): DataViewRestriction? = when (this) { Restrictions.DataviewRestriction.DVRelation -> DataViewRestriction.RELATION Restrictions.DataviewRestriction.DVCreateObject -> DataViewRestriction.CREATE_OBJECT Restrictions.DataviewRestriction.DVNone -> null +} + +fun MAccountStatus.core(): AccountStatus = when (statusType) { + MAccountStatusType.Active -> AccountStatus.Active + MAccountStatusType.PendingDeletion -> AccountStatus.PendingDeletion( + deadline = deletionDate + ) + MAccountStatusType.StartedDeletion -> AccountStatus.Deleted + MAccountStatusType.Deleted -> AccountStatus.Deleted } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/model/SelectAccountResponse.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/model/SelectAccountResponse.kt index 578174becb..0e0d8dd3f8 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/model/SelectAccountResponse.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/model/SelectAccountResponse.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.middleware.model import anytype.model.Account +import com.anytypeio.anytype.core_models.AccountStatus class SelectAccountResponse( @@ -10,5 +11,6 @@ class SelectAccountResponse( val enableDataView: Boolean?, val enableDebug: Boolean?, val enableChannelSwitch: Boolean?, - val enableSpaces: Boolean? + val enableSpaces: Boolean?, + val accountStatus: AccountStatus? ) \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index 41daf79a14..611debbfea 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -21,6 +21,9 @@ interface MiddlewareService { @Throws(Exception::class) fun accountCreate(request: Account.Create.Request): Account.Create.Response + @Throws(Exception::class) + fun accountDelete(request: Account.Delete.Request) : Account.Delete.Response + @Throws(Exception::class) fun accountSelect(request: Account.Select.Request): Account.Select.Response diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index fcaf263926..915350006c 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.middleware.service import anytype.Rpc.* +import com.anytypeio.anytype.core_models.exceptions.AccountIsDeletedException import com.anytypeio.anytype.core_models.exceptions.CreateAccountException import com.anytypeio.anytype.data.auth.exception.BackwardCompatilityNotSupportedException import com.anytypeio.anytype.data.auth.exception.NotFoundObjectException @@ -75,6 +76,17 @@ class MiddlewareServiceImplementation : MiddlewareService { } } + override fun accountDelete(request: Account.Delete.Request): Account.Delete.Response { + val encoded = Service.accountDelete(Account.Delete.Request.ADAPTER.encode(request)) + val response = Account.Delete.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Account.Delete.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } + override fun accountSelect(request: Account.Select.Request): Account.Select.Response { val encoded = Service.accountSelect(Account.Select.Request.ADAPTER.encode(request)) val response = Account.Select.Response.ADAPTER.decode(encoded) @@ -91,7 +103,14 @@ class MiddlewareServiceImplementation : MiddlewareService { val response = Account.Recover.Response.ADAPTER.decode(encoded) val error = response.error if (error != null && error.code != Account.Recover.Response.Error.Code.NULL) { - throw Exception(error.description) + when(error.code) { + Account.Recover.Response.Error.Code.ACCOUNT_IS_DELETED -> { + throw AccountIsDeletedException() + } + else -> { + throw Exception(error.description) + } + } } else { return response } diff --git a/middleware/src/test/java/com/anytypeio/anytype/MiddlewareTest.kt b/middleware/src/test/java/com/anytypeio/anytype/MiddlewareTest.kt index ff0862d7d9..bf42fd3583 100644 --- a/middleware/src/test/java/com/anytypeio/anytype/MiddlewareTest.kt +++ b/middleware/src/test/java/com/anytypeio/anytype/MiddlewareTest.kt @@ -53,7 +53,7 @@ class MiddlewareTest { // TESTING - middleware.logout() + middleware.logout(false) verify(service, times(1)).accountStop(request) verifyNoMoreInteractions(service) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/DeletedAccountViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/DeletedAccountViewModel.kt new file mode 100644 index 0000000000..9b5ff057da --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/DeletedAccountViewModel.kt @@ -0,0 +1,125 @@ +package com.anytypeio.anytype.presentation.auth.account + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.domain.account.DateHelper +import com.anytypeio.anytype.domain.account.RestoreAccount +import com.anytypeio.anytype.domain.auth.interactor.Logout +import com.anytypeio.anytype.domain.base.BaseUseCase +import com.anytypeio.anytype.domain.base.Interactor +import com.anytypeio.anytype.presentation.common.BaseViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import timber.log.Timber +import kotlin.time.DurationUnit.DAYS +import kotlin.time.DurationUnit.MILLISECONDS +import kotlin.time.toDuration + +class DeletedAccountViewModel( + private val restoreAccount: RestoreAccount, + private val logout: Logout, + private val dateHelper: DateHelper +) : BaseViewModel() { + + val commands = MutableSharedFlow(replay = 0) + val progress = MutableStateFlow(0f) + val isLoggingOut = MutableStateFlow(false) + val date = MutableStateFlow(DeletionDate.Unknown) + + fun onStart(deadlineInMillis: Long, nowInMillis: Long) { + val remainingInMillis = (deadlineInMillis - nowInMillis) + if (remainingInMillis >= 0) { + val days = remainingInMillis.toDuration(MILLISECONDS).toDouble(DAYS) + when { + dateHelper.isToday(deadlineInMillis) -> { + date.value = DeletionDate.Today + } + dateHelper.isTomorrow(deadlineInMillis) -> { + date.value = DeletionDate.Tomorrow + } + else -> { + date.value = DeletionDate.Later(days = days.toInt()) + } + } + val delta = 1f - (days.toFloat() / DEADLINE_DURATION_IN_DAYS) + this.progress.value = delta + } + } + + fun cancelDeletionClicked() { + viewModelScope.launch { + restoreAccount(BaseUseCase.None).process( + success = { status -> + when (status) { + is AccountStatus.Active -> { + commands.emit(Command.Resume) + } + is AccountStatus.Deleted -> { + // TODO + } + is AccountStatus.PendingDeletion -> { + // TODO + } + } + }, + failure = { + Timber.e(it, "Error while cancelling account deletion") + sendToast("Error while cancelling account deletion. Please, try again later.") + } + ) + } + } + + fun onLogoutAndClearDataClicked() { + viewModelScope.launch { + logout(Logout.Params(clearLocalRepositoryData = true)).collect { status -> + when (status) { + is Interactor.Status.Error -> { + isLoggingOut.value = false + } + is Interactor.Status.Started -> { + isLoggingOut.value = true + } + is Interactor.Status.Success -> { + isLoggingOut.value = false + commands.emit(Command.Logout) + } + } + } + } + } + + class Factory( + private val restoreAccount: RestoreAccount, + private val logout: Logout, + private val helper: DateHelper + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return DeletedAccountViewModel( + restoreAccount = restoreAccount, + logout = logout, + dateHelper = helper + ) as T + } + } + + sealed class Command { + object Resume : Command() + object Logout : Command() + } + + sealed class DeletionDate { + object Unknown : DeletionDate() + object Today : DeletionDate() + object Tomorrow : DeletionDate() + data class Later(val days: Int) : DeletionDate() + } + + companion object { + const val DEADLINE_DURATION_IN_DAYS = 30f + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/SelectAccountViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/SelectAccountViewModel.kt index 4c1c774b1a..570d637bfd 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/SelectAccountViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/SelectAccountViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.core_models.exceptions.AccountIsDeletedException import com.anytypeio.anytype.core_utils.common.EventWrapper import com.anytypeio.anytype.domain.auth.interactor.ObserveAccounts import com.anytypeio.anytype.domain.auth.interactor.StartLoadingAccounts @@ -62,8 +63,12 @@ class SelectAccountViewModel( ) { result -> result.either( fnL = { e -> + if (e is AccountIsDeletedException) { + error.postValue("This account is deleted. Try using another account or create a new one.") + } else { + error.postValue("Error while account loading \n ${e.localizedMessage}") + } Timber.e(e, "Error while account loading") - error.postValue("Error while account loading \n ${e.localizedMessage}") navigation.postValue(EventWrapper(AppNavigation.Command.Exit)) }, fnR = { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/SetupSelectedAccountViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/SetupSelectedAccountViewModel.kt index 0989a336e2..5f63f82190 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/SetupSelectedAccountViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/auth/account/SetupSelectedAccountViewModel.kt @@ -8,6 +8,7 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary.openAccount import com.anytypeio.anytype.analytics.base.sendEvent import com.anytypeio.anytype.analytics.base.updateUserProperties import com.anytypeio.anytype.analytics.props.UserProperty +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_utils.common.EventWrapper import com.anytypeio.anytype.domain.auth.interactor.StartAccount @@ -61,12 +62,22 @@ class SetupSelectedAccountViewModel( error.postValue("$ERROR_MESSAGE: $msg") Timber.e(it, "Error while selecting account with id: $id") }, - success = { accountId -> + success = { (accountId, status) -> migrationMessageJob.cancel() isMigrationInProgress.value = false updateUserProps(accountId) sendEvent(startTime) - proceedWithUpdatingObjectTypesStore() + if (status is AccountStatus.PendingDeletion) { + navigation.postValue( + EventWrapper( + AppNavigation.Command.DeletedAccountScreen( + deadline = status.deadline + ) + ) + ) + } else { + proceedWithUpdatingObjectTypesStore() + } } ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt index b963daa5f3..741b8ccb8c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModel.kt @@ -3,19 +3,21 @@ package com.anytypeio.anytype.presentation.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.analytics.base.EventsDictionary.openAccount import com.anytypeio.anytype.analytics.base.sendEvent import com.anytypeio.anytype.analytics.base.updateUserProperties -import com.anytypeio.anytype.analytics.props.Props import com.anytypeio.anytype.analytics.props.UserProperty +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.Wallpaper +import com.anytypeio.anytype.domain.account.InterceptAccountStatus import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount +import com.anytypeio.anytype.domain.auth.interactor.Logout import com.anytypeio.anytype.domain.base.BaseUseCase +import com.anytypeio.anytype.domain.base.Interactor import com.anytypeio.anytype.domain.wallpaper.ObserveWallpaper import com.anytypeio.anytype.domain.wallpaper.RestoreWallpaper +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import timber.log.Timber @@ -23,10 +25,14 @@ class MainViewModel( private val launchAccount: LaunchAccount, private val observeWallpaper: ObserveWallpaper, private val restoreWallpaper: RestoreWallpaper, - private val analytics: Analytics + private val analytics: Analytics, + private val interceptAccountStatus: InterceptAccountStatus, + private val logout: Logout ) : ViewModel() { val wallpaper = MutableStateFlow(Wallpaper.Default) + val commands = MutableSharedFlow(replay = 0) + val toasts = MutableSharedFlow(replay = 0) init { viewModelScope.launch { restoreWallpaper(BaseUseCase.None) } @@ -35,6 +41,43 @@ class MainViewModel( wallpaper.value = it } } + viewModelScope.launch { + interceptAccountStatus.build().collect { status -> + when (status) { + is AccountStatus.PendingDeletion -> { + commands.emit( + Command.ShowDeletedAccountScreen( + deadline = status.deadline + ) + ) + } + is AccountStatus.Deleted -> { + proceedWithLogoutDueToAccountDeletion() + } + else -> { + // Do nothing + } + } + } + } + } + + private fun proceedWithLogoutDueToAccountDeletion() { + viewModelScope.launch { + logout(Logout.Params(false)).collect { status -> + when (status) { + is Interactor.Status.Error -> { + toasts.emit("Error while logging out due to account deletion") + } + is Interactor.Status.Started -> { + toasts.emit("Your account is deleted. Logging out...") + } + is Interactor.Status.Success -> { + commands.emit(Command.LogoutDueToAccountDeletion) + } + } + } + } } fun onRestore() { @@ -65,4 +108,9 @@ class MainViewModel( eventName = openAccount ) } + + sealed class Command { + data class ShowDeletedAccountScreen(val deadline: Long) : Command() + object LogoutDueToAccountDeletion : Command() + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModelFactory.kt index 65c75f2c49..34b041a119 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/main/MainViewModelFactory.kt @@ -3,7 +3,9 @@ package com.anytypeio.anytype.presentation.main import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.domain.account.InterceptAccountStatus import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount +import com.anytypeio.anytype.domain.auth.interactor.Logout import com.anytypeio.anytype.domain.wallpaper.ObserveWallpaper import com.anytypeio.anytype.domain.wallpaper.RestoreWallpaper @@ -11,7 +13,9 @@ class MainViewModelFactory( private val launchAccount: LaunchAccount, private val analytics: Analytics, private val observeWallpaper: ObserveWallpaper, - private val restoreWallpaper: RestoreWallpaper + private val restoreWallpaper: RestoreWallpaper, + private val interceptAccountStatus: InterceptAccountStatus, + private val logout: Logout ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( @@ -20,6 +24,8 @@ class MainViewModelFactory( launchAccount = launchAccount, analytics = analytics, observeWallpaper = observeWallpaper, - restoreWallpaper = restoreWallpaper + restoreWallpaper = restoreWallpaper, + interceptAccountStatus = interceptAccountStatus, + logout = logout ) as T } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt index f6db4af285..7612307eab 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt @@ -48,6 +48,10 @@ interface AppNavigation { fun openCreateSetScreen(ctx: Id) fun openUpdateAppScreen() + fun deletedAccountScreen(deadline: Long) + + fun logout() + sealed class Command { object Exit : Command() @@ -98,6 +102,8 @@ interface AppNavigation { data class OpenCreateSetScreen(val ctx: Id) : Command() object OpenUpdateAppScreen : Command() + + data class DeletedAccountScreen(val deadline: Long) : Command() } interface Provider { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/profile/ProfileViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/profile/ProfileViewModel.kt deleted file mode 100644 index 19bc9b642c..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/profile/ProfileViewModel.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.anytypeio.anytype.presentation.profile - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.analytics.base.EventsDictionary -import com.anytypeio.anytype.analytics.base.sendEvent -import com.anytypeio.anytype.analytics.base.updateUserProperties -import com.anytypeio.anytype.analytics.props.UserProperty -import com.anytypeio.anytype.core_utils.common.EventWrapper -import com.anytypeio.anytype.core_utils.ui.ViewState -import com.anytypeio.anytype.core_utils.ui.ViewStateViewModel -import com.anytypeio.anytype.domain.auth.interactor.GetCurrentAccount -import com.anytypeio.anytype.domain.auth.interactor.GetLibraryVersion -import com.anytypeio.anytype.domain.auth.interactor.Logout -import com.anytypeio.anytype.domain.base.BaseUseCase -import com.anytypeio.anytype.domain.base.Interactor -import com.anytypeio.anytype.presentation.navigation.AppNavigation -import com.anytypeio.anytype.presentation.navigation.SupportNavigation -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import timber.log.Timber - -@Deprecated("legacy") -open class ProfileViewModel( - private val getCurrentAccount: GetCurrentAccount, - private val logout: Logout, - private val analytics: Analytics, - private val getLibraryVersion: GetLibraryVersion -) : ViewStateViewModel>(), - SupportNavigation> { - - private val _isLoggingOut = MutableStateFlow(false) - val isLoggingOut: StateFlow = _isLoggingOut - - private var target = "" - - private val libVersion = MutableLiveData("") - val version: LiveData = libVersion - - override val navigation: MutableLiveData> = - MutableLiveData() - - fun onViewCreated() { - stateData.postValue(ViewState.Init) - proceedWithGettingAccount() - proceedWithGetVersion() - } - - fun onBackButtonClicked() { - navigation.postValue(EventWrapper(AppNavigation.Command.Exit)) - } - - private fun proceedWithGettingAccount() { - getCurrentAccount.invoke(viewModelScope, BaseUseCase.None) { result -> - result.either( - fnL = { e -> Timber.e(e, "Error while getting account") }, - fnR = { account -> - target = account.id - stateData.postValue( - ViewState.Success( - data = ProfileView( - name = account.name, - avatar = account.avatar - ) - ) - ) - } - ) - } - } - - private fun proceedWithGetVersion() { - getLibraryVersion.invoke(scope = viewModelScope, params = BaseUseCase.None) { result -> - result.either( - fnL = { e -> Timber.e(e, "Error while getting middleware version") }, - fnR = { version -> libVersion.postValue(version) } - ) - } - } - - fun onLogoutClicked() { - val startTime = System.currentTimeMillis() - viewModelScope.launch { - logout(params = BaseUseCase.None).collect { status -> - when (status) { - is Interactor.Status.Started -> { - _isLoggingOut.value = true - } - is Interactor.Status.Success -> { - _isLoggingOut.value = false - updateUserProperties( - analytics = analytics, - userProperty = UserProperty.AccountId(null) - ) - navigation.postValue(EventWrapper(AppNavigation.Command.StartSplashFromDesktop)) - } - is Interactor.Status.Error -> { - Timber.e(status.throwable, "Error while logging out") - } - } - } - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/profile/ProfileViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/profile/ProfileViewModelFactory.kt deleted file mode 100644 index 38826fffac..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/profile/ProfileViewModelFactory.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.anytypeio.anytype.presentation.profile - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.domain.auth.interactor.GetCurrentAccount -import com.anytypeio.anytype.domain.auth.interactor.GetLibraryVersion -import com.anytypeio.anytype.domain.auth.interactor.Logout - -class ProfileViewModelFactory( - private val logout: Logout, - private val getCurrentAccount: GetCurrentAccount, - private val analytics: Analytics, - private val getLibraryVersion: GetLibraryVersion -) : ViewModelProvider.Factory { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ProfileViewModel( - logout = logout, - getCurrentAccount = getCurrentAccount, - analytics = analytics, - getLibraryVersion = getLibraryVersion - ) as T - } -} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/auth/DeleteAccountViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/auth/DeleteAccountViewModelTest.kt new file mode 100644 index 0000000000..3a619070d3 --- /dev/null +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/auth/DeleteAccountViewModelTest.kt @@ -0,0 +1,129 @@ +package com.anytypeio.anytype.presentation.auth + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.anytypeio.anytype.domain.account.DateHelper +import com.anytypeio.anytype.domain.account.RestoreAccount +import com.anytypeio.anytype.domain.auth.interactor.Logout +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.presentation.auth.account.DeletedAccountViewModel +import com.anytypeio.anytype.presentation.util.CoroutinesTestRule +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import java.time.Duration +import kotlin.test.assertEquals + +class DeleteAccountViewModelTest { + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @get:Rule + val coroutineTestRule = CoroutinesTestRule() + + @Mock + lateinit var repo: AuthRepository + + @Mock + lateinit var helper: DateHelper + + lateinit var restoreAccount: RestoreAccount + lateinit var logout: Logout + + lateinit var vm: DeletedAccountViewModel + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + restoreAccount = RestoreAccount( + repo = repo + ) + logout = Logout( + repo = repo + ) + vm = DeletedAccountViewModel( + restoreAccount = restoreAccount, + logout = logout, + dateHelper = helper + ) + } + + @Test + fun `progress should be zero when view model is created`() { + assertEquals( + expected = 0f, + actual = vm.progress.value + ) + } + + @Test + fun `progress should be equal to 0,5 when deadline equals to 15 days`() { + val nowInMillis = System.currentTimeMillis() + val deadlineInMillis = nowInMillis + Duration.ofDays(15).toMillis() + vm.onStart( + deadlineInMillis = deadlineInMillis, + nowInMillis = nowInMillis + ) + assertEquals( + expected = 0.5f, + actual = vm.progress.value + ) + } + + @Test + fun `progress should be equal to 0,6 when deadline equals to 15 days`() { + val nowInMillis = System.currentTimeMillis() + val deadlineInMillis = nowInMillis + Duration.ofDays(10).toMillis() + vm.onStart( + deadlineInMillis = deadlineInMillis, + nowInMillis = nowInMillis + ) + assertEquals( + expected = 1 - 1 / 3f, + actual = vm.progress.value + ) + } + + @Test + fun `progress should be equal to 0,3 when deadline equals to 15 days`() { + val nowInMillis = System.currentTimeMillis() + val deadlineInMillis = nowInMillis + Duration.ofDays(20).toMillis() + vm.onStart( + deadlineInMillis = deadlineInMillis, + nowInMillis = nowInMillis + ) + assertEquals( + expected = 1 - 2 / 3f, + actual = vm.progress.value + ) + } + + @Test + fun `progress should be equal to 1,0 when deadline equals to now`() { + val nowInMillis = System.currentTimeMillis() + vm.onStart( + nowInMillis = nowInMillis, + deadlineInMillis = nowInMillis + ) + assertEquals( + expected = 1f, + actual = vm.progress.value + ) + } + + @Test + fun `progress should be equal to 0 when deadline is in 30days`() { + val nowInMillis = System.currentTimeMillis() + val deadlineInMillis = nowInMillis + Duration.ofDays(30).toMillis() + vm.onStart( + deadlineInMillis = deadlineInMillis, + nowInMillis = nowInMillis + ) + assertEquals( + expected = 0f, + actual = vm.progress.value + ) + } +} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/auth/SetupSelectedAccountViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/auth/SetupSelectedAccountViewModelTest.kt index 8dd34d64d6..284e6d0863 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/auth/SetupSelectedAccountViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/auth/SetupSelectedAccountViewModelTest.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.auth import MockDataFactory import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.core_models.AccountStatus import com.anytypeio.anytype.core_models.FlavourConfig import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider import com.anytypeio.anytype.domain.auth.interactor.StartAccount @@ -119,14 +120,15 @@ class SetupSelectedAccountViewModelTest { id = any(), path = any() ) - } doReturn Pair( + } doReturn Triple( Account( id = MockDataFactory.randomUuid(), name = MockDataFactory.randomString(), avatar = null, color = null ), - FlavourConfig() + FlavourConfig(), + AccountStatus.Active ) } diff --git a/protocol/src/main/proto/localstore.proto b/protocol/src/main/proto/localstore.proto index 8cb3555fcc..bed763ba35 100644 --- a/protocol/src/main/proto/localstore.proto +++ b/protocol/src/main/proto/localstore.proto @@ -1,10 +1,11 @@ syntax = "proto3"; package anytype.model; -option go_package = "pkg/lib/pb/model"; import "google/protobuf/struct.proto"; import "models.proto"; +option go_package = "pkg/lib/pb/model"; + message ObjectInfo { string id = 1; repeated string objectTypeUrls = 2; // deprecated diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/AccountAndDataScreen.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/AccountAndDataScreen.kt index 6e9d2eafce..4c95917ec4 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/AccountAndDataScreen.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/AccountAndDataScreen.kt @@ -21,7 +21,6 @@ import com.anytypeio.anytype.ui_settings.R fun AccountAndDataScreen( onKeychainPhraseClicked: () -> Unit, onClearFileCachedClicked: () -> Unit, - onResetAccountClicked: () -> Unit, onDeleteAccountClicked: () -> Unit, onPinCodeClicked: () -> Unit, onLogoutClicked: () -> Unit, diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/AccountAndDataViewModel.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/AccountAndDataViewModel.kt index 4705727c02..165ede20af 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/AccountAndDataViewModel.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/AccountAndDataViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.analytics.base.EventsDictionary import com.anytypeio.anytype.analytics.base.sendEvent +import com.anytypeio.anytype.domain.account.DeleteAccount import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.base.Interactor import com.anytypeio.anytype.domain.device.ClearFileCache @@ -15,7 +16,8 @@ import timber.log.Timber class AccountAndDataViewModel( private val clearFileCache: ClearFileCache, - private val analytics: Analytics + private val analytics: Analytics, + private val deleteAccount: DeleteAccount ) : ViewModel() { val isClearFileCacheInProgress = MutableStateFlow(false) @@ -53,12 +55,29 @@ class AccountAndDataViewModel( ) } - class Factory(private val clearFileCache: ClearFileCache, private val analytics: Analytics) : - ViewModelProvider.Factory { + fun onDeleteAccountClicked() { + viewModelScope.launch { + deleteAccount(BaseUseCase.None).process( + success = { + Timber.d("Successfully deleted account, status") + }, + failure = { + Timber.e(it, "Error while deleting account") + } + ) + } + } + + class Factory( + private val clearFileCache: ClearFileCache, + private val deleteAccount: DeleteAccount, + private val analytics: Analytics + ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { return AccountAndDataViewModel( clearFileCache = clearFileCache, + deleteAccount = deleteAccount, analytics = analytics ) as T } diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/LogoutWarningViewModel.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/LogoutWarningViewModel.kt index 2e65906a10..a38b023448 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/LogoutWarningViewModel.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/LogoutWarningViewModel.kt @@ -9,7 +9,6 @@ import com.anytypeio.anytype.analytics.base.EventsPropertiesKey import com.anytypeio.anytype.analytics.base.sendEvent import com.anytypeio.anytype.analytics.props.Props import com.anytypeio.anytype.domain.auth.interactor.Logout -import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.base.Interactor import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -27,7 +26,7 @@ class LogoutWarningViewModel( fun onLogoutClicked() { val startTime = System.currentTimeMillis() viewModelScope.launch { - logout(params = BaseUseCase.None).collect { status -> + logout(params = Logout.Params()).collect { status -> when (status) { is Interactor.Status.Started -> { isLoggingOut.value = true