1
0
Fork 0
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:
Allan Quatermain 2023-06-06 17:03:30 +03:00 committed by GitHub
parent d699c2e5e3
commit 5da051fb00
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 389 additions and 197 deletions

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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)
}

View file

@ -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))
}
)
}

View file

@ -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)

View file

@ -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
) {

View file

@ -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
) {

View file

@ -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
)
}
}
}
}
}

View file

@ -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>

View file

@ -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 cant 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>

View file

@ -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
}
}

View file

@ -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" }