mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1475 Onboarding | Enhancement | Blocking back navigation when logging out after during sign-up (#171)
This commit is contained in:
parent
c2a9d50f14
commit
e74a56fc0f
10 changed files with 129 additions and 86 deletions
|
@ -964,6 +964,8 @@ class ComponentManager(
|
|||
fun release() {
|
||||
instance = null
|
||||
}
|
||||
|
||||
fun isInitialized() = instance != null
|
||||
}
|
||||
|
||||
class ComponentMap<T>(private val builder: () -> T) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
|||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.config.ConfigStorage
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
import com.anytypeio.anytype.domain.device.PathProvider
|
||||
import com.anytypeio.anytype.domain.`object`.SetupMobileUseCaseSkip
|
||||
import com.anytypeio.anytype.domain.platform.MetricsProvider
|
||||
|
@ -93,6 +94,7 @@ object OnboardingVoidModule {
|
|||
|
||||
interface OnboardingVoidDependencies : ComponentDependencies {
|
||||
fun authRepository(): AuthRepository
|
||||
fun userSettings(): UserSettingsRepository
|
||||
fun blockRepository(): BlockRepository
|
||||
fun configStorage(): ConfigStorage
|
||||
fun workspaceManager(): WorkspaceManager
|
||||
|
|
|
@ -61,7 +61,6 @@ import com.anytypeio.anytype.core_utils.insets.RootViewDeferringInsetsCallback
|
|||
import com.anytypeio.anytype.di.common.ComponentManager
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.ext.daggerViewModel
|
||||
import com.anytypeio.anytype.presentation.common.ScreenState
|
||||
import com.anytypeio.anytype.presentation.onboarding.OnboardingStartViewModel
|
||||
import com.anytypeio.anytype.presentation.onboarding.OnboardingStartViewModel.SideEffect
|
||||
import com.anytypeio.anytype.presentation.onboarding.OnboardingViewModel
|
||||
|
@ -127,6 +126,10 @@ class OnboardingFragment : Fragment() {
|
|||
private fun OnboardingScreen() {
|
||||
MaterialTheme {
|
||||
val navController = rememberAnimatedNavController()
|
||||
val defaultBackCallback : BackButtonCallback = { navController.popBackStack() }
|
||||
val signUpBackButtonCallback = remember {
|
||||
mutableStateOf(defaultBackCallback)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
@ -136,27 +139,31 @@ class OnboardingFragment : Fragment() {
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
BackgroundCircle()
|
||||
}
|
||||
Onboarding(currentPage, navController)
|
||||
Onboarding(
|
||||
currentPage = currentPage,
|
||||
navController = navController,
|
||||
backButtonCallback = signUpBackButtonCallback
|
||||
)
|
||||
PagerIndicator(
|
||||
pageCount = OnboardingPage.values().filter { it.visible }.size,
|
||||
page = currentPage,
|
||||
onBackClick = onBoardingViewModel::onBackPressed
|
||||
)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
onBoardingViewModel.navigation.collect { navigation ->
|
||||
when (navigation) {
|
||||
OnboardingViewModel.Navigation.Back -> {
|
||||
navController.popBackStack()
|
||||
}
|
||||
onBackClick = {
|
||||
signUpBackButtonCallback.value?.invoke()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
onBoardingViewModel.toasts.collect {
|
||||
toast(it)
|
||||
}
|
||||
}
|
||||
DisposableEffect(
|
||||
viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.DESTROYED)
|
||||
) {
|
||||
onDispose {
|
||||
signUpBackButtonCallback.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +186,11 @@ class OnboardingFragment : Fragment() {
|
|||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun Onboarding(currentPage: MutableState<OnboardingPage>, navController: NavHostController) {
|
||||
private fun Onboarding(
|
||||
currentPage: MutableState<OnboardingPage>,
|
||||
backButtonCallback: MutableState<BackButtonCallback>,
|
||||
navController: NavHostController
|
||||
) {
|
||||
AnimatedNavHost(navController, startDestination = OnboardingNavigation.auth) {
|
||||
composable(
|
||||
route = OnboardingNavigation.auth,
|
||||
|
@ -227,19 +238,22 @@ class OnboardingFragment : Fragment() {
|
|||
}
|
||||
) {
|
||||
currentPage.value = OnboardingPage.VOID
|
||||
|
||||
val component = componentManager().onboardingNewVoidComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
val vm = daggerViewModel { component.get().getViewModel() }
|
||||
|
||||
VoidScreenWrapper(
|
||||
contentPaddingTop = ContentPaddingTop(),
|
||||
onNextClicked = vm::onNextClicked,
|
||||
screenState = vm.state.collectAsState().value
|
||||
)
|
||||
BackHandler {
|
||||
vm.onSystemBackPressed()
|
||||
}
|
||||
|
||||
backButtonCallback.value = vm::onBackButtonPressed
|
||||
BackHandler { vm.onSystemBackPressed() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
vm.navigation.collect { navigation ->
|
||||
when(navigation) {
|
||||
|
@ -252,13 +266,6 @@ class OnboardingFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
vm.state.collect { state ->
|
||||
onBoardingViewModel.onLoadingStateChanged(
|
||||
isLoading = state is ScreenState.Loading
|
||||
)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
vm.toasts.collect { toast(it) }
|
||||
}
|
||||
|
@ -287,6 +294,7 @@ class OnboardingFragment : Fragment() {
|
|||
}
|
||||
) {
|
||||
currentPage.value = OnboardingPage.MNEMONIC
|
||||
backButtonCallback.value = { navController.popBackStack() }
|
||||
Mnemonic(navController, ContentPaddingTop())
|
||||
}
|
||||
composable(
|
||||
|
@ -304,6 +312,7 @@ class OnboardingFragment : Fragment() {
|
|||
}
|
||||
) {
|
||||
currentPage.value = OnboardingPage.SOUL_CREATION
|
||||
backButtonCallback.value = { navController.popBackStack() }
|
||||
CreateSoul(
|
||||
navController = navController,
|
||||
contentPaddingTop = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
|
||||
|
@ -319,9 +328,10 @@ class OnboardingFragment : Fragment() {
|
|||
}
|
||||
) {
|
||||
currentPage.value = OnboardingPage.SOUL_CREATION_ANIM
|
||||
backButtonCallback.value = null
|
||||
CreateSoulAnimation(ContentPaddingTop())
|
||||
BackHandler {
|
||||
Timber.d("OnBackHandler")
|
||||
// Intercepting back-press event but doing nothing.
|
||||
}
|
||||
}
|
||||
composable(
|
||||
|
@ -649,4 +659,6 @@ class OnboardingFragment : Fragment() {
|
|||
}
|
||||
|
||||
private const val ANIMATION_LENGTH_SLIDE = 300
|
||||
private const val ANIMATION_LENGTH_FADE = 700
|
||||
private const val ANIMATION_LENGTH_FADE = 700
|
||||
|
||||
typealias BackButtonCallback = (() -> Unit)?
|
|
@ -25,12 +25,12 @@ import com.anytypeio.anytype.core_ui.views.ButtonSize
|
|||
import com.anytypeio.anytype.core_ui.views.HeadlineOnBoardingDescription
|
||||
import com.anytypeio.anytype.core_ui.views.OnBoardingButtonPrimary
|
||||
import com.anytypeio.anytype.core_ui.views.Title1
|
||||
import com.anytypeio.anytype.presentation.common.ScreenState
|
||||
import com.anytypeio.anytype.presentation.onboarding.signup.OnboardingVoidViewModel
|
||||
|
||||
|
||||
@Composable
|
||||
fun VoidScreenWrapper(
|
||||
screenState: ScreenState,
|
||||
screenState: OnboardingVoidViewModel.ScreenState,
|
||||
contentPaddingTop: Int,
|
||||
onNextClicked: () -> Unit
|
||||
) {
|
||||
|
@ -43,7 +43,7 @@ fun VoidScreenWrapper(
|
|||
|
||||
@Composable
|
||||
fun VoidScreen(
|
||||
screenState: ScreenState,
|
||||
screenState: OnboardingVoidViewModel.ScreenState,
|
||||
onNextClicked: () -> Unit,
|
||||
contentPaddingTop: Int
|
||||
) {
|
||||
|
@ -70,7 +70,7 @@ fun VoidScreen(
|
|||
onClick = { onNextClicked() },
|
||||
size = ButtonSize.Large
|
||||
)
|
||||
if (screenState is ScreenState.Loading) {
|
||||
if (screenState is OnboardingVoidViewModel.ScreenState.Loading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
|
|
|
@ -391,4 +391,7 @@ Do the computation of an expensive paragraph of text on a background thread:
|
|||
<string name="mail_more_space_body">Hi, Anytype team. I am reaching out to request an increase in my file storage capacity as I have run out of storage. My current limits is %1$s. My account id is %2$s. Cheers, %3$s</string>
|
||||
<string name="your_recovery_phrase_can_t_be_empty">Your recovery phrase can\'t be empty</string>
|
||||
<string name="go_to_the_app">Go to the app</string>
|
||||
|
||||
<string name="exiting_please_wait">Exiting... please wait</string>
|
||||
<string name="loading_please_wait">Loading... please wait</string>
|
||||
</resources>
|
||||
|
|
|
@ -3,25 +3,23 @@ package com.anytypeio.anytype.domain.auth.interactor
|
|||
import com.anytypeio.anytype.domain.auth.model.AuthStatus
|
||||
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Either
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use case for checking authorisation status.
|
||||
* User can be either authorized or unauthorized based on number of available accounts.
|
||||
* @param repository repository containing information about available accounts
|
||||
*/
|
||||
class CheckAuthorizationStatus(
|
||||
class CheckAuthorizationStatus @Inject constructor(
|
||||
private val repository: AuthRepository
|
||||
) : BaseUseCase<AuthStatus, Unit>() {
|
||||
|
||||
override suspend fun run(params: Unit) = try {
|
||||
override suspend fun run(params: Unit) = safe {
|
||||
repository.getAccounts().let { accounts ->
|
||||
if (accounts.isNotEmpty())
|
||||
Either.Right(AuthStatus.AUTHORIZED)
|
||||
AuthStatus.AUTHORIZED
|
||||
else
|
||||
Either.Right(AuthStatus.UNAUTHORIZED)
|
||||
AuthStatus.UNAUTHORIZED
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Either.Left(e)
|
||||
}
|
||||
}
|
|
@ -5,11 +5,12 @@ import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
|||
import com.anytypeio.anytype.domain.base.Interactor
|
||||
import com.anytypeio.anytype.domain.config.ConfigStorage
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Use case for logging out.
|
||||
*/
|
||||
class Logout(
|
||||
class Logout @Inject constructor(
|
||||
private val repo: AuthRepository,
|
||||
private val config: ConfigStorage,
|
||||
private val user: UserSettingsRepository,
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package com.anytypeio.anytype.presentation.common
|
||||
|
||||
sealed class ScreenState {
|
||||
object Idle: ScreenState()
|
||||
object Loading: ScreenState()
|
||||
object Success: ScreenState()
|
||||
data class Failure(val msg: String): ScreenState()
|
||||
}
|
|
@ -2,36 +2,14 @@ package com.anytypeio.anytype.presentation.onboarding
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class OnboardingViewModel(
|
||||
private val analytics: Analytics
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val isInProgress = MutableStateFlow(false)
|
||||
|
||||
val navigation = MutableSharedFlow<Navigation>()
|
||||
|
||||
fun onBackPressed() {
|
||||
if (!isInProgress.value) {
|
||||
viewModelScope.launch {
|
||||
navigation.emit(Navigation.Back)
|
||||
}
|
||||
} else {
|
||||
sendToast(LOADING_MSG)
|
||||
}
|
||||
}
|
||||
|
||||
fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
isInProgress.value = isLoading
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val analytics: Analytics
|
||||
) : ViewModelProvider.Factory {
|
||||
|
@ -43,12 +21,4 @@ class OnboardingViewModel(
|
|||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Navigation {
|
||||
object Back: Navigation()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOADING_MSG = "Loading... please wait."
|
||||
}
|
||||
}
|
|
@ -3,15 +3,18 @@ package com.anytypeio.anytype.presentation.onboarding.signup
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus
|
||||
import com.anytypeio.anytype.domain.auth.interactor.CreateAccount
|
||||
import com.anytypeio.anytype.domain.auth.interactor.Logout
|
||||
import com.anytypeio.anytype.domain.auth.interactor.SetupWallet
|
||||
import com.anytypeio.anytype.domain.auth.model.AuthStatus
|
||||
import com.anytypeio.anytype.domain.base.Interactor
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.device.PathProvider
|
||||
import com.anytypeio.anytype.domain.`object`.SetupMobileUseCaseSkip
|
||||
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager
|
||||
import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.common.ScreenState
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
@ -26,7 +29,9 @@ class OnboardingVoidViewModel @Inject constructor(
|
|||
private val pathProvider: PathProvider,
|
||||
private val spaceGradientProvider: SpaceGradientProvider,
|
||||
private val relationsSubscriptionManager: RelationsSubscriptionManager,
|
||||
private val objectTypesSubscriptionManager: ObjectTypesSubscriptionManager
|
||||
private val objectTypesSubscriptionManager: ObjectTypesSubscriptionManager,
|
||||
private val checkAuthorizationStatus: CheckAuthorizationStatus,
|
||||
private val logout: Logout
|
||||
): BaseViewModel() {
|
||||
|
||||
val state = MutableStateFlow<ScreenState>(ScreenState.Idle)
|
||||
|
@ -99,19 +104,62 @@ class OnboardingVoidViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun onBackButtonPressed() {
|
||||
Timber.d("onBackButtonPressed!")
|
||||
resolveBackNavigation()
|
||||
}
|
||||
|
||||
fun onSystemBackPressed() {
|
||||
resolveBackNavigation()
|
||||
}
|
||||
|
||||
private fun resolveBackNavigation() {
|
||||
if (state.value !is ScreenState.Loading) {
|
||||
viewModelScope.launch {
|
||||
navigation.emit(
|
||||
Navigation.GoBack
|
||||
)
|
||||
when(state.value) {
|
||||
is ScreenState.Loading -> {
|
||||
sendToast(LOADING_MSG)
|
||||
}
|
||||
is ScreenState.Exiting -> {
|
||||
sendToast(EXITING_MSG)
|
||||
}
|
||||
else -> {
|
||||
viewModelScope.launch {
|
||||
state.value = ScreenState.Exiting.Status
|
||||
Timber.d("Checking auth status")
|
||||
checkAuthorizationStatus(Unit).proceed(
|
||||
failure = { e ->
|
||||
navigation.emit(Navigation.GoBack).also {
|
||||
Timber.e(e, "Error while checking auth status")
|
||||
}
|
||||
},
|
||||
success = { status ->
|
||||
when(status) {
|
||||
AuthStatus.AUTHORIZED -> {
|
||||
Timber.d("Proceeding with logout")
|
||||
proceedWithLogout()
|
||||
}
|
||||
AuthStatus.UNAUTHORIZED -> {
|
||||
navigation.emit(Navigation.GoBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithLogout() {
|
||||
logout.invoke(Logout.Params(clearLocalRepositoryData = true)).collect { status ->
|
||||
when (status) {
|
||||
is Interactor.Status.Started -> {
|
||||
state.value = ScreenState.Exiting.Logout.also {
|
||||
sendToast(EXITING_MSG)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
navigation.emit(Navigation.GoBack)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendToast(LOADING_MSG)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +175,9 @@ class OnboardingVoidViewModel @Inject constructor(
|
|||
private val pathProvider: PathProvider,
|
||||
private val spaceGradientProvider: SpaceGradientProvider,
|
||||
private val relationsSubscriptionManager: RelationsSubscriptionManager,
|
||||
private val objectTypesSubscriptionManager: ObjectTypesSubscriptionManager
|
||||
private val objectTypesSubscriptionManager: ObjectTypesSubscriptionManager,
|
||||
private val checkAuthorizationStatus: CheckAuthorizationStatus,
|
||||
private val logout: Logout
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
|
@ -138,12 +188,25 @@ class OnboardingVoidViewModel @Inject constructor(
|
|||
pathProvider = pathProvider,
|
||||
spaceGradientProvider = spaceGradientProvider,
|
||||
relationsSubscriptionManager = relationsSubscriptionManager,
|
||||
objectTypesSubscriptionManager = objectTypesSubscriptionManager
|
||||
objectTypesSubscriptionManager = objectTypesSubscriptionManager,
|
||||
logout = logout,
|
||||
checkAuthorizationStatus = checkAuthorizationStatus
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOADING_MSG = "Loading, please wait."
|
||||
const val EXITING_MSG = "Clearing resources, please wait."
|
||||
}
|
||||
|
||||
sealed class ScreenState {
|
||||
object Idle: ScreenState()
|
||||
object Loading: ScreenState()
|
||||
object Success: ScreenState()
|
||||
sealed class Exiting : ScreenState() {
|
||||
object Status : Exiting()
|
||||
object Logout: Exiting()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue