Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/flipcash/app/build.gradle.kts
Original file line numberDiff line numberDiff line change
Expand Up@@ -223,7 +223,6 @@ dependencies {
implementation(project(":apps:flipcash:features:lab"))
implementation(project(":apps:flipcash:features:advanced"))
implementation(project(":apps:flipcash:features:device-logs"))
implementation(project(":apps:flipcash:features:appsettings"))
implementation(project(":apps:flipcash:features:appupdates"))
implementation(project(":apps:flipcash:features:deposit"))
implementation(project(":apps:flipcash:features:myaccount"))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,6 +49,9 @@ internal fun AppNavigationBar(
navigator: CodeNavigator,
modifier: Modifier = Modifier,
hazeState: HazeState? = null,
// A tab home taking over the whole screen without leaving its route (the You tab's tip card
// expanding in place). See TabBarVisibilityController.
forceHidden: Boolean = false,
) {
// Selection follows the base of the backstack (the tab "home"), so it stays correct while a
// sheet/modal sits on top and is right on launch. The top route only gates visibility.
Expand DownExpand Up@@ -79,7 +82,7 @@ internal fun AppNavigationBar(
contentAlignment = Alignment.BottomCenter,
) {
AnimatedVisibility(
visible = topTab != null && bottomBarMessages.isEmpty() && !billUp,
visible = topTab != null && bottomBarMessages.isEmpty() && !billUp && !forceHidden,
enter = slideInVertically { it } + fadeIn(),
exit = slideOutVertically { it } + fadeOut(),
) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,6 +28,8 @@ import com.flipcash.app.cardexpand.CardExpansionController
import com.flipcash.app.cardexpand.LocalCardExpansion
import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.navigation.DeeplinkAction
import com.flipcash.app.core.navigation.LocalTabBarVisibility
import com.flipcash.app.core.navigation.TabBarVisibilityController
import com.flipcash.app.core.navigation.asNavBarTab
import com.flipcash.app.core.ui.transitions.CardExpandTransition
import com.flipcash.app.internal.ui.AppNavigationBar
Expand DownExpand Up@@ -178,12 +180,19 @@ internal fun NewAppContent(
val hazeState = rememberHazeState()
val tabBarHeight = remember { mutableStateOf(0.dp) }

// Lets a tab home hide the bar without leaving its route — the You tab's tip card expands to
// full screen in place, so there's no route change for the visibility rule below to notice.
val tabBarVisibility = remember { TabBarVisibilityController() }

// Card-expand (iOS #587): the wallet requests an expansion (via LocalCardExpansion); the detail is
// drawn by CardExpandHost inside the wallet entry, driven by one progress scalar, so the deck stays
// composed and reorganises behind it. See CardExpansionController / CurrencyInfoExpansion.
// [cardExpansion] is owned by App so a `/token` deeplink — which is handled there, outside this
// shell — can open a token as its expanded card instead of pushing a screen.
CompositionLocalProvider(LocalCardExpansion provides cardExpansion) {
CompositionLocalProvider(
LocalCardExpansion provides cardExpansion,
LocalTabBarVisibility provides tabBarVisibility,
) {
Box(modifier = Modifier.fillMaxSize()) {
// Mark the nav content as the haze source so the frosted bar blurs whatever scrolls beneath it.
Box(modifier = Modifier.hazeSource(hazeState)) {
Expand DownExpand Up@@ -282,6 +291,7 @@ internal fun NewAppContent(
AppNavigationBar(
navigator = codeNavigator,
hazeState = hazeState,
forceHidden = tabBarVisibility.isHidden,
modifier = Modifier
.align(Alignment.BottomCenter)
.measured { if (it.height > tabBarHeight.value) tabBarHeight.value = it.height }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,6 @@ import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.scene.OverlayScene
import androidx.navigation3.scene.SinglePaneSceneStrategy
import com.flipcash.app.advanced.AdvancedFeaturesScreen
import com.flipcash.app.appsettings.AppSettingsScreen
import com.flipcash.app.devicelogs.DeviceLogsScreen
import com.flipcash.app.backupkey.BackupKeyScreen
import com.flipcash.app.balance.BalanceScreen
Expand DownExpand Up@@ -146,7 +145,6 @@ fun appEntryProvider(


// Menu
annotatedEntry<AppRoute.Menu.AppSettings> { AppSettingsScreen() }
annotatedEntry<AppRoute.Menu.Lab> { key -> LabsScreen(onboarding = key.onboarding) }
annotatedEntry<AppRoute.Menu.NavBarSettings> { NavBarSettingsScreen() }
annotatedEntry<AppRoute.Menu.UserProfile> { UserProfileScreen() }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -302,8 +302,6 @@ sealed interface AppRoute : NavKey, Parcelable {
@Serializable
data object Blocklist: Menu
@Serializable
data object AppSettings : Menu
@Serializable
data object AdvancedFeatures : Menu
@Serializable
data object DeviceLogs : Menu
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -91,11 +91,5 @@ sealed interface Scannable {
data class TipCard(
override val data: List<Byte>,
val user: UserProfile,
/**
* True when this is the viewer's *own* tip card, presented for display (e.g. the You tab's
* full-screen card) rather than scanned from someone else. Suppresses the Send-a-Tip modal
* and its add-money prompt — you can't tip yourself.
*/
val isSelf: Boolean = false,
) : Scannable
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
package com.flipcash.app.core.navigation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf

/**
* Lets a tab home take the whole screen without leaving its route.
*
* The v2 tab bar is root chrome hoisted above the nav content, so it normally hides only when the
* top route stops being a tab home. A screen that expands something to full screen *in place* — the
* You tab's tip card — never changes route, so it has to say so.
*
* Requests are counted rather than latched to a boolean so overlapping callers can't uncover the
* bar from under one another.
*/
@Stable
class TabBarVisibilityController {
private var requests by mutableStateOf(0)

val isHidden: Boolean get() = requests > 0

fun hide() {
requests++
}

fun release() {
requests = (requests - 1).coerceAtLeast(0)
}
}

val LocalTabBarVisibility = staticCompositionLocalOf { TabBarVisibilityController() }

/** Hides the tab bar for as long as [hidden] holds and this composable stays in the tree. */
@Composable
fun HideTabBar(hidden: Boolean) {
val controller = LocalTabBarVisibility.current
DisposableEffect(controller, hidden) {
if (hidden) controller.hide()
onDispose { if (hidden) controller.release() }
}
}
17 changes: 16 additions & 1 deletion apps/flipcash/core/src/main/res/values/strings.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -972,4 +972,19 @@

<string name="title_tipOnboarding">Send Your First Tip</string>

</resources>
<!-- You tab (tip card) -->
<string name="action_viewFullScreen">Full Screen</string>
<string name="action_closeFullScreen">Close</string>
<string name="action_download">Download</string>
<string name="title_clipboardLabelTipCardLink">Tip Card Link</string>
<string name="title_downloadTipCardAs">Download As</string>
<string name="label_exportPng">PNG</string>
<string name="subtitle_exportPng">Social, chats, overlays</string>
<string name="label_exportSvg">SVG</string>
<string name="subtitle_exportSvg">Stays sharp at any size</string>
<string name="title_shareTipCode">My Flipcash Code</string>
<string name="error_title_tipCardExportFailed">Couldn\'t Export</string>
<string name="error_description_tipCardExportFailed">We were unable to export your tip code. Please try again</string>
<string name="title_changeDisplayName">Change Display Name</string>

</resources>
1 change: 1 addition & 0 deletions apps/flipcash/features/advanced/build.gradle.kts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ android {

dependencies {
implementation(project(":apps:flipcash:features:device-logs"))
implementation(project(":apps:flipcash:shared:authentication"))
implementation(project(":apps:flipcash:shared:bill-customization"))
implementation(project(":apps:flipcash:shared:featureflags"))
implementation(project(":apps:flipcash:shared:menu"))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ import com.flipcash.app.advanced.internal.AdvancedFeaturesScreen
import com.flipcash.app.advanced.internal.AdvancedFeaturesScreenViewModel
import com.flipcash.app.bill.customization.Event
import com.flipcash.app.bill.customization.LocalBillPlaygroundController
import com.flipcash.app.core.AppRoute
import com.flipcash.core.R
import com.getcode.navigation.core.LocalCodeNavigator
import com.getcode.opencode.model.financial.Token
Expand DownExpand Up@@ -56,4 +57,38 @@ fun AdvancedFeaturesScreen() {
}
.launchIn(this)
}

LaunchedEffect(viewModel) {
viewModel.eventFlow
.filterIsInstance<AdvancedFeaturesScreenViewModel.Event.OnViewAccessKey>()
.onEach { navigator.push(AppRoute.Menu.BackupKey) }
.launchIn(this)
}

LaunchedEffect(viewModel) {
viewModel.eventFlow
.filterIsInstance<AdvancedFeaturesScreenViewModel.Event.OnSwitchAccountTo>()
.onEach { navigator.hide() }
.launchIn(this)
}

LaunchedEffect(viewModel) {
viewModel.eventFlow
.filterIsInstance<AdvancedFeaturesScreenViewModel.Event.OnLoggedOutCompletely>()
.onEach {
navigator.hide()
navigator.replaceAll(AppRoute.OnboardingFlow())
}
.launchIn(this)
}

LaunchedEffect(viewModel) {
viewModel.eventFlow
.filterIsInstance<AdvancedFeaturesScreenViewModel.Event.OnAccountDeleted>()
.onEach {
navigator.hide()
navigator.replaceAll(AppRoute.OnboardingFlow())
}
.launchIn(this)
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,11 +6,29 @@ import androidx.compose.material.icons.outlined.Description
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.flipcash.app.core.AppRoute
import com.flipcash.app.featureflags.FeatureFlag
import com.flipcash.app.menu.FullMenuItem
import com.flipcash.app.menu.StaffMenuItem
import com.flipcash.core.R
import com.getcode.util.resources.icons.Delete

/**
* Node 9279:121978. Access Key, Log Out and Delete Account moved here from My Account — they're
* recovery/destructive actions, not account details.
*/
internal data object AccessKey : FullMenuItem<AdvancedFeaturesScreenViewModel.Event>() {
override val icon: Painter
@Composable get() = painterResource(R.drawable.ic_hardware_security_key)
override val name: String
@Composable get() = stringResource(R.string.title_accessKey)
override val action: AdvancedFeaturesScreenViewModel.Event =
AdvancedFeaturesScreenViewModel.Event.OnAccessKeyClicked
}

internal data object BillCustomizer : FullMenuItem<AdvancedFeaturesScreenViewModel.Event>() {
override val icon: Painter
Expand All@@ -37,4 +55,37 @@ internal data object BetaFlags : FullMenuItem<AdvancedFeaturesScreenViewModel.Ev
@Composable get() = stringResource(R.string.title_betaFlags)
override val action: AdvancedFeaturesScreenViewModel.Event =
AdvancedFeaturesScreenViewModel.Event.OpenScreen(AppRoute.Menu.Lab())
}
}

internal data object LogOut : FullMenuItem<AdvancedFeaturesScreenViewModel.Event>() {
override val icon: Painter
@Composable get() = painterResource(R.drawable.ic_menu_logout)
override val name: String
@Composable get() = stringResource(R.string.action_logout)
override val action: AdvancedFeaturesScreenViewModel.Event =
AdvancedFeaturesScreenViewModel.Event.OnLogOutClicked
}

internal data object DeleteAccount : FullMenuItem<AdvancedFeaturesScreenViewModel.Event>() {
override val icon: Painter
@Composable get() = rememberVectorPainter(ImageVector.Delete)
override val name: String
@Composable get() = stringResource(R.string.action_deleteAccount)
override val action: AdvancedFeaturesScreenViewModel.Event =
AdvancedFeaturesScreenViewModel.Event.OnDeleteAccountClicked
}

/**
* Staff/beta only, and dead without Google's Password Manager behind it —
* `PassphraseCredentialManager.selectCredential()` refuses outright when the flag is off. Sits here
* rather than on the You tab: it's a beta tool, next to the other one.
*/
internal data object SwitchAccount : StaffMenuItem<AdvancedFeaturesScreenViewModel.Event>() {
override val icon: Painter
@Composable get() = painterResource(R.drawable.ic_menu_switchaccounts)
override val name: String
@Composable get() = stringResource(R.string.title_switchAccounts)
override val action: AdvancedFeaturesScreenViewModel.Event =
AdvancedFeaturesScreenViewModel.Event.OnSwitchAccountsClicked
override val featureFlag: FeatureFlag<*> = FeatureFlag.CredentialManager
}
Loading
Loading