diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt index 15575ac2e0..8411126173 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.feature.onboarding.signup import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.CrashReporter import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.domain.account.AwaitAccountStartManager import com.anytypeio.anytype.domain.auth.interactor.CreateAccount @@ -16,7 +17,9 @@ import com.anytypeio.anytype.domain.device.PathProvider import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.`object`.ImportGetStartedUseCase +import com.anytypeio.anytype.domain.payments.SetMembershipEmail import com.anytypeio.anytype.domain.platform.InitialParamsProvider +import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager import com.anytypeio.anytype.domain.spaces.SpaceDeletedStatusWatcher import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager @@ -94,6 +97,14 @@ object OnboardingSoulCreationModule { dispatchers = dispatchers ) + @JvmStatic + @Provides + @SoulCreationScreenScope + fun provideSetMembershipEmail( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): SetMembershipEmail = SetMembershipEmail(repo = repo, dispatchers = dispatchers) + @Module interface Declarations { @Binds @@ -119,6 +130,7 @@ interface OnboardingSoulCreationDependencies : ComponentDependencies { fun userPermissionProvider(): UserPermissionProvider fun awaitAccountStartManager(): AwaitAccountStartManager fun globalSubscriptionManager(): GlobalSubscriptionManager + fun stringResourceProvider(): StringResourceProvider } @Scope diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingFragment.kt index 8d3fdc9bc8..db158000b0 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingFragment.kt @@ -66,7 +66,6 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.fragment.findNavController import androidx.navigation.navArgument -import androidx.navigation.navOptions import com.anytypeio.anytype.BuildConfig.USE_EDGE_TO_EDGE import com.anytypeio.anytype.R import com.anytypeio.anytype.core_models.Id @@ -74,7 +73,6 @@ import com.anytypeio.anytype.core_models.NO_VALUE import com.anytypeio.anytype.core_ui.BuildConfig.LIBRARY_PACKAGE_NAME import com.anytypeio.anytype.core_ui.MNEMONIC_WORD_COUNT import com.anytypeio.anytype.core_ui.MnemonicPhrasePaletteColors -import com.anytypeio.anytype.core_ui.foundation.GenericAlert import com.anytypeio.anytype.core_ui.views.BaseAlertDialog import com.anytypeio.anytype.core_utils.ext.argOrNull import com.anytypeio.anytype.core_utils.ext.shareFirstFileFromPath @@ -94,6 +92,7 @@ import com.anytypeio.anytype.ui.home.HomeScreenFragment import com.anytypeio.anytype.ui.onboarding.screens.AuthScreenWrapper import com.anytypeio.anytype.ui.onboarding.screens.signin.RecoveryScreenWrapper import com.anytypeio.anytype.ui.onboarding.screens.signup.MnemonicPhraseScreenWrapper +import com.anytypeio.anytype.ui.onboarding.screens.signup.SetEmailWrapper import com.anytypeio.anytype.ui.onboarding.screens.signup.SetProfileNameWrapper import com.anytypeio.anytype.ui.vault.VaultFragment import com.google.android.exoplayer2.ExoPlayer @@ -377,6 +376,40 @@ class OnboardingFragment : Fragment() { ) BackHandler { onBackClicked() } } + composable( + route = "${OnboardingNavigation.setEmail}?$ONBOARDING_NAME_PARAM={$ONBOARDING_NAME_PARAM}", + arguments = listOf( + navArgument(ONBOARDING_NAME_PARAM) { + type = NavType.StringType + defaultValue = "" + nullable = false + } + ), + enterTransition = { + fadeIn(tween(ANIMATION_LENGTH_FADE)) + }, + exitTransition = { + fadeOut(tween(ANIMATION_LENGTH_FADE)) + } + ) { + val focus = LocalFocusManager.current + val onBackClicked : () -> Unit = { + val lastDestination = navController.currentBackStackEntry + if (lastDestination?.destination?.route?.startsWith(OnboardingNavigation.setEmail) == true) { + focus.clearFocus(true) + navController.popBackStack() + } else { + Timber.d("Skipping exit click...") + } + } + currentPage.value = OnboardingPage.SET_EMAIL + backButtonCallback.value = onBackClicked + AddEmail( + navController = navController, + onBackClicked = onBackClicked + ) + BackHandler { onBackClicked() } + } } } @@ -555,6 +588,16 @@ class OnboardingFragment : Fragment() { is OnboardingSetProfileNameViewModel.Navigation.GoBack -> { // } + + is OnboardingSetProfileNameViewModel.Navigation.NavigateToAddEmailScreen -> { + if (keyboardInsets.getBottom(density) > 0) { + focusManager.clearFocus(force = true) + delay(KEYBOARD_HIDE_DELAY) + } + navController.navigate( + route = "${OnboardingNavigation.setEmail}?$ONBOARDING_NAME_PARAM=${command.name}", + ) + } } } } @@ -731,6 +774,63 @@ class OnboardingFragment : Fragment() { } } + @Composable + private fun AddEmail( + navController: NavHostController, + onBackClicked: () -> Unit + ) { + val component = componentManager().onboardingSoulCreationComponent + val vm = daggerViewModel { component.get().getViewModel() } + + val focusManager = LocalFocusManager.current + val keyboardInsets = WindowInsets.ime + val density = LocalDensity.current + + val name = navController.currentBackStackEntry?.arguments?.getString(ONBOARDING_NAME_PARAM) ?: "" + + SetEmailWrapper( + viewModel = vm, + name = name, + onBackClicked = onBackClicked + ) + + LaunchedEffect(Unit) { + vm.navigation.collect { command -> + when (command) { + is OnboardingSetProfileNameViewModel.Navigation.NavigateToMnemonic -> { + if (keyboardInsets.getBottom(density) > 0) { + focusManager.clearFocus(force = true) + delay(KEYBOARD_HIDE_DELAY) + } + val space = command.space + val startingObject = command.startingObject + navController.navigate( + route = buildString { + append("${OnboardingNavigation.mnemonic}?$ONBOARDING_SPACE_PARAM=${space.id}") + startingObject?.let { append("&$ONBOARDING_STARTING_OBJECT_PARAM=${it}") } + } + ) + } + is OnboardingSetProfileNameViewModel.Navigation.GoBack -> { + // + } + + is OnboardingSetProfileNameViewModel.Navigation.NavigateToAddEmailScreen -> { + //do nothing + } + } + } + } + LaunchedEffect(Unit) { + vm.toasts.collect { + toast(it) + } + } + DisposableEffect(Unit) { + onDispose { component.release() } + } + } + private fun getVideoPlayer(context: Context, videoPath: Uri): Player { val player = ExoPlayer.Builder(context).build() val source = DefaultDataSource.Factory( @@ -772,6 +872,8 @@ class OnboardingFragment : Fragment() { private const val ONBOARDING_SPACE_PARAM = "space" private const val ONBOARDING_STARTING_OBJECT_PARAM = "startingObject" + + private const val ONBOARDING_NAME_PARAM = "name" } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingNavigation.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingNavigation.kt index 98d850a282..62679e1237 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingNavigation.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingNavigation.kt @@ -6,5 +6,5 @@ object OnboardingNavigation { const val void = "void" const val mnemonic = "mnemonic" const val setProfileName = "createSoul" - const val enterTheVoid = "enterTheVoid" + const val setEmail = "setEmail" } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingScreenContract.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingScreenContract.kt index c9f445a508..6acc0f34a4 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingScreenContract.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/OnboardingScreenContract.kt @@ -7,5 +7,6 @@ enum class OnboardingPage(val num: Int, val visible: Boolean) { SET_PROFILE_NAME(1, false), SOUL_CREATION_ANIM(4, false), RECOVERY(5, false), - ENTER_THE_VOID(6, false) + ENTER_THE_VOID(6, false), + SET_EMAIL(3, false) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingEmailScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingEmailScreen.kt new file mode 100644 index 0000000000..d034904c77 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingEmailScreen.kt @@ -0,0 +1,240 @@ +package com.anytypeio.anytype.ui.onboarding.screens.signup + +import android.util.Patterns +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +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.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_ui.common.DefaultPreviews +import com.anytypeio.anytype.core_ui.foundation.noRippleClickable +import com.anytypeio.anytype.core_ui.views.ButtonSize +import com.anytypeio.anytype.core_ui.views.Caption1Regular +import com.anytypeio.anytype.core_ui.views.HeadlineTitleSemibold +import com.anytypeio.anytype.core_ui.views.OnBoardingButtonPrimary +import com.anytypeio.anytype.core_ui.views.OnBoardingButtonSecondary +import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular +import com.anytypeio.anytype.core_ui.views.UXBody +import com.anytypeio.anytype.presentation.onboarding.signup.OnboardingSetProfileNameViewModel +import kotlin.also +import kotlin.text.isNotEmpty + +@Composable +fun SetEmailWrapper( + viewModel: OnboardingSetProfileNameViewModel, + name: String, + onBackClicked: () -> Unit, +) { + OnboardingEmailScreen( + onContinueClicked = { email -> + viewModel.onEmailContinueClicked( + name = name, + email = email + ) + }, + onSkipClicked = { + viewModel.onEmailSkippedClicked( + name = name, + ) + }, + isLoading = viewModel.state + .collectAsStateWithLifecycle() + .value is OnboardingSetProfileNameViewModel.ScreenState.Loading, + onBackClicked = onBackClicked + ) +} + +@Composable +fun OnboardingEmailScreen( + onContinueClicked: (String) -> Unit, + onSkipClicked: () -> Unit, + onBackClicked: () -> Unit, + isLoading: Boolean +) { + var innerValue by remember { mutableStateOf(TextFieldValue()) } + var isError by remember { mutableStateOf(false) } + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + + fun isValidEmail(email: String): Boolean { + return Patterns.EMAIL_ADDRESS.matcher(email).matches() + } + + fun validateAndSubmit() { + if (isValidEmail(innerValue.text)) { + isError = false + focusRequester.freeFocus() + keyboardController?.hide() + onContinueClicked(innerValue.text) + } else { + isError = true + } + } + + Box( + modifier = Modifier + .windowInsetsPadding(WindowInsets.navigationBars) + .imePadding() + //.background(color = colorResource(id = R.color.black)) + .fillMaxSize() + ) { + Column { + Spacer(modifier = Modifier.height(148.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = stringResource(R.string.onboarding_email_add_title), + color = colorResource(id = R.color.text_white), + style = HeadlineTitleSemibold, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), + text = stringResource(R.string.onboarding_email_add_description), + style = UXBody, + color = colorResource(id = R.color.text_white), + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(32.dp)) + OutlinedTextField( + value = innerValue, + onValueChange = { input -> + innerValue = input + isError = false + }, + shape = RoundedCornerShape(size = 16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .focusRequester(focusRequester), + placeholder = { + Text( + text = stringResource(id = R.string.onboarding_enter_email), + style = PreviewTitle1Regular, + color = colorResource(id = R.color.text_tertiary) + ) + }, + textStyle = PreviewTitle1Regular.copy( + color = Color(0xFFC2C2C2) + ), + singleLine = true, + isError = isError, + supportingText = { + if (isError) { + Text( + text = stringResource(id = R.string.onboarding_email_error), + color = colorResource(id = R.color.palette_system_red), + style = Caption1Regular + ) + } + }, + colors = TextFieldDefaults.colors( + disabledTextColor = colorResource(id = R.color.text_primary), + cursorColor = Color(0xFFC2C2C2), + focusedContainerColor = Color(0xFF212121), + unfocusedContainerColor = Color(0xFF212121), + errorContainerColor = Color(0xFF212121), + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent + ), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions { + validateAndSubmit() + } + ) + Spacer(modifier = Modifier.height(32.dp)) + } + Image( + modifier = Modifier + .align(Alignment.TopStart) + .padding(top = 16.dp, start = 9.dp) + .noRippleClickable { + onBackClicked() + }, + painter = painterResource(id = R.drawable.ic_back_onboarding_32), + contentDescription = stringResource(R.string.content_description_back_button_icon) + ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + start = 20.dp, + end = 20.dp, + bottom = 20.dp + ) + .align(Alignment.BottomCenter) + ) { + OnBoardingButtonPrimary( + text = stringResource(id = R.string.onboarding_button_continue), + onClick = { + validateAndSubmit() + }, + size = ButtonSize.Large, + modifier = Modifier.fillMaxWidth(), + isLoading = isLoading, + enabled = innerValue.text.isNotEmpty() + ) + Spacer(modifier = Modifier.height(8.dp)) + OnBoardingButtonSecondary( + text = stringResource(id = R.string.onboarding_button_skip), + onClick = { + onSkipClicked().also { + focusRequester.freeFocus() + } + }, + textColor = colorResource(id = R.color.text_white), + size = ButtonSize.Large, + modifier = Modifier.fillMaxWidth(), + ) + } + } +} + +@DefaultPreviews +@Composable +private fun SetProfileNameScreenPreview() { + OnboardingEmailScreen( + onContinueClicked = {}, + onBackClicked = {}, + onSkipClicked = {}, + isLoading = false + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingMnemonicPhraseScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingMnemonicPhraseScreen.kt index b6906bab5b..cb8dc378b7 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingMnemonicPhraseScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingMnemonicPhraseScreen.kt @@ -42,7 +42,6 @@ import com.anytypeio.anytype.core_ui.extensions.conditional import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.core_ui.views.BodyRegular import com.anytypeio.anytype.core_ui.views.ButtonSize -import com.anytypeio.anytype.core_ui.views.Caption1Regular import com.anytypeio.anytype.core_ui.views.HeadlineHeading import com.anytypeio.anytype.core_ui.views.HeadlineOnBoardingDescription import com.anytypeio.anytype.core_ui.views.OnBoardingButtonPrimary diff --git a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingSetProfileNameScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingSetProfileNameScreen.kt index 99d8da14c6..720951e8cb 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingSetProfileNameScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/onboarding/screens/signup/OnboardingSetProfileNameScreen.kt @@ -45,12 +45,13 @@ fun SetProfileNameWrapper( viewModel: OnboardingSetProfileNameViewModel, onBackClicked: () -> Unit, ) { - val defaultSpaceName = stringResource(id = R.string.onboarding_my_first_space) + val name = remember { mutableStateOf("") } + SetProfileNameScreen( - onNextClicked = { name -> + onNextClicked = { inputName -> + name.value = inputName viewModel.onNextClicked( - name = name, - spaceName = defaultSpaceName + name = inputName ) }, isLoading = viewModel.state diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt index 539da80ff3..389a9e712e 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt @@ -550,7 +550,8 @@ sealed class Command { data class Finalize(val name: String, val nameType: NameServiceNameType) : Membership() data class GetVerificationEmail( val email: String, - val subscribeToNewsletter: Boolean + val subscribeToNewsletter: Boolean, + val isFromOnboarding: Boolean ) : Membership() data class VerifyEmailCode(val code: String) : Membership() diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/OnBoardingColors.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/OnBoardingColors.kt index c5f84de3d7..48b6c97fac 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/OnBoardingColors.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/OnBoardingColors.kt @@ -11,12 +11,12 @@ val OnboardingSubtitleColor = Color(0xFF505050) val OnBoardingTextPrimaryColor = Color(0xFFF3F2EC) val OnBoardingTextSecondaryColor = Color(0x80F3F2EC) -val ColorButtonPrimaryActive = Color(0xFF252525) +val ColorButtonPrimaryActive = Color(0xFF3A3A3A) val ColorButtonPrimaryPressed = Color(0xFF464646) val ColorButtonPrimaryText = Color(0xFFF2F3EC) -val ColorButtonPrimaryInactive = Color(0xFF1F1E1D) -val ColorButtonPrimaryInactiveText = Color(0xFF64635B) +val ColorButtonPrimaryInactive = Color(0xFF1F1F1F) +val ColorButtonPrimaryInactiveText = Color(0xFF646464) val ColorButtonSecondaryActive = Color.Transparent val ColorButtonSecondaryPressed = Color(0xFF1F1E1C) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/views/TypographyCompose.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/views/TypographyCompose.kt index ed4907c9d0..4c3067a46c 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/views/TypographyCompose.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/views/TypographyCompose.kt @@ -328,4 +328,14 @@ val ModalTitle = TextStyle( val AvatarTitle = TextStyle( fontFamily = fontInterRegular, fontWeight = FontWeight.W600 -) \ No newline at end of file +) + +//Content/Headlines/Title Semibold +val HeadlineTitleSemibold = + TextStyle( + fontFamily = fontInterSemibold, + fontWeight = FontWeight.W600, + fontSize = 28.sp, + lineHeight = 32.sp, + letterSpacing = (-0.017).em + ) \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/payments/SetMembershipEmail.kt b/domain/src/main/java/com/anytypeio/anytype/domain/payments/SetMembershipEmail.kt index 63713dc1d8..222d9dc5c8 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/payments/SetMembershipEmail.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/payments/SetMembershipEmail.kt @@ -14,13 +14,15 @@ class SetMembershipEmail @Inject constructor( override suspend fun doWork(params: Params) { val command = Command.Membership.GetVerificationEmail( email = params.email, - subscribeToNewsletter = params.subscribeToNewsletter + subscribeToNewsletter = params.subscribeToNewsletter, + isFromOnboarding = params.isFromOnboarding ) repo.membershipGetVerificationEmail(command) } data class Params( val email: String, - val subscribeToNewsletter: Boolean + val subscribeToNewsletter: Boolean, + val isFromOnboarding: Boolean = false ) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/resources/StringResourceProvider.kt b/domain/src/main/java/com/anytypeio/anytype/domain/resources/StringResourceProvider.kt index 678d627414..de87cb36f7 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/resources/StringResourceProvider.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/resources/StringResourceProvider.kt @@ -9,4 +9,5 @@ interface StringResourceProvider { fun getUntitledObjectTitle(): String fun getSetOfObjectsTitle(): String fun getPropertiesFormatPrettyString(format: RelationFormat): String + fun getDefaultSpaceName(): String } \ No newline at end of file diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 6ece3a1a8d..ecd55eeb26 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1132,8 +1132,6 @@ Skip Not now Copy to clipboard - Set your name - Only seen by people you share something with. There is no central registry of these names. Anytype Identity Creating your Identity… Type your recovery phrase @@ -2044,4 +2042,13 @@ Please provide specific details of your needs here. Move to bin Error while creating account: space is missing + Add Your Name + Only seen by people you share something with. There is no central registry for these names. + Stay in the loop + We’d love to share tips, tricks and product updates with you. Your email is never linked to your identity. We won’t share your data. Ever. + Enter your email + Continue + Skip + Incorrect email + \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index 4208f65a6a..0c43bbce2e 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -2663,7 +2663,8 @@ class Middleware @Inject constructor( fun membershipGetVerificationEmail(command: Command.Membership.GetVerificationEmail) { val request = Rpc.Membership.GetVerificationEmail.Request( email = command.email, - subscribeToNewsletter = command.subscribeToNewsletter + subscribeToNewsletter = command.subscribeToNewsletter, + isOnboardingList = command.isFromOnboarding ) logRequestIfDebug(request) val (response, time) = measureTimedValue { service.membershipGetVerificationEmail(request) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt index 1a428b1c9c..e6da606b8f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt @@ -19,9 +19,12 @@ import com.anytypeio.anytype.domain.device.PathProvider import com.anytypeio.anytype.domain.misc.LocaleProvider import com.anytypeio.anytype.domain.`object`.ImportGetStartedUseCase import com.anytypeio.anytype.domain.`object`.SetObjectDetails +import com.anytypeio.anytype.domain.payments.SetMembershipEmail +import com.anytypeio.anytype.domain.resources.StringResourceProvider import com.anytypeio.anytype.domain.spaces.SetSpaceDetails import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager import com.anytypeio.anytype.domain.workspace.SpaceManager +import com.anytypeio.anytype.presentation.BuildConfig import com.anytypeio.anytype.presentation.common.BaseViewModel import com.anytypeio.anytype.presentation.extension.proceedWithAccountEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsOnboardingScreenEvent @@ -46,7 +49,9 @@ class OnboardingSetProfileNameViewModel @Inject constructor( private val crashReporter: CrashReporter, private val localeProvider: LocaleProvider, private val globalSubscriptionManager: GlobalSubscriptionManager, - private val spaceManager: SpaceManager + private val spaceManager: SpaceManager, + private val stringProvider: StringResourceProvider, + private val setMembershipEmail: SetMembershipEmail, ) : BaseViewModel() { init { @@ -62,22 +67,23 @@ class OnboardingSetProfileNameViewModel @Inject constructor( val navigation = MutableSharedFlow() fun onNextClicked( - name: String, - spaceName: String + name: String ) { if (state.value !is ScreenState.Loading) { - proceedWithCreatingWallet( - name = name, - spaceName = spaceName - ) + viewModelScope.launch { + navigation.emit( + Navigation.NavigateToAddEmailScreen( + name = name + ) + ) + } } else { sendToast(LOADING_MSG) } } private fun proceedWithCreatingWallet( - name: String, - spaceName: String + name: String ) { state.value = ScreenState.Loading setupWallet.invoke( @@ -92,8 +98,7 @@ class OnboardingSetProfileNameViewModel @Inject constructor( }, fnR = { proceedWithCreatingAccount( - name = name, - spaceName = spaceName + name = name ) } ) @@ -101,9 +106,9 @@ class OnboardingSetProfileNameViewModel @Inject constructor( } private fun proceedWithCreatingAccount( - name: String, - spaceName: String + name: String ) { + val spaceName = stringProvider.getDefaultSpaceName() val startTime = System.currentTimeMillis() val params = CreateAccount.Params( name = name, @@ -248,6 +253,53 @@ class OnboardingSetProfileNameViewModel @Inject constructor( } } + //region Email screen + fun onEmailContinueClicked( + email: String, + name: String, + ) { + proceedWithSettingEmail(email = email) + if (state.value !is ScreenState.Loading) { + proceedWithCreatingWallet( + name = name + ) + } else { + sendToast(LOADING_MSG) + } + } + + fun onEmailSkippedClicked( + name: String + ) { + if (state.value !is ScreenState.Loading) { + proceedWithCreatingWallet( + name = name + ) + } else { + sendToast(LOADING_MSG) + } + } + + private fun proceedWithSettingEmail(email: String) { + val params = SetMembershipEmail.Params( + email = email, + subscribeToNewsletter = false, + isFromOnboarding = true + ) + viewModelScope.launch { + setMembershipEmail.async(params).fold( + onSuccess = { Timber.d("Email set") }, + onFailure = { error -> + if (BuildConfig.DEBUG) { + sendToast("Error setting email") + } + Timber.e("Error setting email: $error") + } + ) + } + } + //endregion + class Factory @Inject constructor( private val setObjectDetails: SetObjectDetails, private val setSpaceDetails: SetSpaceDetails, @@ -261,7 +313,9 @@ class OnboardingSetProfileNameViewModel @Inject constructor( private val crashReporter: CrashReporter, private val localeProvider: LocaleProvider, private val globalSubscriptionManager: GlobalSubscriptionManager, - private val spaceManager: SpaceManager + private val spaceManager: SpaceManager, + private val stringProvider: StringResourceProvider, + private val setMembershipEmail: SetMembershipEmail ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -278,7 +332,9 @@ class OnboardingSetProfileNameViewModel @Inject constructor( crashReporter = crashReporter, localeProvider = localeProvider, globalSubscriptionManager = globalSubscriptionManager, - spaceManager = spaceManager + spaceManager = spaceManager, + stringProvider = stringProvider, + setMembershipEmail = setMembershipEmail ) as T } } @@ -292,6 +348,9 @@ class OnboardingSetProfileNameViewModel @Inject constructor( sealed class Navigation { data class NavigateToMnemonic(val space: SpaceId, val startingObject: Id?): Navigation() + data class NavigateToAddEmailScreen( + val name: String + ) : Navigation() data object GoBack: Navigation() } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/StringResourceProviderImpl.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/StringResourceProviderImpl.kt index fe9794d551..da1076bc65 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/StringResourceProviderImpl.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/StringResourceProviderImpl.kt @@ -51,4 +51,8 @@ class StringResourceProviderImpl @Inject constructor(private val context: Contex RelationFormat.UNDEFINED -> context.getString(R.string.undefined) } } + + override fun getDefaultSpaceName(): String { + return context.getString(R.string.onboarding_my_first_space) + } } \ No newline at end of file