Skip to content
Merged
20 changes: 17 additions & 3 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,27 @@
"area: auth":
- changed-files:
- any-glob-to-any-file:
- "apps/flipcash/features/login/**"
- "apps/flipcash/shared/authentication/**"
- "apps/flipcash/shared/accesskey/**"

"area: session":
- changed-files:
- any-glob-to-any-file:
- "apps/flipcash/shared/session/**"
- "apps/flipcash/features/contact-verification/**"

"area: phone":
- changed-files:
- any-glob-to-any-file:
- "apps/flipcash/shared/phone/**"

"area: onboarding":
- changed-files:
- any-glob-to-any-file:
- "apps/flipcash/features/login/**"
- "apps/flipcash/shared/accesskey/**"
- "apps/flipcash/features/contact-verification/**"
- "apps/flipcash/shared/permissions/**"
- "apps/flipcash/core/**/onboarding/**"

"area: tokens":
- changed-files:
- any-glob-to-any-file:
Expand Down
1 change: 1 addition & 0 deletions apps/flipcash/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ dependencies {
implementation(project(":apps:flipcash:features:transactions"))
implementation(project(":apps:flipcash:features:bill-customization"))
implementation(project(":apps:flipcash:features:currency-creator"))
implementation(project(":apps:flipcash:features:direct-send"))
implementation(project(":apps:flipcash:features:discovery"))
implementation(project(":apps:flipcash:features:userflags"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,8 @@ internal fun App(
action.entropy,
onSwitchAccount = {
codeNavigator.replaceAll(
AppRoute.Onboarding.Login(
action.entropy,
AppRoute.OnboardingFlow(
seed = action.entropy,
fromDeeplink = true
)
)
Expand All @@ -287,13 +287,13 @@ internal fun App(
LaunchedEffect(userState.authState) {
if (userState.authState == AuthState.LoggedOut) {
val current = codeNavigator.currentRouteKey
if (current !is AppRoute.Loading && current !is AppRoute.Onboarding) {
if (current !is AppRoute.Loading && current !is AppRoute.OnboardingFlow) {
codeNavigator.pendingSheetDismiss = null
val switchEntropy =
viewModel.consumePendingSwitchEntropy()
codeNavigator.replaceAll(
AppRoute.Onboarding.Login(
switchEntropy
AppRoute.OnboardingFlow(
seed = switchEntropy
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ fun AppRestrictedScreen(restrictionType: RestrictionType) {
coroutineScope.launch {
homeViewModel.logout()
.onSuccess {
navigator.replaceAll(
AppRoute.Onboarding.Login()
)
navigator.replaceAll(AppRoute.OnboardingFlow())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,9 @@ import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEn
import com.flipcash.app.lab.LabsScreen
import com.flipcash.app.lab.NavBarSettingsScreen
import com.flipcash.app.lab.StandaloneLabsScreen
import com.flipcash.app.login.accesskey.AccessKeyScreen
import com.flipcash.app.login.accesskey.PhotoAccessKeyScreen
import com.flipcash.app.login.router.LoginRouter
import com.flipcash.app.login.seed.SeedInputScreen
import com.flipcash.app.login.OnboardingFlowScreen
import com.flipcash.app.menu.MenuScreen
import com.flipcash.app.myaccount.MyAccountScreen
import com.flipcash.app.permissions.ContactPermissionScreen
import com.flipcash.app.permissions.NotificationPermissionRationaleScreen
import com.flipcash.app.permissions.NotificationPermissionScreen
import com.flipcash.app.purchase.PurchaseAccountScreen
import com.flipcash.app.scanner.ScannerScreen
import com.flipcash.app.shareapp.ShareAppScreen
import com.flipcash.app.tokens.SwapFlowScreen
Expand Down Expand Up @@ -75,16 +68,10 @@ fun appEntryProvider(
// Loading / splash
annotatedEntry<AppRoute.Loading> { MainRoot(deepLink) }

// Onboarding
annotatedEntry<AppRoute.Onboarding.Login> { key -> LoginRouter(key.seed, key.fromDeeplink) }
annotatedEntry<AppRoute.Onboarding.SeedInput> { SeedInputScreen() }
annotatedEntry<AppRoute.Onboarding.AccessKey> { AccessKeyScreen() }
annotatedEntry<AppRoute.Onboarding.AccessKeySavedLocation> { PhotoAccessKeyScreen() }
annotatedEntry<AppRoute.Onboarding.Purchase> { key -> PurchaseAccountScreen(key.fromLogin) }
annotatedEntry<AppRoute.Onboarding.ContactPermission> { key -> ContactPermissionScreen(key.postCreate) }
annotatedEntry<AppRoute.Onboarding.NotificationPermission> { key -> NotificationPermissionScreen(key.postCreate) }
annotatedEntry<AppRoute.Onboarding.NotificationPermissionRationale> { key -> NotificationPermissionRationaleScreen(key.permanentlyDenied) }
annotatedEntry<AppRoute.Onboarding.CameraPermission> { }
// Onboarding flow
annotatedEntry<AppRoute.OnboardingFlow> { key ->
OnboardingFlowScreen(route = key, resultStateRegistry = resultStateRegistry)
}

// Main
annotatedEntry<AppRoute.Main.Sheet> { key ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,25 +179,13 @@ private fun buildNavGraphForLaunch(
): LaunchNavGraph? {
return when (state) {
is AuthState.Registered -> {
if (state.seenAccessKey) {
val routes = if (userFlags?.requiresIapForRegistration == true) {
listOf(
AppRoute.Onboarding.Login(),
AppRoute.Onboarding.AccessKey,
AppRoute.Onboarding.Purchase()
)
} else {
listOf(AppRoute.Main.Scanner)
}
LaunchNavGraph(routes)
} else {
LaunchNavGraph(
listOf(
AppRoute.Onboarding.Login(),
AppRoute.Onboarding.AccessKey
)
)
val resumePoint = when {
!state.seenAccessKey -> AppRoute.OnboardingFlow.ResumePoint.AccessKey
userFlags?.requiresIapForRegistration == true ->
AppRoute.OnboardingFlow.ResumePoint.AccessKeyThenPurchase
else -> AppRoute.OnboardingFlow.ResumePoint.PostAccessKey
}
LaunchNavGraph(listOf(AppRoute.OnboardingFlow(resumeAt = resumePoint)))
}

AuthState.LoggedInWithUser -> {
Expand All @@ -223,10 +211,10 @@ private fun buildNavGraphForLaunch(
if (link != null) {
when (val action = router.dispatch(link)) {
is DeeplinkAction.Navigate -> LaunchNavGraph(action.routes)
else -> LaunchNavGraph(listOf(AppRoute.Onboarding.Login()))
else -> LaunchNavGraph(listOf(AppRoute.OnboardingFlow()))
}
} else {
LaunchNavGraph(listOf(AppRoute.Onboarding.Login()))
LaunchNavGraph(listOf(AppRoute.OnboardingFlow()))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import com.flipcash.app.core.verification.VerificationResult
import com.flipcash.app.core.verification.VerificationStep
import com.flipcash.app.core.withdrawal.WithdrawalResult
import com.flipcash.app.core.withdrawal.WithdrawalStep
import com.flipcash.app.core.onboarding.OnboardingStep
import com.getcode.navigation.NonDismissableRoute
import com.getcode.navigation.NonDraggableRoute
import com.getcode.navigation.flow.FlowRoute
import com.getcode.navigation.flow.FlowRouteWithResult
import com.getcode.opencode.exchange.VerifiedFiat
import com.getcode.opencode.internal.solana.model.SwapId
Expand Down Expand Up @@ -62,6 +64,31 @@ sealed interface AppRoute : NavKey, Parcelable {
}


@Serializable
@Parcelize
data class OnboardingFlow(
val phase: Phase = Phase.Account,
val seed: String? = null,
val fromDeeplink: Boolean = false,
val resumeAt: ResumePoint = ResumePoint.Login,
val skipContacts: Boolean = false,
) : AppRoute, FlowRoute {
enum class Phase { Account, Permissions }
enum class ResumePoint { Login, AccessKey, AccessKeyThenPurchase, PostAccessKey }

override val initialStack: List<NavKey>
get() = when (phase) {
Phase.Account -> when (resumeAt) {
ResumePoint.Login -> listOf(OnboardingStep.Start(seed, fromDeeplink))
ResumePoint.AccessKey -> listOf(OnboardingStep.Start(), OnboardingStep.AccessKey)
ResumePoint.AccessKeyThenPurchase ->
listOf(OnboardingStep.Start(), OnboardingStep.AccessKey, OnboardingStep.Purchase)
ResumePoint.PostAccessKey -> emptyList()
}
Phase.Permissions -> listOf(OnboardingStep.ContactPermission, OnboardingStep.NotificationPermission)
}
}

@Serializable
@Parcelize
sealed interface Main : AppRoute {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.flipcash.app.core.onboarding

import android.os.Parcelable
import com.getcode.navigation.NonDismissableRoute
import com.getcode.navigation.flow.FlowStep
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

/**
* Steps inside the Onboarding flow. Owned by [com.flipcash.app.core.AppRoute.OnboardingFlow]
* and rendered inside a [com.getcode.navigation.flow.FlowHost].
*/
@Serializable
sealed interface OnboardingStep : FlowStep, Parcelable {
@Parcelize
@Serializable
data class Start(val seed: String? = null, val fromDeeplink: Boolean = false) : OnboardingStep

@Parcelize
@Serializable
data object SeedInput : OnboardingStep

@Parcelize
@Serializable
data object AccessKey : OnboardingStep

@Parcelize
@Serializable
data object AccessKeySavedLocation : OnboardingStep

@Parcelize
@Serializable
data object Purchase : OnboardingStep

@Parcelize
@Serializable
data object ContactPermission : OnboardingStep

@Parcelize
@Serializable
data object NotificationPermission : OnboardingStep

@Parcelize
@Serializable
data class NotificationPermissionRationale(
val permanentlyDenied: Boolean = false,
) : OnboardingStep, NonDismissableRoute
}

@Serializable
sealed interface OnboardingResult : Parcelable {
@Parcelize
@Serializable
data object ProceedToVerification : OnboardingResult

@Parcelize
@Serializable
data object LoggedIn : OnboardingResult

@Parcelize
@Serializable
data object Completed : OnboardingResult
}
8 changes: 6 additions & 2 deletions apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,16 @@
<string name="error_description_deeplinkOnRampInsufficientUsdc">Your %1$s wallet doesn\'t have enough USDC for this purchase. Please add more USDC and try again.</string>
<string name="error_description_deeplinkOnRampUnknown">Please try again</string>

<string name="title_verifyPhoneNumber">Verify Phone Number</string>
<string name="title_connectPhoneNumber">Connect Phone Number</string>
<string name="subtitle_enterPhoneNumberToContinue">Please enter your phone number to continue</string>
<string name="subtitle_enterPhoneVerificationCode">An SMS message was sent to your phone number with a verification code.\nPlease enter the verification code above</string>
<string name="subtitle_enterEmailToContinue">Please enter your email to continue</string>

<string name="title_verifyEmailAddress">Verify Your Email</string>
<string name="title_connectPhoneToSend">Connect Phone To Send</string>
<string name="subtitle_connectPhoneToSend">Connect your phone number to send cash.</string>
<string name="action_connectPhoneNumber">Connect Your Phone Number</string>

<string name="title_connectEmailAddress">Connect Your Email</string>
<string name="subtitle_resend">Resend</string>

<string name="title_enterTheCode">Enter The Code</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fun VerificationFlowScreen(
value = NavResultOrCanceled.ReturnValue(result),
)
if (route.target != null && result is VerificationResult.Success) {
outerNavigator.replace(route.target!!)
outerNavigator.replaceAll(route.target!!)
} else {
outerNavigator.pop()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fun EmailMagicLinkContent(
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarWithTitle(
title = stringResource(R.string.title_verifyEmailAddress),
title = stringResource(R.string.title_connectEmailAddress),
isInModal = true,
titleAlignment = Alignment.CenterHorizontally,
backButton = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fun EmailVerificationContent(
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarWithTitle(
title = stringResource(R.string.title_verifyEmailAddress),
title = stringResource(R.string.title_connectEmailAddress),
isInModal = true,
titleAlignment = Alignment.CenterHorizontally,
backButton = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@ 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.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fun PhoneVerificationContent(isInModal: Boolean = true) {
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarWithTitle(
title = stringResource(R.string.title_verifyPhoneNumber),
title = stringResource(R.string.title_connectPhoneNumber),
isInModal = isInModal,
titleAlignment = Alignment.CenterHorizontally,
backButton = true,
Expand Down
2 changes: 2 additions & 0 deletions apps/flipcash/features/login/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ dependencies {
implementation(project(":apps:flipcash:shared:analytics"))
implementation(project(":apps:flipcash:shared:authentication"))
implementation(project(":apps:flipcash:shared:featureflags"))
implementation(project(":apps:flipcash:shared:permissions"))
implementation(project(":apps:flipcash:shared:userflags"))
implementation(project(":apps:flipcash:features:purchase"))
implementation(project(":libs:datetime"))
implementation(project(":libs:messaging"))
implementation(project(":libs:permissions:bindings"))
Expand Down
Loading
Loading