From 3d2239bee359d103c6fce2b2bb1ba77c00fa84ab Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Wed, 23 Feb 2022 19:47:15 +0300 Subject: [PATCH] App | Enhancement | Logout warning (#2121) --- .../anytype/di/common/ComponentManager.kt | 6 ++ .../di/feature/settings/AccountAndDataDI.kt | 6 +- .../di/feature/settings/LogoutWarningDI.kt | 39 ++++++++ .../anytype/di/main/MainComponent.kt | 2 + .../ui/settings/AccountAndDataFragment.kt | 25 +---- .../ui/settings/LogoutWarningFragment.kt | 99 +++++++++++++++++++ app/src/main/res/drawable/ic_drag.xml | 2 +- .../rectangle_keychain_background.xml | 2 +- app/src/main/res/navigation/graph.xml | 10 ++ app/src/main/res/values/strings.xml | 1 + .../anytype/core_ui/foundation/Foundation.kt | 83 ++++++++++++++++ .../account/AccountAndDataViewModel.kt | 41 +------- .../account/LogoutWarningViewModel.kt | 49 +++++++++ ui-settings/src/main/res/values/strings.xml | 5 + 14 files changed, 306 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/com/anytypeio/anytype/di/feature/settings/LogoutWarningDI.kt create mode 100644 app/src/main/java/com/anytypeio/anytype/ui/settings/LogoutWarningFragment.kt create mode 100644 ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/LogoutWarningViewModel.kt 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 59f7bdfa57..231858f59d 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 @@ -11,6 +11,7 @@ import com.anytypeio.anytype.di.feature.sets.viewer.ViewerCardSizeSelectModule import com.anytypeio.anytype.di.feature.sets.viewer.ViewerImagePreviewSelectModule import com.anytypeio.anytype.di.feature.settings.AboutAppModule import com.anytypeio.anytype.di.feature.settings.AccountAndDataModule +import com.anytypeio.anytype.di.feature.settings.LogoutWarningModule import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectModule import com.anytypeio.anytype.di.main.MainComponent @@ -607,10 +608,15 @@ class ComponentManager(private val main: MainComponent) { val aboutAppComponent = Component { main.aboutAppComponent().module(AboutAppModule).build() } + val accountAndDataComponent = Component { main.accountAndDataComponent().module(AccountAndDataModule).build() } + val logoutWarningComponent = Component { + main.logoutWarningComponent().module(LogoutWarningModule).build() + } + class Component(private val builder: () -> T) { private var instance: T? = null 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 d8eb526721..be385ef94f 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 @@ -30,11 +30,9 @@ object AccountAndDataModule { @Provides @PerScreen fun provideViewModelFactory( - clearFileCache: ClearFileCache, - logout: Logout + clearFileCache: ClearFileCache ): AccountAndDataViewModel.Factory = AccountAndDataViewModel.Factory( - clearFileCache = clearFileCache, - logout = logout + clearFileCache = clearFileCache ) @JvmStatic diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/settings/LogoutWarningDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/settings/LogoutWarningDI.kt new file mode 100644 index 0000000000..fcc9a78da0 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/settings/LogoutWarningDI.kt @@ -0,0 +1,39 @@ +package com.anytypeio.anytype.di.feature.settings + +import com.anytypeio.anytype.core_utils.di.scope.PerScreen +import com.anytypeio.anytype.domain.auth.interactor.Logout +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.ui.settings.LogoutWarningFragment +import com.anytypeio.anytype.ui_settings.account.LogoutWarningViewModel +import dagger.Module +import dagger.Provides +import dagger.Subcomponent + +@Subcomponent(modules = [LogoutWarningModule::class]) +@PerScreen +interface LogoutWarningSubComponent { + + @Subcomponent.Builder + interface Builder { + fun module(module: LogoutWarningModule): Builder + fun build(): LogoutWarningSubComponent + } + + fun inject(fragment: LogoutWarningFragment) +} + +@Module +object LogoutWarningModule { + + @JvmStatic + @Provides + @PerScreen + fun provideViewModelFactory( + logout: Logout + ): LogoutWarningViewModel.Factory = LogoutWarningViewModel.Factory(logout = logout) + + @JvmStatic + @Provides + @PerScreen + fun logout(repo: AuthRepository): Logout = Logout(repo) +} \ No newline at end of file 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 23cbba3f63..84c4a5e58a 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 @@ -4,6 +4,7 @@ import com.anytypeio.anytype.app.AndroidApplication import com.anytypeio.anytype.di.feature.* import com.anytypeio.anytype.di.feature.settings.AboutAppSubComponent import com.anytypeio.anytype.di.feature.settings.AccountAndDataSubComponent +import com.anytypeio.anytype.di.feature.settings.LogoutWarningSubComponent import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectSubComponent import dagger.Component import javax.inject.Singleton @@ -54,6 +55,7 @@ interface MainComponent { fun debugSettingsBuilder(): DebugSettingsSubComponent.Builder fun keychainPhraseComponentBuilder(): KeychainPhraseSubComponent.Builder fun otherSettingsComponentBuilder(): OtherSettingsSubComponent.Builder + fun logoutWarningComponent() : LogoutWarningSubComponent.Builder //endregion } \ No newline at end of file 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 8e166a8770..81fbb558c8 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 @@ -9,9 +9,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy 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_utils.ext.toast @@ -20,7 +17,6 @@ import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.ui.dashboard.ClearCacheAlertFragment import com.anytypeio.anytype.ui_settings.account.AccountAndDataScreen import com.anytypeio.anytype.ui_settings.account.AccountAndDataViewModel -import kotlinx.coroutines.launch import javax.inject.Inject class AccountAndDataFragment : BaseBottomSheetComposeFragment() { @@ -34,6 +30,10 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() { findNavController().navigate(R.id.keychainDialog) } + private val onLogoutClicked = { + findNavController().navigate(R.id.logoutWarningScreen) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -48,7 +48,7 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() { onClearFileCachedClicked = { proceedWithClearFileCacheWarning() }, onDeleteAccountClicked = { toast(resources.getString(R.string.coming_soon)) }, onResetAccountClicked = { toast(resources.getString(R.string.coming_soon)) }, - onLogoutClicked = { vm.onLogoutClicked() }, + onLogoutClicked = onLogoutClicked, onPinCodeClicked = { toast(resources.getString(R.string.coming_soon)) }, isLogoutInProgress = vm.isLoggingOut.collectAsState().value, isClearCacheInProgress = vm.isClearFileCacheInProgress.collectAsState().value @@ -58,21 +58,6 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() { } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - vm.commands.collect { command -> - when(command) { - AccountAndDataViewModel.Command.Logout -> { - findNavController().navigate(R.id.actionLogout) - } - } - } - } - } - } - private fun proceedWithClearFileCacheWarning() { val dialog = ClearCacheAlertFragment.new() dialog.onClearAccepted = { vm.onClearFileCacheAccepted() } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/LogoutWarningFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/LogoutWarningFragment.kt new file mode 100644 index 0000000000..ab9578fe3c --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/LogoutWarningFragment.kt @@ -0,0 +1,99 @@ +package com.anytypeio.anytype.ui.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +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.Warning +import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment +import com.anytypeio.anytype.di.common.componentManager +import com.anytypeio.anytype.ui_settings.account.LogoutWarningViewModel +import com.google.android.material.bottomsheet.BottomSheetBehavior +import kotlinx.coroutines.launch +import javax.inject.Inject + +class LogoutWarningFragment : BaseBottomSheetComposeFragment() { + + @Inject + lateinit var factory: LogoutWarningViewModel.Factory + + private val vm by viewModels { factory } + + private val onBackupPhraseClicked = { + findNavController().navigate(R.id.keychainDialog) + } + + 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.log_out), + cancelButtonText = stringResource(R.string.back_up_your_phrase), + title = stringResource(R.string.have_you_back_up_your_keychain), + subtitle = stringResource(R.string.you_will_need_to_sign_in), + onNegativeClick = onBackupPhraseClicked, + onPositiveClick = { vm.onLogoutClicked() }, + isInProgress = vm.isLoggingOut.collectAsState().value + ) + } + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + vm.commands.collect { command -> + when (command) { + LogoutWarningViewModel.Command.Logout -> { + findNavController().navigate(R.id.actionLogout) + } + } + } + } + launch { + vm.isLoggingOut.collect { isLoggingOut -> + isCancelable = isLoggingOut == false + } + } + } + } + } + + override fun setCancelable(cancelable: Boolean) { + super.setCancelable(cancelable) + dialog?.let { d -> + d.setCanceledOnTouchOutside(cancelable) + d.window?.decorView?.findViewById(R.id.design_bottom_sheet)?.let { + BottomSheetBehavior.from(it).isHideable = cancelable + } + } + } + + override fun injectDependencies() { + componentManager().logoutWarningComponent.get().inject(this) + } + + override fun releaseDependencies() { + componentManager().logoutWarningComponent.release() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_drag.xml b/app/src/main/res/drawable/ic_drag.xml index 34bc2af3de..c8cad1cc05 100644 --- a/app/src/main/res/drawable/ic_drag.xml +++ b/app/src/main/res/drawable/ic_drag.xml @@ -4,6 +4,6 @@ android:viewportWidth="36" android:viewportHeight="4"> diff --git a/app/src/main/res/drawable/rectangle_keychain_background.xml b/app/src/main/res/drawable/rectangle_keychain_background.xml index ee97351fe7..b27d4e8d32 100644 --- a/app/src/main/res/drawable/rectangle_keychain_background.xml +++ b/app/src/main/res/drawable/rectangle_keychain_background.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index 1d524f96d7..d9bba9e562 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -189,6 +189,16 @@ android:id="@+id/aboutAppScreen" android:name="com.anytypeio.anytype.ui.settings.AboutAppFragment" /> + + + + All files will be deleted from your current device. They can be downloaded again from a backup node or another device. new profile + Back up diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/Foundation.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/Foundation.kt index 8eb7bdaed1..1fc26a57c3 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/Foundation.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/foundation/Foundation.kt @@ -3,18 +3,23 @@ package com.anytypeio.anytype.core_ui.foundation import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.anytypeio.anytype.core_ui.R @Composable @@ -100,4 +105,82 @@ fun Arrow() { end = 20.dp ) ) +} + +@Composable +fun Warning( + title: String, + subtitle: String, + actionButtonText: String, + cancelButtonText: String, + onNegativeClick: () -> Unit, + onPositiveClick: () -> Unit, + isInProgress: Boolean = false +) { + Column { + Text( + style = MaterialTheme.typography.h2, + text = title, + modifier = Modifier.padding( + top = 24.dp, + start = 20.dp, + end = 20.dp + ), + color = colorResource(R.color.text_primary) + ) + Text( + text = subtitle, + modifier = Modifier.padding( + top = 12.dp, + start = 20.dp, + end = 20.dp + ), + color = colorResource(R.color.text_primary) + ) + Row( + modifier = Modifier.height(68.dp).padding( + top = 8.dp, + start = 20.dp, + end = 20.dp + ).fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier.height(48.dp).border( + width = 1.dp, + color = colorResource(R.color.shape_primary), + shape = RoundedCornerShape(10.dp) + ).weight(1.0f, true).clickable(onClick = onNegativeClick), + contentAlignment = Alignment.Center + ) { + Text( + text = cancelButtonText, + color = colorResource(R.color.text_primary), + fontSize = 17.sp + ) + } + Spacer(modifier = Modifier.width(10.dp)) + Box( + modifier = Modifier.height(48.dp).background( + color = colorResource(R.color.anytype_text_red), + shape = RoundedCornerShape(10.dp) + ).weight(1.0f, true).clickable(onClick = onPositiveClick), + contentAlignment = Alignment.Center + ) { + Text( + text = actionButtonText, + color = Color.White, + fontSize = 17.sp, + fontWeight = FontWeight.SemiBold + ) + if (isInProgress) { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.CenterEnd).padding(end = 20.dp).size(16.dp), + color = Color.White, + strokeWidth = 2.dp + ) + } + } + } + } } \ No newline at end of file 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 9881c7b57d..9241e0a7bc 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 @@ -3,21 +3,14 @@ package com.anytypeio.anytype.ui_settings.account import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -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.device.ClearFileCache -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import timber.log.Timber -class AccountAndDataViewModel( - private val clearFileCache: ClearFileCache, - private val logout: Logout, -) : ViewModel() { - - val commands = MutableSharedFlow(replay = 0) +class AccountAndDataViewModel(private val clearFileCache: ClearFileCache) : ViewModel() { val isClearFileCacheInProgress = MutableStateFlow(false) val isLoggingOut = MutableStateFlow(false) @@ -43,40 +36,12 @@ class AccountAndDataViewModel( } } - fun onLogoutClicked() { - viewModelScope.launch { - logout(params = BaseUseCase.None).collect { status -> - when (status) { - is Interactor.Status.Started -> { - isLoggingOut.value = true - } - is Interactor.Status.Success -> { - isLoggingOut.value = false - commands.emit(Command.Logout) - } - is Interactor.Status.Error -> { - isLoggingOut.value = true - Timber.e(status.throwable, "Error while logging out") - } - } - } - } - } - - class Factory( - private val clearFileCache: ClearFileCache, - private val logout: Logout, - ) : ViewModelProvider.Factory { + class Factory(private val clearFileCache: ClearFileCache) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { return AccountAndDataViewModel( - clearFileCache = clearFileCache, - logout = logout + clearFileCache = clearFileCache ) as T } } - - sealed class Command { - object Logout : Command() - } } \ No newline at end of file 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 new file mode 100644 index 0000000000..97f2378c3a --- /dev/null +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/account/LogoutWarningViewModel.kt @@ -0,0 +1,49 @@ +package com.anytypeio.anytype.ui_settings.account + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +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 +import kotlinx.coroutines.launch +import timber.log.Timber + +class LogoutWarningViewModel(private val logout: Logout) : ViewModel() { + + val commands = MutableSharedFlow(replay = 0) + val isLoggingOut = MutableStateFlow(false) + + fun onLogoutClicked() { + viewModelScope.launch { + logout(params = BaseUseCase.None).collect { status -> + when (status) { + is Interactor.Status.Started -> { + isLoggingOut.value = true + } + is Interactor.Status.Success -> { + isLoggingOut.value = false + commands.emit(Command.Logout) + } + is Interactor.Status.Error -> { + isLoggingOut.value = true + Timber.e(status.throwable, "Error while logging out") + } + } + } + } + } + + class Factory(private val logout: Logout) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return LogoutWarningViewModel(logout = logout) as T + } + } + + sealed class Command { + object Logout : Command() + } +} \ No newline at end of file diff --git a/ui-settings/src/main/res/values/strings.xml b/ui-settings/src/main/res/values/strings.xml index 3fd7338b60..59ccc3e736 100644 --- a/ui-settings/src/main/res/values/strings.xml +++ b/ui-settings/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ + About App version Library @@ -17,4 +18,8 @@ Pin code Access Wallpaper + + Have you backed up your keychain phrase? + You will need it to sign in. Keep it in a safe place. If you lose it, you can no longer access your account. + \ No newline at end of file