Skip to content
Merged
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
13 changes: 9 additions & 4 deletions Flipcash/Core/Navigation/AppRouter+Destination.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ extension AppRouter {

// Settings flow
case settingsMyAccount
/// The display name on its own, edited from My Account. The full
/// profile-setup flow starts on the same screen but carries on to the
/// tip card; this one returns to the settings list.
case changeDisplayName
case settingsAdvancedFeatures
case settingsAdvancedBetaFeatures
case settingsAppSettings
Expand Down Expand Up @@ -85,8 +89,8 @@ extension AppRouter {
.transactionHistory, .activity, .give, .buyCurrency, .convertCurrency,
.withdrawCurrency, .usdcDepositEducation, .usdcDepositAddress:
return .balance
case .settingsMyAccount, .settingsAdvancedFeatures, .settingsAdvancedBetaFeatures,
.settingsAppSettings, .settingsAccountSelection,
case .settingsMyAccount, .changeDisplayName, .settingsAdvancedFeatures,
.settingsAdvancedBetaFeatures, .settingsAppSettings, .settingsAccountSelection,
.settingsApplicationLogs, .blockedUsers, .accessKey, .withdraw:
return .settings
case .profileName, .profilePhoto, .tipcard,
Expand Down Expand Up @@ -116,6 +120,7 @@ extension AppRouter {
case .usdcDepositEducation: "usdcDepositEducation"
case .usdcDepositAddress: "usdcDepositAddress"
case .settingsMyAccount: "settingsMyAccount"
case .changeDisplayName: "changeDisplayName"
case .settingsAdvancedFeatures: "settingsAdvancedFeatures"
case .settingsAdvancedBetaFeatures: "settingsAdvancedBetaFeatures"
case .settingsAppSettings: "settingsAppSettings"
Expand Down Expand Up @@ -155,8 +160,8 @@ extension AppRouter {
case .activity,
.discoverCurrencies, .currencyCreationSummary, .currencyCreationWizard,
.usdcDepositEducation, .usdcDepositAddress,
.settingsMyAccount, .settingsAdvancedFeatures, .settingsAdvancedBetaFeatures,
.settingsAppSettings, .settingsAccountSelection,
.settingsMyAccount, .changeDisplayName, .settingsAdvancedFeatures,
.settingsAdvancedBetaFeatures, .settingsAppSettings, .settingsAccountSelection,
.settingsApplicationLogs, .blockedUsers, .accessKey, .withdraw,
.profileName, .profilePhoto, .tipcard:
return nil
Expand Down
3 changes: 3 additions & 0 deletions Flipcash/Core/Navigation/AppRouter+DestinationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ struct DestinationView: View {
case .settingsMyAccount:
SettingsMyAccountScreen()

case .changeDisplayName:
ChangeDisplayNameScreen(currentName: sessionContainer.session.profile?.displayName ?? "")

case .settingsAdvancedFeatures:
SettingsAdvancedFeaturesScreen()

Expand Down
31 changes: 24 additions & 7 deletions Flipcash/Core/Screens/Main/Profile/ProfileNameScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ private let logger = Logger(label: "flipcash.profile-name")

struct ProfileNameScreen: View {

/// Where a saved name leads.
enum Completion {
/// Profile setup: on to the tip card the name unlocks.
case tipcard
/// A lone edit: back to the screen that opened this one.
case back
}

var completion: Completion = .tipcard

@Environment(Container.self) private var container
@Environment(SessionContainer.self) private var sessionContainer
@Environment(AppRouter.self) private var router
Expand Down Expand Up @@ -96,13 +106,20 @@ struct ProfileNameScreen: View {
)
try await sessionContainer.session.updateProfile()

// `push` resolves the stack when it runs, and this runs after two
// RPCs — by now the user may have swapped to another sheet, whose
// stack has no profile-creation state to mount against.
guard !Task.isCancelled, router.presentedSheet?.stack == .tips else { return }
// The photo-capture step is skipped for now — the card omits the
// profile photo, so setup goes straight from the name to the card.
router.push(.tipcard)
guard !Task.isCancelled else { return }

switch completion {
case .tipcard:
// `push` resolves the stack when it runs, and this runs after two
// RPCs — by now the user may have swapped to another sheet, whose
// stack has no profile-creation state to mount against.
guard router.presentedSheet?.stack == .tips else { return }
// The photo-capture step is skipped for now — the card omits the
// profile photo, so setup goes straight from the name to the card.
router.push(.tipcard)
case .back:
router.popTopmost()
}

} catch ErrorProfile.moderated(let category) {
logger.info("Display name moderation denied", metadata: ["category": "\(category)"])
Expand Down
29 changes: 29 additions & 0 deletions Flipcash/Core/Screens/Settings/ChangeDisplayNameScreen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// ChangeDisplayNameScreen.swift
// Flipcash
//

import SwiftUI

/// Editing the display name on its own, reached from My Account (node
/// 9277:121893). Hosts the profile-setup name step, which returns here once the
/// name saves instead of carrying on to the tip card.
struct ChangeDisplayNameScreen: View {

/// The name step reads its text from the environment: profile setup owns
/// that state at its sheet root so the photo step can read the name back.
/// A lone edit has no following step, so it owns the state itself, seeded
/// with the name already on the profile.
@State private var creationState: ProfileCreationState

init(currentName: String) {
let state = ProfileCreationState()
state.displayName = currentName
_creationState = State(initialValue: state)
}

var body: some View {
ProfileNameScreen(completion: .back)
.environment(creationState)
}
}
4 changes: 4 additions & 0 deletions Flipcash/Core/Screens/Settings/SettingsMyAccountScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ struct SettingsMyAccountScreen: View {
@ViewBuilder
private func list() -> some View {
VStack(alignment: .leading, spacing: 0) {
SettingsRow(asset: .profile, title: "Change Display Name", insets: insets) {
router.push(.changeDisplayName)
}

SettingsRow(systemImage: "nosign", title: "Blocked", insets: insets) {
router.push(.blockedUsers)
}
Expand Down
1 change: 1 addition & 0 deletions FlipcashTests/Navigation/AppRouterCrossStackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ struct AppRouterCrossStackTests {
(AppRouter.Destination.usdcDepositEducation, AppRouter.Stack.balance),
(AppRouter.Destination.usdcDepositAddress, AppRouter.Stack.balance),
(AppRouter.Destination.settingsMyAccount, AppRouter.Stack.settings),
(AppRouter.Destination.changeDisplayName, AppRouter.Stack.settings),
(AppRouter.Destination.settingsAdvancedFeatures, AppRouter.Stack.settings),
(AppRouter.Destination.settingsAppSettings, AppRouter.Stack.settings),
(AppRouter.Destination.settingsAdvancedBetaFeatures, AppRouter.Stack.settings),
Expand Down
13 changes: 13 additions & 0 deletions FlipcashTests/Navigation/YouTabRoutingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,17 @@ struct YouTabRoutingTests {
#expect(router[.you] == AppRouter.navigationPath(.settingsMyAccount))
#expect(router[.settings].isEmpty) // never leaks onto the Settings sheet's stack
}

@Test("changing the display name pushes onto the You tab, and saving pops back")
func changeDisplayName_pushesAndPopsOnYouStack() {
let router = AppRouter()
router.activeTabStack = .you
router.push(.settingsMyAccount)
router.push(.changeDisplayName)
#expect(router[.you] == AppRouter.navigationPath(.settingsMyAccount, .changeDisplayName))

// What `ProfileNameScreen(completion: .back)` runs once the name saves.
router.popTopmost()
#expect(router[.you] == AppRouter.navigationPath(.settingsMyAccount))
}
}