mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1352 Onboarding | Enhancement | Fixed onboarding signup flow ui bugs
DROID-1352 Onboarding | Enhancement | Fixed onboarding signup flow ui bugs
This commit is contained in:
parent
d699c2e5e3
commit
5da051fb00
13 changed files with 389 additions and 197 deletions
|
@ -195,6 +195,7 @@ dependencies {
|
|||
implementation libs.composeAccompanistPager
|
||||
implementation libs.composeAccompanistThemeAdapter
|
||||
implementation libs.composeAccompanistPagerIndicators
|
||||
implementation libs.composeAccompanistNavAnimation
|
||||
implementation libs.preference
|
||||
implementation libs.activityCompose
|
||||
implementation libs.composeReorderable
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
package com.anytypeio.anytype.ui.onboarding
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Left
|
||||
import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Right
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
@ -23,11 +33,10 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.anytypeio.anytype.R
|
||||
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.di.common.componentManager
|
||||
|
@ -39,6 +48,10 @@ import com.anytypeio.anytype.ui.onboarding.screens.InviteCodeScreenWrapper
|
|||
import com.anytypeio.anytype.ui.onboarding.screens.MnemonicPhraseScreenWrapper
|
||||
import com.anytypeio.anytype.ui.onboarding.screens.RecoveryScreenWrapper
|
||||
import com.anytypeio.anytype.ui.onboarding.screens.VoidScreenWrapper
|
||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||
import com.google.accompanist.navigation.animation.composable
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
|
||||
|
||||
class OnboardingFragment : BaseComposeFragment() {
|
||||
|
||||
|
@ -60,7 +73,7 @@ class OnboardingFragment : BaseComposeFragment() {
|
|||
PagerIndicator(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, top = 16.dp),
|
||||
pageCount = Page.values().size,
|
||||
pageCount = Page.values().filter { it.visible }.size,
|
||||
page = currentPage
|
||||
)
|
||||
Onboarding(currentPage)
|
||||
|
@ -70,117 +83,240 @@ class OnboardingFragment : BaseComposeFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun Onboarding(currentPage: MutableState<Page>) {
|
||||
val navController = rememberNavController()
|
||||
NavHost(navController, startDestination = OnboardingNavigation.auth) {
|
||||
composable(OnboardingNavigation.auth) {
|
||||
currentPage.value = Page.AUTH
|
||||
val component = componentManager().onboardingAuthComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
AuthScreenWrapper(
|
||||
viewModel = daggerViewModel { component.get().getViewModel() },
|
||||
navigateToInviteCode = {
|
||||
navController.navigate(OnboardingNavigation.inviteCode)
|
||||
},
|
||||
navigateToLogin = {
|
||||
navController.navigate(OnboardingNavigation.recovery)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(OnboardingNavigation.inviteCode) {
|
||||
currentPage.value = Page.INVITE_CODE
|
||||
val component = componentManager().onboardingInviteCodeComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
|
||||
val viewModel: OnboardingInviteCodeViewModel = daggerViewModel {
|
||||
component.get().getViewModel()
|
||||
val navController = rememberAnimatedNavController()
|
||||
AnimatedNavHost(navController, startDestination = OnboardingNavigation.auth) {
|
||||
composable(
|
||||
route = OnboardingNavigation.auth,
|
||||
enterTransition = { null },
|
||||
exitTransition = {
|
||||
fadeOut(tween(150))
|
||||
}
|
||||
|
||||
InviteCodeScreenWrapper(viewModel = viewModel)
|
||||
val navigationCommands =
|
||||
viewModel.navigationFlow.collectAsState(
|
||||
initial = OnboardingInviteCodeViewModel.InviteCodeNavigation.Idle
|
||||
)
|
||||
LaunchedEffect(key1 = navigationCommands.value) {
|
||||
when (navigationCommands.value) {
|
||||
is OnboardingInviteCodeViewModel.InviteCodeNavigation.Void -> {
|
||||
navController.navigate(OnboardingNavigation.void)
|
||||
) {
|
||||
currentPage.value = Page.AUTH
|
||||
Auth(navController)
|
||||
}
|
||||
composable(
|
||||
route = OnboardingNavigation.inviteCode,
|
||||
enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
OnboardingNavigation.void -> {
|
||||
slideIntoContainer(Right, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
exitTransition = {
|
||||
when (targetState.destination.route) {
|
||||
OnboardingNavigation.auth -> {
|
||||
fadeOut(tween(ANIMATION_LENGTH_FADE))
|
||||
}
|
||||
else -> {
|
||||
|
||||
slideOutOfContainer(Left, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
currentPage.value = Page.INVITE_CODE
|
||||
InviteCode(navController)
|
||||
}
|
||||
composable(OnboardingNavigation.recovery) {
|
||||
composable(
|
||||
route = OnboardingNavigation.recovery,
|
||||
enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
OnboardingNavigation.inviteCode -> {
|
||||
slideIntoContainer(Left, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
else -> {
|
||||
slideIntoContainer(Right, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
}
|
||||
},
|
||||
exitTransition = {
|
||||
slideOutOfContainer(Left, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
) {
|
||||
RecoveryScreenWrapper()
|
||||
}
|
||||
composable(OnboardingNavigation.void) {
|
||||
composable(
|
||||
route = OnboardingNavigation.void,
|
||||
enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
OnboardingNavigation.inviteCode -> {
|
||||
slideIntoContainer(Left, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
else -> {
|
||||
slideIntoContainer(Right, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
}
|
||||
},
|
||||
exitTransition = {
|
||||
when (targetState.destination.route) {
|
||||
OnboardingNavigation.mnemonic -> {
|
||||
slideOutOfContainer(Left, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
else -> {
|
||||
slideOutOfContainer(Right, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
currentPage.value = Page.VOID
|
||||
VoidScreenWrapper {
|
||||
navController.navigate(OnboardingNavigation.mnemonic)
|
||||
}
|
||||
}
|
||||
composable(OnboardingNavigation.mnemonic) {
|
||||
currentPage.value = Page.MNEMONIC
|
||||
val component = componentManager().onboardingMnemonicComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
MnemonicPhraseScreenWrapper(
|
||||
viewModel = daggerViewModel { component.get().getViewModel() },
|
||||
openSoulCreation = {
|
||||
navController.navigate(OnboardingNavigation.createSoul)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(OnboardingNavigation.createSoul) {
|
||||
currentPage.value = Page.SOUL_CREATION
|
||||
val component = componentManager().onboardingSoulCreationComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
val viewModel = daggerViewModel { component.get().getViewModel() }
|
||||
CreateSoulWrapper(viewModel)
|
||||
val navigationCommands =
|
||||
viewModel.navigationFlow.collectAsState(
|
||||
initial = OnboardingSoulCreationViewModel.Navigation.Idle
|
||||
)
|
||||
LaunchedEffect(key1 = navigationCommands.value) {
|
||||
when (navigationCommands.value) {
|
||||
is OnboardingSoulCreationViewModel.Navigation.OpenSoulCreationAnim -> {
|
||||
navController.navigate(
|
||||
route = OnboardingNavigation.createSoulAnim
|
||||
)
|
||||
composable(
|
||||
route = OnboardingNavigation.mnemonic,
|
||||
enterTransition = {
|
||||
when (initialState.destination.route) {
|
||||
OnboardingNavigation.void -> {
|
||||
slideIntoContainer(Left, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
else -> {
|
||||
|
||||
slideIntoContainer(Right, tween(ANIMATION_LENGTH_SLIDE))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
exitTransition = { fadeOut(tween(ANIMATION_LENGTH_SLIDE)) }
|
||||
) {
|
||||
currentPage.value = Page.MNEMONIC
|
||||
Mnemonic(navController)
|
||||
}
|
||||
composable(OnboardingNavigation.createSoulAnim) {
|
||||
val component = componentManager().onboardingSoulCreationAnimComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
|
||||
CreateSoulAnimWrapper(
|
||||
viewModel = daggerViewModel { component.get().getViewModel() }
|
||||
) {
|
||||
findNavController().navigate(
|
||||
R.id.homeScreen
|
||||
)
|
||||
composable(
|
||||
route = OnboardingNavigation.createSoul,
|
||||
enterTransition = { slideIntoContainer(Left, tween(ANIMATION_LENGTH_SLIDE)) },
|
||||
exitTransition = { fadeOut(tween(ANIMATION_LENGTH_FADE)) }
|
||||
) {
|
||||
currentPage.value = Page.SOUL_CREATION
|
||||
CreateSoul(navController)
|
||||
}
|
||||
composable(
|
||||
route = OnboardingNavigation.createSoulAnim,
|
||||
enterTransition = { fadeIn(tween(ANIMATION_LENGTH_FADE)) }
|
||||
) {
|
||||
currentPage.value = Page.SOUL_CREATION_ANIM
|
||||
CreateSoulAnimation()
|
||||
BackHandler {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CreateSoulAnimation() {
|
||||
val component = componentManager().onboardingSoulCreationAnimComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
|
||||
CreateSoulAnimWrapper(
|
||||
viewModel = daggerViewModel { component.get().getViewModel() }
|
||||
) {
|
||||
findNavController().navigate(R.id.action_openHome)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CreateSoul(navController: NavHostController) {
|
||||
val component = componentManager().onboardingSoulCreationComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
val viewModel = daggerViewModel { component.get().getViewModel() }
|
||||
CreateSoulWrapper(viewModel)
|
||||
val navigationCommands =
|
||||
viewModel.navigationFlow.collectAsState(
|
||||
initial = OnboardingSoulCreationViewModel.Navigation.Idle
|
||||
)
|
||||
LaunchedEffect(key1 = navigationCommands.value) {
|
||||
when (navigationCommands.value) {
|
||||
is OnboardingSoulCreationViewModel.Navigation.OpenSoulCreationAnim -> {
|
||||
navController.navigate(
|
||||
route = OnboardingNavigation.createSoulAnim
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Mnemonic(navController: NavHostController) {
|
||||
val component = componentManager().onboardingMnemonicComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
MnemonicPhraseScreenWrapper(
|
||||
viewModel = daggerViewModel { component.get().getViewModel() },
|
||||
openSoulCreation = {
|
||||
navController.navigate(OnboardingNavigation.createSoul)
|
||||
},
|
||||
copyMnemonicToClipboard = ::copyMnemonicToClipboard
|
||||
)
|
||||
}
|
||||
|
||||
private fun copyMnemonicToClipboard(mnemonicPhrase: String) {
|
||||
try {
|
||||
val clipboard =
|
||||
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip =
|
||||
ClipData.newPlainText("Mnemonic phrase", mnemonicPhrase)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
toast("Mnemonic phrase copied")
|
||||
} catch (e: Exception) {
|
||||
toast("Could not copy your mnemonic phrase. Please try again later, or copy it manually.")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InviteCode(navController: NavHostController) {
|
||||
val component = componentManager().onboardingInviteCodeComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
|
||||
val viewModel: OnboardingInviteCodeViewModel = daggerViewModel {
|
||||
component.get().getViewModel()
|
||||
}
|
||||
|
||||
InviteCodeScreenWrapper(viewModel = viewModel)
|
||||
val navigationCommands =
|
||||
viewModel.navigationFlow.collectAsState(
|
||||
initial = OnboardingInviteCodeViewModel.InviteCodeNavigation.Idle
|
||||
)
|
||||
LaunchedEffect(key1 = navigationCommands.value) {
|
||||
when (navigationCommands.value) {
|
||||
is OnboardingInviteCodeViewModel.InviteCodeNavigation.Void -> {
|
||||
navController.navigate(OnboardingNavigation.void)
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Auth(navController: NavHostController) {
|
||||
val component = componentManager().onboardingAuthComponent.ReleaseOn(
|
||||
viewLifecycleOwner = viewLifecycleOwner,
|
||||
state = Lifecycle.State.DESTROYED
|
||||
)
|
||||
AuthScreenWrapper(
|
||||
viewModel = daggerViewModel { component.get().getViewModel() },
|
||||
navigateToInviteCode = {
|
||||
navController.navigate(OnboardingNavigation.inviteCode)
|
||||
},
|
||||
navigateToLogin = {
|
||||
navController.navigate(OnboardingNavigation.recovery)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun injectDependencies() {}
|
||||
|
||||
override fun releaseDependencies() {}
|
||||
|
@ -206,16 +342,5 @@ fun <T> ComponentManager.Component<T>.ReleaseOn(
|
|||
return that
|
||||
}
|
||||
|
||||
fun Modifier.conditional(
|
||||
condition: Boolean,
|
||||
ifTrue: Modifier.() -> Modifier,
|
||||
ifFalse: (Modifier.() -> Modifier)? = null
|
||||
): Modifier {
|
||||
return if (condition) {
|
||||
then(ifTrue(Modifier))
|
||||
} else if (ifFalse != null) {
|
||||
then(ifFalse(Modifier))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
private const val ANIMATION_LENGTH_SLIDE = 700
|
||||
private const val ANIMATION_LENGTH_FADE = 700
|
|
@ -13,7 +13,7 @@ class OnboardingMnemonicViewModel @Inject constructor(
|
|||
private val getMnemonic: GetMnemonic
|
||||
) : ViewModel() {
|
||||
|
||||
val state = MutableStateFlow<State>(State.Idle)
|
||||
val state = MutableStateFlow<State>(State.Idle(""))
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
|
@ -22,9 +22,9 @@ class OnboardingMnemonicViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun openMnemonic() {
|
||||
state.value = (state.value as? State.Mnemonic)?.copy(
|
||||
visible = true
|
||||
) ?: state.value
|
||||
if (state.value is State.Mnemonic) {
|
||||
state.value = State.MnemonicOpened((state.value as State.Mnemonic).mnemonicPhrase)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithMnemonicPhrase() {
|
||||
|
@ -38,9 +38,13 @@ class OnboardingMnemonicViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
|
||||
sealed class State {
|
||||
object Idle : State()
|
||||
data class Mnemonic(val mnemonicPhrase: String, val visible: Boolean = false) : State()
|
||||
sealed interface State {
|
||||
|
||||
val mnemonicPhrase: String
|
||||
|
||||
class Idle(override val mnemonicPhrase: String): State
|
||||
class Mnemonic(override val mnemonicPhrase: String): State
|
||||
class MnemonicOpened(override val mnemonicPhrase: String): State
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
|
|
|
@ -12,5 +12,6 @@ enum class Page(val num: Int, val visible: Boolean) {
|
|||
INVITE_CODE(1, true),
|
||||
VOID(2, true),
|
||||
MNEMONIC(3, true),
|
||||
SOUL_CREATION(4, true)
|
||||
SOUL_CREATION(4, true),
|
||||
SOUL_CREATION_ANIM(5, false)
|
||||
}
|
|
@ -19,25 +19,36 @@ class OnboardingSoulCreationViewModel @Inject constructor(
|
|||
) : ViewModel() {
|
||||
|
||||
private val accountId = configStorage.get().profile
|
||||
private val workspaceId = configStorage.get().workspace
|
||||
|
||||
private val _navigationFlow = MutableSharedFlow<Navigation>()
|
||||
val navigationFlow: SharedFlow<Navigation> = _navigationFlow
|
||||
|
||||
fun setAccountName(name: String) {
|
||||
fun setAccountAndSpaceName(name: String) {
|
||||
viewModelScope.launch {
|
||||
setObjectDetails.execute(
|
||||
SetObjectDetails.Params(
|
||||
ctx = accountId,
|
||||
details = mapOf(
|
||||
Relations.NAME to name
|
||||
)
|
||||
)
|
||||
SetObjectDetails.Params(ctx = accountId, details = mapOf(Relations.NAME to name))
|
||||
).fold(
|
||||
onFailure = {
|
||||
Timber.e(it, "Error while updating object details")
|
||||
},
|
||||
onSuccess = {
|
||||
_navigationFlow.emit(Navigation.OpenSoulCreationAnim(name))
|
||||
setWorkspaceName(name)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setWorkspaceName(name: String) {
|
||||
viewModelScope.launch {
|
||||
setObjectDetails.execute(
|
||||
SetObjectDetails.Params(ctx = workspaceId, details = mapOf(Relations.NAME to name))
|
||||
).fold(
|
||||
onFailure = {
|
||||
Timber.e(it, "Error while updating object details")
|
||||
},
|
||||
onSuccess = {
|
||||
_navigationFlow.emit(Navigation.OpenSoulCreationAnim(name))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.anytypeio.anytype.ui.onboarding.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -14,7 +13,6 @@ 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.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
|
@ -60,7 +58,6 @@ fun AuthScreen(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black)
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) {
|
||||
Title(modifier = Modifier)
|
||||
|
|
|
@ -16,9 +16,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -36,7 +33,7 @@ import com.anytypeio.anytype.ui.onboarding.OnboardingSoulCreationViewModel
|
|||
@Composable
|
||||
fun CreateSoulWrapper(viewModel: OnboardingSoulCreationViewModel) {
|
||||
CreateSoulScreen {
|
||||
viewModel.setAccountName(it)
|
||||
viewModel.setAccountAndSpaceName(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,14 +41,13 @@ fun CreateSoulWrapper(viewModel: OnboardingSoulCreationViewModel) {
|
|||
private fun CreateSoulScreen(
|
||||
onCreateSoulClicked: (String) -> Unit
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
val text = remember { mutableStateOf("") }
|
||||
CreateSoulTitle(modifier = Modifier.padding(bottom = 16.dp))
|
||||
CreateSoulInput(text, focusRequester)
|
||||
CreateSoulInput(text)
|
||||
Spacer(modifier = Modifier.height(9.dp))
|
||||
CreateSoulDescription()
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
|
@ -79,14 +75,10 @@ fun CreateSoulTitle(modifier: Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun CreateSoulInput(text: MutableState<String>, focusRequester: FocusRequester) {
|
||||
fun CreateSoulInput(text: MutableState<String>) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.onGloballyPositioned {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
.wrapContentHeight(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
|
|
@ -16,9 +16,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -34,9 +32,7 @@ import com.anytypeio.anytype.ui.onboarding.OnboardingInput
|
|||
import com.anytypeio.anytype.ui.onboarding.OnboardingInviteCodeViewModel
|
||||
|
||||
@Composable
|
||||
fun InviteCodeScreenWrapper(
|
||||
viewModel: OnboardingInviteCodeViewModel,
|
||||
) {
|
||||
fun InviteCodeScreenWrapper(viewModel: OnboardingInviteCodeViewModel) {
|
||||
val state = viewModel.state.collectAsStateWithLifecycle().value
|
||||
InviteCodeScreen(
|
||||
state = state,
|
||||
|
@ -51,8 +47,6 @@ fun InviteCodeScreen(
|
|||
state: OnboardingInviteCodeViewModel.InviteCodeViewState,
|
||||
onInviteCodeEntered: (String) -> Unit,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
when (state) {
|
||||
is OnboardingInviteCodeViewModel.InviteCodeViewState.WalletCreating -> {}
|
||||
else -> {
|
||||
|
@ -62,7 +56,7 @@ fun InviteCodeScreen(
|
|||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
InviteCodeTitle(modifier = Modifier.padding(bottom = 16.dp))
|
||||
InviteCodeInput(inviteCode, focusRequester)
|
||||
InviteCodeInput(inviteCode)
|
||||
Spacer(modifier = Modifier.height(9.dp))
|
||||
InviteCodeDescription()
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
|
@ -95,14 +89,10 @@ fun InviteCodeTitle(modifier: Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun InviteCodeInput(text: MutableState<String>, focusRequester: FocusRequester) {
|
||||
fun InviteCodeInput(text: MutableState<String>) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
.onGloballyPositioned {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
.wrapContentHeight(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -22,6 +23,7 @@ import com.anytypeio.anytype.R
|
|||
import com.anytypeio.anytype.core_ui.ColorBackgroundField
|
||||
import com.anytypeio.anytype.core_ui.OnBoardingTextPrimaryColor
|
||||
import com.anytypeio.anytype.core_ui.OnBoardingTextSecondaryColor
|
||||
import com.anytypeio.anytype.core_ui.extensions.conditional
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonSize
|
||||
import com.anytypeio.anytype.core_ui.views.HeadlineOnBoardingDescription
|
||||
import com.anytypeio.anytype.core_ui.views.OnBoardingButtonPrimary
|
||||
|
@ -29,18 +31,19 @@ import com.anytypeio.anytype.core_ui.views.OnBoardingButtonSecondary
|
|||
import com.anytypeio.anytype.core_ui.views.Title1
|
||||
import com.anytypeio.anytype.ui.onboarding.MnemonicPhraseWidget
|
||||
import com.anytypeio.anytype.ui.onboarding.OnboardingMnemonicViewModel
|
||||
import com.anytypeio.anytype.ui.onboarding.conditional
|
||||
|
||||
@Composable
|
||||
fun MnemonicPhraseScreenWrapper(
|
||||
viewModel: OnboardingMnemonicViewModel,
|
||||
openSoulCreation: () -> Unit
|
||||
openSoulCreation: () -> Unit,
|
||||
copyMnemonicToClipboard: (String) -> Unit
|
||||
) {
|
||||
val state = viewModel.state.collectAsStateWithLifecycle().value
|
||||
MnemonicPhraseScreen(
|
||||
state = state,
|
||||
reviewMnemonic = { viewModel.openMnemonic() },
|
||||
openSoulCreation = openSoulCreation
|
||||
openSoulCreation = openSoulCreation,
|
||||
copyMnemonicToClipboard = copyMnemonicToClipboard
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -48,7 +51,8 @@ fun MnemonicPhraseScreenWrapper(
|
|||
fun MnemonicPhraseScreen(
|
||||
state: OnboardingMnemonicViewModel.State,
|
||||
reviewMnemonic: () -> Unit,
|
||||
openSoulCreation: () -> Unit
|
||||
openSoulCreation: () -> Unit,
|
||||
copyMnemonicToClipboard: (String) -> Unit
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
|
@ -58,13 +62,14 @@ fun MnemonicPhraseScreen(
|
|||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
MnemonicTitle()
|
||||
MnemonicPhrase(state)
|
||||
MnemonicPhrase(state, copyMnemonicToClipboard)
|
||||
MnemonicDescription()
|
||||
}
|
||||
MnemonicButtons(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
openMnemonic = reviewMnemonic,
|
||||
openSoulCreation = openSoulCreation
|
||||
openSoulCreation = openSoulCreation,
|
||||
state = state
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -73,35 +78,52 @@ fun MnemonicPhraseScreen(
|
|||
fun MnemonicButtons(
|
||||
modifier: Modifier = Modifier,
|
||||
openMnemonic: () -> Unit,
|
||||
openSoulCreation: () -> Unit
|
||||
openSoulCreation: () -> Unit,
|
||||
state: OnboardingMnemonicViewModel.State
|
||||
) {
|
||||
Column(modifier.wrapContentHeight()) {
|
||||
OnBoardingButtonPrimary(
|
||||
text = stringResource(id = R.string.onboarding_mnemonic_show_key),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
onClick = {
|
||||
openMnemonic.invoke()
|
||||
}, size = ButtonSize.Large
|
||||
)
|
||||
OnBoardingButtonSecondary(
|
||||
text = stringResource(id = R.string.onboarding_mnemonic_check_later),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 14.dp,
|
||||
end = 16.dp,
|
||||
bottom = 56.dp
|
||||
),
|
||||
onClick = {
|
||||
openSoulCreation.invoke()
|
||||
}, size = ButtonSize.Large
|
||||
)
|
||||
when (state) {
|
||||
is OnboardingMnemonicViewModel.State.MnemonicOpened -> {
|
||||
OnBoardingButtonPrimary(
|
||||
text = stringResource(id = R.string.onboarding_mnemonic_key_saved),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 56.dp),
|
||||
onClick = {
|
||||
openSoulCreation.invoke()
|
||||
}, size = ButtonSize.Large
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
OnBoardingButtonPrimary(
|
||||
text = stringResource(id = R.string.onboarding_mnemonic_show_key),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
onClick = {
|
||||
openMnemonic.invoke()
|
||||
}, size = ButtonSize.Large
|
||||
)
|
||||
OnBoardingButtonSecondary(
|
||||
text = stringResource(id = R.string.onboarding_mnemonic_check_later),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 14.dp,
|
||||
end = 16.dp,
|
||||
bottom = 56.dp
|
||||
),
|
||||
onClick = {
|
||||
openSoulCreation.invoke()
|
||||
}, size = ButtonSize.Large
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun MnemonicTitle() {
|
||||
Box(
|
||||
|
@ -121,34 +143,54 @@ fun MnemonicTitle() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun MnemonicPhrase(state: OnboardingMnemonicViewModel.State) {
|
||||
fun MnemonicPhrase(
|
||||
state: OnboardingMnemonicViewModel.State,
|
||||
copyMnemonicToClipboard: (String) -> Unit
|
||||
) {
|
||||
when (state) {
|
||||
is OnboardingMnemonicViewModel.State.Idle -> {}
|
||||
is OnboardingMnemonicViewModel.State.Mnemonic -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp)
|
||||
.background(color = ColorBackgroundField, shape = RoundedCornerShape(24.dp))
|
||||
else -> {
|
||||
Column(
|
||||
Modifier
|
||||
.wrapContentHeight()
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
MnemonicPhraseWidget(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.conditional(
|
||||
!state.visible, ifTrue = {
|
||||
blur(15.dp)
|
||||
}
|
||||
)
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp
|
||||
),
|
||||
mnemonic = state.mnemonicPhrase
|
||||
)
|
||||
.padding(bottom = 16.dp)
|
||||
.background(color = ColorBackgroundField, shape = RoundedCornerShape(24.dp))
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
MnemonicPhraseWidget(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.conditional(
|
||||
condition = state is OnboardingMnemonicViewModel.State.MnemonicOpened,
|
||||
positive = { blur(15.dp) }
|
||||
)
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp
|
||||
),
|
||||
mnemonic = state.mnemonicPhrase
|
||||
)
|
||||
}
|
||||
if (state is OnboardingMnemonicViewModel.State.MnemonicOpened) {
|
||||
OnBoardingButtonSecondary(
|
||||
text = stringResource(id = R.string.onboarding_mnemonic_copy),
|
||||
modifier = Modifier
|
||||
.align(CenterHorizontally)
|
||||
.padding(bottom = 12.dp),
|
||||
onClick = {
|
||||
copyMnemonicToClipboard.invoke(state.mnemonicPhrase)
|
||||
}, size = ButtonSize.SmallSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -333,6 +333,15 @@
|
|||
<fragment
|
||||
android:id="@+id/authStartScreen"
|
||||
android:name="com.anytypeio.anytype.ui.onboarding.OnboardingFragment" />
|
||||
<action
|
||||
android:id="@+id/action_openHome"
|
||||
app:destination="@id/homeScreen"
|
||||
app:enterAnim="@anim/fade_in"
|
||||
app:exitAnim="@anim/fade_out"
|
||||
app:popEnterAnim="@anim/fade_in"
|
||||
app:popExitAnim="@anim/fade_out"
|
||||
app:popUpTo="@id/authStartScreen"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
</navigation>
|
||||
|
||||
|
|
|
@ -369,7 +369,9 @@ Do the computation of an expensive paragraph of text on a background thread:
|
|||
<string name="onboarding_mnemonic_title">Your Void is encrypted by the Key</string>
|
||||
<string name="onboarding_mnemonic_description">The Key is a set of 12 words. It is unique for the Void and can’t be changed. You only one person who knows the Key. If you lose it, no one, including us, can help restore your Void. Store it securely.</string>
|
||||
<string name="onboarding_mnemonic_show_key">Show me the Key</string>
|
||||
<string name="onboarding_mnemonic_key_saved">I saved my key</string>
|
||||
<string name="onboarding_mnemonic_check_later">Check later</string>
|
||||
<string name="onboarding_mnemonic_copy">Copy to clipboard</string>
|
||||
<string name="onboarding_soul_creation_title">Create your Soul</string>
|
||||
<string name="onboarding_soul_creation_description">Think of it like an identity, or a profile within\nAnytype.</string>
|
||||
<string name="onboarding_soul_creation_placeholder">Call it whatever you want</string>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.anytypeio.anytype.core_ui.extensions
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
fun Modifier.conditional(
|
||||
condition: Boolean,
|
||||
positive: Modifier.() -> Modifier,
|
||||
negative: (Modifier.() -> Modifier)? = null
|
||||
): Modifier {
|
||||
return if (condition) {
|
||||
then(positive(Modifier))
|
||||
} else if (negative != null) {
|
||||
then(negative(Modifier))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
|
@ -80,6 +80,7 @@ composeMaterial3 = { module = "androidx.compose.material3:material3", version.re
|
|||
composeAccompanistPager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanistVersion" }
|
||||
composeAccompanistPagerIndicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanistVersion" }
|
||||
composeAccompanistThemeAdapter = { module = "com.google.accompanist:accompanist-themeadapter-material", version.ref = "accompanistVersion" }
|
||||
composeAccompanistNavAnimation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanistVersion" }
|
||||
composeReorderable = { module = "org.burnoutcrew.composereorderable:reorderable", version.ref = "composeReorderableVersion" }
|
||||
kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.4.1" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompatVersion" }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue