Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a33f907
local snapshot: redesign working tree for swipe to send (never push)
aidenjwalker Oct 28, 2026
6af57fe
feat(swipe): three tunable looks (glass thumb, thumb fills, lens) and…
aidenjwalker Oct 28, 2026
86a5357
feat(swipe): Swipe Showcase in Beta Features and --swipe-sandbox
aidenjwalker Oct 28, 2026
a4138a5
test(swipe): pin the commit thresholds (70%, or 50% with a fling)
aidenjwalker Oct 28, 2026
1eb25d5
fix(swipe): review fixes; failure look is opt-in via SwipeControlFailure
aidenjwalker Oct 28, 2026
44c4a33
feat(swipe): bake Aiden's tuned values, default to the lens look, add…
aidenjwalker Oct 28, 2026
6cae53f
feat(swipe): amount-flow page in the Swipe Showcase
aidenjwalker Oct 28, 2026
b65cfa7
feat(swipe): sit the swipe in the Next button's slot on amount entry
aidenjwalker Oct 28, 2026
cbb2153
feat(swipe): track stroke, colour and handle width props; dim the amo…
aidenjwalker Oct 28, 2026
732ffb0
feat(swipe): centre the label in the space beside the thumb
aidenjwalker Oct 28, 2026
6c67915
Revert "feat(swipe): centre the label in the space beside the thumb"
aidenjwalker Oct 28, 2026
e21e331
feat(swipe): bake Aiden's second round of tuned values
aidenjwalker Oct 28, 2026
6896bd7
fix(swipe): lift the thumb on touch-down, even inside a sheet, on the…
aidenjwalker Oct 28, 2026
bdf6bca
feat(swipe): bake Aiden's third round of tuned values
aidenjwalker Oct 28, 2026
54c25d4
feat(swipe): touch haptic and lens clarity tunables
aidenjwalker Oct 28, 2026
b2f750a
feat(swipe): bake the lens look for real use
aidenjwalker Oct 28, 2026
c228fb4
fix(swipe): design review fold-in; lock the keypad while the swipe is…
aidenjwalker Oct 28, 2026
f21b300
docs(profile): survey, data model and round-1 directions for the prof…
aidenjwalker Oct 28, 2026
84d0b66
docs(profile): round-1 frames, critique fold-in and open taste calls
aidenjwalker Oct 28, 2026
46a6d4c
docs(profile): round-2 states and X social link, frame ids and data n…
aidenjwalker Oct 28, 2026
b4b101c
docs(profile): direction D, poster users, cover-only public groups
aidenjwalker Oct 28, 2026
fb49f69
docs(profile): D revision, member grid, requirement pass, social chips
aidenjwalker Oct 28, 2026
4379ff6
docs(profile): requirement status options, status line vs holding meter
aidenjwalker Oct 28, 2026
796d7c5
docs(profile): immersive cover-derived colour for public groups
aidenjwalker Oct 28, 2026
67e805d
docs(profile): tailored chatting-recently options with host highlight
aidenjwalker Oct 28, 2026
f8b9ca0
docs(profile): refined pulse, group bio and polish pass
aidenjwalker Oct 28, 2026
08ab396
docs(profile): build spec for the public group profile in code
aidenjwalker Oct 28, 2026
3c2db72
feat(ui): port the Bleed colour engine into FlipcashUI
aidenjwalker Oct 28, 2026
f0c8e54
feat(profile): public group profile on a cover-derived page
aidenjwalker Oct 28, 2026
f799306
fix(profile): design review fold-in for the public group profile
aidenjwalker Oct 29, 2026
77ad7bb
docs(profile): code build 1 result and open calls
aidenjwalker Oct 29, 2026
0801fb9
feat(profile): box pattern for public group profile, tunable spacing …
aidenjwalker Oct 29, 2026
35bfe99
fix(profile): public group tuner scope, cover stretch, pulse card spa…
aidenjwalker Oct 29, 2026
562f242
fix(profile): bake tuned cover height 320, fade 214, card radius 24
aidenjwalker Oct 29, 2026
ea86bdc
feat(profile): tunable content offset and avatar ring on the public g…
aidenjwalker Oct 29, 2026
921529d
feat(profile): Leave Chat as a calm capsule under the public group's …
aidenjwalker Oct 29, 2026
99c4a4f
fix(ui): app background #121212
aidenjwalker Oct 29, 2026
6bf7f70
fix(profile): app back arrow on the public group profile
aidenjwalker Oct 29, 2026
0ab4b2c
chore(profile): public group profile playground in Beta Features (loc…
aidenjwalker Oct 29, 2026
53bdecc
chore(design): bake tuned values and remove the local tuner
aidenjwalker Oct 29, 2026
3085497
Merge upstream/main into feat/ui-redesign-showcase
aidenjwalker Oct 29, 2026
a44307a
fix(design): fold the design critique's blocking and should-fix findings
aidenjwalker Oct 29, 2026
6331200
test(chat): retarget v1 look assertions to the redesign; shorten the …
aidenjwalker Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
19 changes: 16 additions & 3 deletions Flipcash/Core/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private extension AppDelegate {

func setupAppearance() {
// No font: the bar's own large (34 pt bold) and inline (17 pt semibold) titles are the
// intended ones, and only the bar's defaults follow a text-size change live.
let largeAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.appDisplayLarge,
.foregroundColor: UIColor(.textMain),
]

let titleAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.appTitle,
.foregroundColor: UIColor(.textMain),
]

Expand All @@ -342,13 +342,14 @@ private extension AppDelegate {
buttonAppearance.normal.titleTextAttributes = buttonAttributes

let bar = UINavigationBar.appearance()
let backArrow = Self.backIndicator()

bar.largeTitleTextAttributes = largeAttributes
bar.titleTextAttributes = titleAttributes

if #available(iOS 26, *) {
// iOS 26: use the system's default (Liquid Glass) bar background and
// only override the title fonts. A *transparent* background removes
// only override the title colour. A *transparent* background removes
// the bar's glass material, so the buttons have no stable bar-glass
// to composite against and the system repaints their glass during
// the push transition — that's the flicker as screens push/settle.
Expand All @@ -358,6 +359,7 @@ private extension AppDelegate {
barAppearance.configureWithDefaultBackground()
barAppearance.titleTextAttributes = titleAttributes
barAppearance.largeTitleTextAttributes = largeAttributes
barAppearance.setBackIndicatorImage(backArrow, transitionMaskImage: backArrow)

bar.standardAppearance = barAppearance
bar.scrollEdgeAppearance = barAppearance
Expand All @@ -371,6 +373,7 @@ private extension AppDelegate {
barAppearance.titleTextAttributes = titleAttributes
barAppearance.largeTitleTextAttributes = largeAttributes
barAppearance.backButtonAppearance = buttonAppearance
barAppearance.setBackIndicatorImage(backArrow, transitionMaskImage: backArrow)

bar.standardAppearance = barAppearance
bar.scrollEdgeAppearance = barAppearance
Expand All @@ -381,6 +384,16 @@ private extension AppDelegate {
}

}

/// The navigation back glyph: Central's left arrow at the 22 pt the app's own back buttons use.
static func backIndicator() -> UIImage {
let size = CGSize(width: 22, height: 22)
let arrow = UIImage.icon(.arrowLeft)
return UIGraphicsImageRenderer(size: size).image { _ in
arrow.draw(in: CGRect(origin: .zero, size: size))
}
.withRenderingMode(.alwaysTemplate)
}
}

// MARK: - UINavigationController -
Expand Down
54 changes: 54 additions & 0 deletions Flipcash/Core/FlipcashApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ struct FlipcashApp: App {
#if DEBUG
if MotionSandbox.isRequested {
MotionSandbox()
} else if DialogSandbox.isRequested {
DialogSandbox()
} else if TraySandbox.isRequested {
TraySandbox()
} else if SwipeSandbox.isRequested {
SwipeShowcaseHost()
} else if let benchmark = ScrollBenchmark.requested {
benchmark
} else {
Expand Down Expand Up @@ -183,3 +189,51 @@ private extension View {
modifier(ScenePhaseModifier(appDelegate: appDelegate))
}
}

#if DEBUG
/// Real `DialogItem`s through the real `.dialog(item:)` seam, with no sign-in (`--dialog-sandbox`,
/// `--dialog-sandbox-open <n>`). Actions do nothing.
private struct DialogSandbox: View {

static var isRequested: Bool { ProcessInfo.processInfo.arguments.contains("--dialog-sandbox") }

@State private var dialogItem: DialogItem?

private let trustedWebsites = TrustedWebsites(defaults: UserDefaults(suiteName: "dialog-sandbox")!)

private var items: [(String, () -> DialogItem)] {
[
("Log out (alert, destructive)", {
.alert(title: "Are You Sure You Want To Log Out?", subtitle: "You can get into this account using your Access Key") {
DialogAction.destructive("Log Out") {}
DialogAction.cancel()
}
}),
("Leaving Flipcash (info, checkbox)", { .leavingFlipcash(host: "example.com", trustedWebsites: trustedWebsites) {} }),
("Error (tracked, default OK)", { .error(title: "Something Went Wrong", subtitle: "Please try again later") }),
("Success (not dismissable)", { .success(title: "Report Sent", subtitle: "Thanks. We'll take a look") }),
]
}

var body: some View {
VStack(spacing: 12) {
ForEach(items.indices, id: \.self) { index in
Button(items[index].0) { dialogItem = items[index].1() }
.buttonStyle(.filled20Compact)
}
}
.padding(20)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.backgroundMain)
.dialog(item: $dialogItem)
.preferredColorScheme(.dark)
.task {
let arguments = ProcessInfo.processInfo.arguments
guard let i = arguments.firstIndex(of: "--dialog-sandbox-open"), i + 1 < arguments.count,
let index = Int(arguments[i + 1]), items.indices.contains(index) else { return }
try? await Task.sleep(for: .seconds(0.5))
dialogItem = items[index].1()
}
}
}
#endif
22 changes: 13 additions & 9 deletions Flipcash/Core/Screens/Conversation/AttachMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ struct AttachMenu: View {
}
panel.animate { $0.toggle() }
} label: {
Image(systemName: SystemSymbol.plus.rawValue)
.font(.default(size: 17, weight: .semibold))
Image.icon(.plusLarge)
.iconSize(22)
.foregroundStyle(Color.textMain)
.frame(width: BarMetrics.accessorySize, height: BarMetrics.accessorySize)
.background(AttachSurfaceLayout.plusFill, in: Circle())
Expand Down Expand Up @@ -149,7 +149,12 @@ struct AttachMenuRows: View {
Button {
onSelect(item)
} label: {
Label(item.title, systemImage: item.systemImage)
Label {
Text(item.title)
} icon: {
Image.icon(item.icon)
.iconSize(22)
}
.labelStyle(AttachMenuRowLabelStyle())
}
.buttonStyle(.plain)
Expand All @@ -173,7 +178,6 @@ private struct AttachMenuRowLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(spacing: 14) {
configuration.icon
.font(.default(size: 19, weight: .medium))
.frame(width: Self.iconDiameter, height: Self.iconDiameter)
.background(Color.white.opacity(0.08), in: Circle())
configuration.title
Expand All @@ -198,12 +202,12 @@ extension AttachMenuItem {
}
}

/// The row's SF Symbol.
var systemImage: String {
/// The row's glyph.
var icon: Icon {
switch self {
case .cash: "banknote"
case .camera: "camera"
case .photos: "photo.on.rectangle"
case .cash: .banknote1
case .camera: .camera1
case .photos: .images3
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions Flipcash/Core/Screens/Conversation/AttachSurface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ enum AttachSurfaceLayout {
/// Matches the composer field's corner, so the menu reads as part of it.
static let menuCornerRadius: CGFloat = BarMetrics.fieldCornerRadius
/// `+`'s flat fill on the composer field, which the surface turns into as it reaches `+`.
static let plusFill = Color.white.opacity(0.10)
static let plusFill = Color.white.opacity(0.08)
/// How far past `+`'s size the surface has fully turned from `+`'s fill into the panel's glass.
static let plusBlendDistance: CGFloat = 40
/// Concentric with the screen's corners, which the card stands ``AttachOverlayLayout/cardInset`` in from.
Expand Down Expand Up @@ -247,8 +247,8 @@ struct AttachSurface: View {

/// `+`'s glyph where `+` stands, so the surface reads as `+` itself as it grows and collapses.
private var plusGlyph: some View {
Image(systemName: SystemSymbol.plus.rawValue)
.font(.default(size: 17, weight: .semibold))
Image.icon(.plusLarge)
.iconSize(22)
.foregroundStyle(Color.textMain)
.frame(width: plus.width, height: plus.height)
.accessibilityHidden(true)
Expand Down
14 changes: 7 additions & 7 deletions Flipcash/Core/Screens/Conversation/ChatCameraSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ struct ChatCameraSheet: View {
showsMoreControls.toggle()
}
} label: {
ChatCameraControlLabel(systemImage: showsMoreControls ? "xmark" : "ellipsis")
ChatCameraControlLabel(icon: showsMoreControls ? .crossMedium : .dotGrid1x3Horizontal)
.overlay(alignment: .topTrailing) {
if !showsMoreControls {
Circle()
Expand Down Expand Up @@ -212,7 +212,7 @@ struct ChatCameraSheet: View {
return Button {
camera.toggleFlash()
} label: {
ChatCameraControlLabel(systemImage: isOn ? "bolt.fill" : "bolt.slash.fill")
ChatCameraControlLabel(icon: isOn ? .lightningBolt : .lightningBoltOff)
}
.buttonStyle(.plain)
.accessibilityLabel("Flash")
Expand All @@ -224,7 +224,7 @@ struct ChatCameraSheet: View {
Button {
camera.flip()
} label: {
ChatCameraControlLabel(systemImage: "arrow.triangle.2.circlepath")
ChatCameraControlLabel(icon: .arrowsRepeat)
}
.buttonStyle(.plain)
.accessibilityLabel(camera.position == .front ? "Use back camera" : "Use front camera")
Expand All @@ -233,7 +233,7 @@ struct ChatCameraSheet: View {

private var backButton: some View {
Button(action: onCancel) {
ChatCameraControlLabel(systemImage: "chevron.left")
ChatCameraControlLabel(icon: .arrowLeft)
}
.buttonStyle(.plain)
.accessibilityLabel("Back to attach menu")
Expand All @@ -244,11 +244,11 @@ struct ChatCameraSheet: View {
/// A round control over the viewfinder: a white glyph on dark-tinted glass.
private struct ChatCameraControlLabel: View {

let systemImage: String
let icon: Icon

var body: some View {
Image(systemName: systemImage)
.font(.default(size: 17, weight: .semibold))
Image.icon(icon)
.iconSize(22)
.foregroundStyle(Color.white)
.frame(width: 44, height: 44)
.overlayGlassBackground(in: Circle())
Expand Down
4 changes: 2 additions & 2 deletions Flipcash/Core/Screens/Conversation/ChatPhotosCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ struct ChatPhotosCard: View {
private var controls: some View {
HStack {
Button(action: onBack) {
Image(systemName: "chevron.left")
.font(.default(size: 17, weight: .semibold))
Image.icon(.arrowLeft)
.iconSize(22)
.foregroundStyle(Color.textMain)
.frame(width: 44, height: 44)
.contentShape(Circle())
Expand Down
22 changes: 12 additions & 10 deletions Flipcash/Core/Screens/Conversation/ComposerChipStrip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ private struct ComposerChipView: View {
Button(action: onRetry) {
ZStack {
Color.black.opacity(0.45)
Image(systemName: "arrow.clockwise")
.font(.default(size: 18, weight: .semibold))
Image.icon(.arrowRotateClockwise)
.iconSize(22)
.foregroundStyle(Color.white)
}
}
Expand All @@ -163,8 +163,8 @@ private struct ComposerChipView: View {
case .error:
ZStack {
Color.black.opacity(0.45)
Image(systemName: "exclamationmark.triangle.fill")
.font(.default(size: 18, weight: .semibold))
Image.icon(.exclamationTriangle)
.iconSize(22)
.foregroundStyle(Color.textError)
}
.accessibilityLabel("Photo can't be sent")
Expand Down Expand Up @@ -238,8 +238,8 @@ struct ComposerLinkChip: View {
}
}
} else {
Image(systemName: "link")
.font(.default(size: 20, weight: .semibold))
Image.icon(.chainLink4)
.iconSize(24)
.foregroundStyle(Color.white.opacity(0.6))
}
}
Expand Down Expand Up @@ -293,10 +293,12 @@ private struct ComposerChipRemoveButton: View {

var body: some View {
Button(action: action) {
Image(systemName: SystemSymbol.closeCircle.rawValue)
.font(.default(size: 18, weight: .semibold))
.symbolRenderingMode(.palette)
.foregroundStyle(Color.white, Color.black.opacity(0.55))
Image.icon(.circleXFilled)
.iconSize(22)
.foregroundStyle(Color.white)
// The glyph's ✕ is a hole in its disc, which vanishes over a light photo. A disc of
// the chat's ground under the glyph's middle half draws the ✕ instead.
.background { Circle().fill(Color.backgroundMain).scaleEffect(0.5) }
.padding(3)
.contentShape(Rectangle())
}
Expand Down
17 changes: 6 additions & 11 deletions Flipcash/Core/Screens/Conversation/ComposerReplyStrip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,13 @@ struct ComposerReplyStrip: View {

thumbnail

// A disc rather than a bare ✕, because the ground behind it is glass sampling the
// transcript: a hairline glyph's contrast changed with whatever message scrolled past.
// The fill gives it its own ground and takes that variable out.
Button(action: onDismiss) {
Image(systemName: SystemSymbol.closeCircle.rawValue)
// `.system`, not the app face: an SF Symbol only takes its optical axes from a
// system font.
.font(.system(size: 22))
// Palette, not monochrome. A monochrome fill knocks the ✕ out as a hole, which
// over glass fills with the transcript — the thing the disc is here to stop.
.symbolRenderingMode(.palette)
.foregroundStyle(Color.textMain, Color.textSecondary.opacity(0.35))
Image.icon(.circleXFilled)
.iconSize(28)
.foregroundStyle(Color.textMain)
// The glyph's ✕ is a hole in its disc, which over glass shows the transcript. A
// disc of the chat's ground under the glyph's middle half draws the ✕ instead.
.background { Circle().fill(Color.backgroundMain).scaleEffect(0.5) }
// Wider than the disc: the glyph is small enough to miss on its own.
.frame(width: 34, height: 34)
.contentShape(.rect)
Expand Down
Loading
Loading