Skip to content

Commit 1ba8a03

Browse files
authored
feat(you): build out the You tab's tip card (#618)
* feat(you): build out the You tab's tip card The tab shipped as a card and two settings rows. This fills in the rest of the page (node 9276:4634): the copyable public link, Share and Download, and a card that expands in place — everything below it slides out, the tab bar goes with it, and the card grows about its own centre into the middle of the screen. Expansion lives on the page rather than the app-root bill overlay, so the card never leaves the tab; `Session.presentOwnTipcard` and its test go with the overlay route. Download exports the code as a real file. Both formats draw the same figure at 1024pt on the app's background: the SVG comes from the shared Kotlin renderer in `SharedCoreKit` (`KikCode.svg`), the PNG from the `CodeView` the app already draws, so exporting one and then the other gives you the same code twice. The file goes to the share sheet, which is where iOS keeps "Save to Files". Settings picked up the loose ends: Beta Features absorbed the developer-only Beta Flags screen (one screen, two sections, gated on the version easter egg) rather than keeping a second entry point that opened an empty list. * fix(you): present the export share sheet after the picker closes The download sheet handed the file to `ShareSheet` from its own tap handler, while the picker was still on screen. `ShareSheet` presents on the top-most view controller, which at that moment is the picker being dismissed — so the share sheet had a fair chance of never appearing. Holds the tapped format and exports on the sheet's `onDismiss` instead, when there is a settled controller to present on. * fix(settings): always offer the Beta Features row The row was hidden when no flag was visible, on the reasoning that it would open an empty screen. The screen has an empty state for exactly that case, so the row stays put and `hasVisibleOptions` now only decides which of the two the screen draws. * test(you): add Maestro flows for the tip card export and Beta Features Drives what the unit tests can't reach: the download picker's hand-off to the system share sheet, which is a UIKit presentation the export path gets wrong easily, and the Beta Features row's presence in Advanced. Run one with: maestro --device <udid> test .maestro/you-tab-download.yaml * test(settings): add a Maestro flow for the Beta Features empty state * feat(you): add Change Display Name to My Account (#621) Node 9277:121893 opens My Account with a Change Display Name row. It lands straight on the profile-setup name step, which now takes what a saved name leads to: setup carries on to the tip card, a lone edit returns to the settings list. The step reads its text from an environment-held state that setup owns at its sheet root so the photo step can read the name back; a lone edit has no following step, so it owns that state itself, seeded with the name already on the profile. Mirrors the Android row (code-android-app#1299). * refactor(bill): draw codes from the shared geometry CodeView carried its own copy of the ring layout, the bit packing, and the badge, all of which already exist in shared-core and now come back as CGPaths through SharedCoreKit. Deleting the copy is what keeps a code on screen, an exported PNG, an exported SVG, and Android drawing one figure rather than four that happen to agree today.
1 parent 8d0cd69 commit 1ba8a03

45 files changed

Lines changed: 1365 additions & 597 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,6 @@ Package.resolved
5050
/.build-benchmark
5151

5252
# Claude
53-
.claude/worktrees
53+
.claude/worktrees
54+
# Maestro flow screenshots
55+
.maestro/screenshots/
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Revokes developer access through the version easter egg (ten taps) and checks
2+
# the Beta Features row still opens — onto its empty state. Restores access at
3+
# the end, so the flow leaves the device as it found it.
4+
appId: com.flipcash.app.ios
5+
---
6+
- launchApp
7+
- tapOn: "You"
8+
- scrollUntilVisible:
9+
element:
10+
text: "Version .*"
11+
centerElement: true
12+
- repeat:
13+
times: 10
14+
commands:
15+
- tapOn: "Version .*"
16+
17+
- tapOn: "Advanced"
18+
- assertVisible: "Beta Features"
19+
- tapOn: "Beta Features"
20+
- assertVisible: "No Beta Features"
21+
- assertVisible: "There are no beta features available right now."
22+
- takeScreenshot: .maestro/screenshots/beta-features-empty
23+
24+
# --- put the access back ---
25+
- launchApp
26+
- tapOn: "You"
27+
- scrollUntilVisible:
28+
element:
29+
text: "Version .*"
30+
centerElement: true
31+
- repeat:
32+
times: 10
33+
commands:
34+
- tapOn: "Version .*"
35+
- tapOn: "Advanced"
36+
- tapOn: "Beta Features"
37+
- assertVisible: "Vibrate on scan"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# The Beta Features row is always offered, whether or not any flag is on: the
2+
# screen behind it draws its own empty state.
3+
appId: com.flipcash.app.ios
4+
---
5+
- launchApp
6+
- tapOn: "You"
7+
# The settings rows sit below the fold, under the tab bar.
8+
- scrollUntilVisible:
9+
element:
10+
text: "Advanced"
11+
centerElement: true
12+
- tapOn: "Advanced"
13+
- assertVisible: "Beta Features"
14+
- tapOn: "Beta Features"
15+
- takeScreenshot: .maestro/screenshots/beta-features

‎.maestro/you-tab-download.yaml‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Drives the You tab's export path end to end, once per format: card page ->
2+
# Download -> pick a format -> the system share sheet holding the written file.
3+
appId: com.flipcash.app.ios
4+
---
5+
- launchApp
6+
- tapOn: "You"
7+
- assertVisible: "Full Screen"
8+
9+
# --- SVG ---
10+
- tapOn:
11+
id: "you-download-button"
12+
- assertVisible: "Download As"
13+
- assertVisible: "PNG"
14+
- assertVisible: "SVG"
15+
- assertVisible: "Stays sharp at any size"
16+
- tapOn:
17+
id: "you-download-svg"
18+
# The share sheet titles and sizes itself from the exported file.
19+
- assertVisible: "Copy"
20+
- takeScreenshot: .maestro/screenshots/share-sheet-svg
21+
22+
# --- PNG ---
23+
# Relaunching is the cheapest way to get the system sheet off screen.
24+
- launchApp
25+
- tapOn: "You"
26+
- tapOn:
27+
id: "you-download-button"
28+
- tapOn:
29+
id: "you-download-png"
30+
- assertVisible: "Copy"
31+
- takeScreenshot: .maestro/screenshots/share-sheet-png

‎Code.xcodeproj/project.pbxproj‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
9A017D692EAC1DE200216925 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 9A017D682EAC1DE200216925 /* Kingfisher */; };
2525
9A444EE72E8C414D002B1E39 /* BigDecimal in Frameworks */ = {isa = PBXBuildFile; productRef = 9A444EE62E8C414D002B1E39 /* BigDecimal */; };
2626
9A780F972EDF817D009BC4D9 /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 9A780F962EDF817D009BC4D9 /* CodeScanner */; };
27+
9AE5C0122FA10003004C0DE0 /* SharedCoreKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9AE5C0112FA10002004C0DE0 /* SharedCoreKit */; };
2728
9ABDD1952D9D7B61006B6CDA /* FlipcashCore in Frameworks */ = {isa = PBXBuildFile; productRef = 9ABDD1942D9D7B61006B6CDA /* FlipcashCore */; };
2829
9AC011182DA4320F0030298E /* FlipcashUI in Frameworks */ = {isa = PBXBuildFile; productRef = 9AC011172DA4320F0030298E /* FlipcashUI */; };
2930
9AC0156C2DA576FA0030298E /* opencv2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AD5E64B25F2BF3E007388AE /* opencv2.framework */; };
@@ -175,6 +176,7 @@
175176
9AC0156C2DA576FA0030298E /* opencv2.framework in Frameworks */,
176177
9ABDD1952D9D7B61006B6CDA /* FlipcashCore in Frameworks */,
177178
9ADEF1D92DD627C6001B260A /* Mixpanel in Frameworks */,
179+
9AE5C0122FA10003004C0DE0 /* SharedCoreKit in Frameworks */,
178180
);
179181
runOnlyForDeploymentPostprocessing = 0;
180182
};
@@ -378,6 +380,7 @@
378380
9A780F962EDF817D009BC4D9 /* CodeScanner */,
379381
4CB225752F77260D0075874E /* FirebaseInstallations */,
380382
4CB225772F7726500075874E /* FirebaseMessaging */,
383+
9AE5C0112FA10002004C0DE0 /* SharedCoreKit */,
381384
);
382385
productName = Flipcash;
383386
productReference = 9A516F7A2D9B12E900AF478B /* Flipcash.app */;
@@ -506,6 +509,7 @@
506509
10A6C0E8F8974C1887219D41 /* XCRemoteSwiftPackageReference "tweetnacl-swiftwrap" */,
507510
9A444EE52E8C414D002B1E39 /* XCRemoteSwiftPackageReference "BigDecimal" */,
508511
9A780F952EDF817D009BC4D9 /* XCLocalSwiftPackageReference "CodeScanner" */,
512+
9AE5C0102FA10001004C0DE0 /* XCRemoteSwiftPackageReference "flipcash-shared-core-spm" */,
509513
);
510514
productRefGroup = 9AED1101258BE1310088D902 /* Products */;
511515
projectDirPath = "";
@@ -1759,6 +1763,14 @@
17591763
kind = branch;
17601764
};
17611765
};
1766+
9AE5C0102FA10001004C0DE0 /* XCRemoteSwiftPackageReference "flipcash-shared-core-spm" */ = {
1767+
isa = XCRemoteSwiftPackageReference;
1768+
repositoryURL = "https://github.com/code-payments/flipcash-shared-core-spm";
1769+
requirement = {
1770+
kind = upToNextMinorVersion;
1771+
minimumVersion = 0.3.1;
1772+
};
1773+
};
17621774
9ACE172927E6287C00ACA047 /* XCRemoteSwiftPackageReference "mixpanel-swift" */ = {
17631775
isa = XCRemoteSwiftPackageReference;
17641776
repositoryURL = "https://github.com/mixpanel/mixpanel-swift";
@@ -1816,6 +1828,11 @@
18161828
package = 9A9499D62CFB829300DC219B /* XCRemoteSwiftPackageReference "SQLite" */;
18171829
productName = SQLite;
18181830
};
1831+
9AE5C0112FA10002004C0DE0 /* SharedCoreKit */ = {
1832+
isa = XCSwiftPackageProductDependency;
1833+
package = 9AE5C0102FA10001004C0DE0 /* XCRemoteSwiftPackageReference "flipcash-shared-core-spm" */;
1834+
productName = SharedCoreKit;
1835+
};
18191836
9ADEF1D62DD627C0001B260A /* Bugsnag */ = {
18201837
isa = XCSwiftPackageProductDependency;
18211838
package = 9A476D482978884700C5B5CE /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */;

‎Code.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved‎

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Flipcash/Core/Controllers/BetaFlags.swift‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ class BetaFlags {
5454
hasEnabled(.newUI)
5555
}
5656

57+
/// Whether the Beta Features screen has anything to show — the public
58+
/// flags everyone gets, plus the developer-only section once the version
59+
/// easter egg unlocks access. False means the screen draws its empty state.
60+
var hasVisibleOptions: Bool {
61+
accessGranted || Option.allCases.contains { $0.availability == .publicBeta }
62+
}
63+
5764
/// Enables or disables a beta flag and persists the change to disk.
5865
func set(_ option: Option, enabled: Bool) {
5966
if enabled {

‎Flipcash/Core/Navigation/AppRouter+Destination.swift‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,13 @@ extension AppRouter {
5050

5151
// Settings flow
5252
case settingsMyAccount
53+
/// The display name on its own, edited from My Account. The full
54+
/// profile-setup flow starts on the same screen but carries on to the
55+
/// tip card; this one returns to the settings list.
56+
case changeDisplayName
5357
case settingsAdvancedFeatures
5458
case settingsAdvancedBetaFeatures
5559
case settingsAppSettings
56-
case settingsBetaFlags
5760
case settingsAccountSelection
5861
case settingsApplicationLogs
5962
case blockedUsers
@@ -86,8 +89,8 @@ extension AppRouter {
8689
.transactionHistory, .activity, .give, .buyCurrency, .convertCurrency,
8790
.withdrawCurrency, .usdcDepositEducation, .usdcDepositAddress:
8891
return .balance
89-
case .settingsMyAccount, .settingsAdvancedFeatures, .settingsAdvancedBetaFeatures,
90-
.settingsAppSettings, .settingsBetaFlags, .settingsAccountSelection,
92+
case .settingsMyAccount, .changeDisplayName, .settingsAdvancedFeatures,
93+
.settingsAdvancedBetaFeatures, .settingsAppSettings, .settingsAccountSelection,
9194
.settingsApplicationLogs, .blockedUsers, .accessKey, .withdraw:
9295
return .settings
9396
case .profileName, .profilePhoto, .tipcard,
@@ -117,10 +120,10 @@ extension AppRouter {
117120
case .usdcDepositEducation: "usdcDepositEducation"
118121
case .usdcDepositAddress: "usdcDepositAddress"
119122
case .settingsMyAccount: "settingsMyAccount"
123+
case .changeDisplayName: "changeDisplayName"
120124
case .settingsAdvancedFeatures: "settingsAdvancedFeatures"
121125
case .settingsAdvancedBetaFeatures: "settingsAdvancedBetaFeatures"
122126
case .settingsAppSettings: "settingsAppSettings"
123-
case .settingsBetaFlags: "settingsBetaFlags"
124127
case .settingsAccountSelection: "settingsAccountSelection"
125128
case .settingsApplicationLogs: "settingsApplicationLogs"
126129
case .blockedUsers: "blockedUsers"
@@ -157,8 +160,8 @@ extension AppRouter {
157160
case .activity,
158161
.discoverCurrencies, .currencyCreationSummary, .currencyCreationWizard,
159162
.usdcDepositEducation, .usdcDepositAddress,
160-
.settingsMyAccount, .settingsAdvancedFeatures, .settingsAdvancedBetaFeatures,
161-
.settingsAppSettings, .settingsBetaFlags, .settingsAccountSelection,
163+
.settingsMyAccount, .changeDisplayName, .settingsAdvancedFeatures,
164+
.settingsAdvancedBetaFeatures, .settingsAppSettings, .settingsAccountSelection,
162165
.settingsApplicationLogs, .blockedUsers, .accessKey, .withdraw,
163166
.profileName, .profilePhoto, .tipcard:
164167
return nil

‎Flipcash/Core/Navigation/AppRouter+DestinationView.swift‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ struct DestinationView: View {
7070
case .settingsMyAccount:
7171
SettingsMyAccountScreen()
7272

73+
case .changeDisplayName:
74+
ChangeDisplayNameScreen(currentName: sessionContainer.session.profile?.displayName ?? "")
75+
7376
case .settingsAdvancedFeatures:
7477
SettingsAdvancedFeaturesScreen()
7578

@@ -79,9 +82,6 @@ struct DestinationView: View {
7982
case .settingsAppSettings:
8083
SettingsAppSettingsScreen()
8184

82-
case .settingsBetaFlags:
83-
BetaFlagsScreen()
84-
8585
case .settingsAccountSelection:
8686
// The action closure dismisses the settings sheet and switches accounts.
8787
// Captured at the modifier site so the AppRouter stays pure-navigation.

‎Flipcash/Core/Screens/Main/Home/HomeTabView.swift‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,16 @@ struct HomeTabView: View {
5757
}
5858

5959
/// Hide the tab bar whenever the active tab has pushed a screen (e.g. Wallet →
60-
/// Currency Info → Give) so that screen owns the full height, and while a
60+
/// Currency Info → Give) so that screen owns the full height, while a
6161
/// bill/tipcard is up (the app-root overlay owns the screen then, as the v1
62-
/// bill replaced the bottom bar). Sheets already cover the bar, so only
63-
/// in-tab pushes need handling here.
62+
/// bill replaced the bottom bar), and while the You tab's card is expanded.
63+
/// Sheets already cover the bar, so only in-tab pushes need handling here.
6464
@State private var cardExpansion = WalletCardExpansion()
65+
@State private var tipCardPresentation = TipCardPresentation()
6566

6667
private var isTabBarHidden: Bool {
6768
if cardExpansion.isExpanded { return true }
69+
if tipCardPresentation.isExpanded { return true }
6870
if sessionContainer.session.isShowingBill { return true }
6971
guard let stack = selection.pushStack else { return false }
7072
return !router[stack].isEmpty
@@ -96,7 +98,11 @@ struct HomeTabView: View {
9698
selection = tab
9799
router.requestedTabStack = nil
98100
}
99-
.onChange(of: selection) { _, tab in router.activeTabStack = tab.pushStack }
101+
.onChange(of: selection) { _, tab in
102+
router.activeTabStack = tab.pushStack
103+
// Leaving the tab puts the card back (and the brightness with it).
104+
tipCardPresentation.collapse()
105+
}
100106
.onDisappear { router.activeTabStack = nil }
101107
}
102108

@@ -182,6 +188,7 @@ struct HomeTabView: View {
182188
ChatTab()
183189
case .tipCard:
184190
TipCardTab(onSetUp: { selection = .chat })
191+
.environment(tipCardPresentation)
185192
}
186193
}
187194
}

0 commit comments

Comments
 (0)