mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 23:45:20 +00:00
ux-fixes: lock alignment, caption band, wrapping, tap targets, VoiceOver, theme sweep (#1366)
* Fix lock glyph alignment, privacy-caption band, and empty-state wrapping - Message-row locks: align to first text baseline instead of a hardcoded top padding that left the lock ~4pt below the line's visual center - Header/caption locks: 1pt optical lift (lock.fill ink is bottom-heavy; geometric centering reads low); seal badge stays untouched - DM privacy caption: sit on the themed surface like the rest of the bottom chrome instead of painting its own orange band - Empty-state lines: non-breaking spaces so the closing * can't orphan and 'bitchat/ for help' can't break right after the slash Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Unify sheet close buttons, widen tiny tap targets, handle long nicknames - New SheetCloseButton component: one glyph size/weight (13 semibold), 32pt visual box, 44pt hit target; adopted by all 7 sheets (sizes had drifted across 12/13/14pt, two had no frame at all) - Favorite star buttons get real tap targets (peer list + DM header) - DM header nickname: single line with middle truncation instead of wrapping into the fixed-height header; peer-list names truncate tail - Geohash people rows: leading glyph 12 -> 10 to match mesh rows - Sidebar lock glyphs get the same optical lift as the DM header Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Make channel switching, voice notes, and header actions work under VoiceOver - Channel rows in the location sheet are now single activatable buttons (label + selected trait + switch hint) with the bookmark toggle mirrored as a named accessibility action; bookmark buttons labeled - Voice-note mic: press-and-hold drag gestures can't be activated by VoiceOver, so the default accessibility action now toggles start/stop-and-send; announces 'recording' state; localized labels - Attachment button: camera (long-press) path exposed as a named action; labels localized instead of hardcoded English - People-count button announces connected vs no-one-reachable (was color-only); verification QR button gains a spoken name (.help is only a hint on iOS); bitchat/ logo exposes its tap-for-app-info as a button (panic triple-tap stays undiscoverable on purpose) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Theme-correctness sweep: palette colors everywhere, AX-size header growth - Fingerprint/verification sheet cards: palette-tinted boxes instead of fixed gray bands that ignored matrix green and occluded glass - Voice-note card: palette background (translucent) instead of opaque white/black; waveform + payment chips + image placeholder follow suit - .secondary/.primary/Color.blue swapped for palette.secondary/primary/ accentBlue across location sheets, people sheets, message captions, and the header count (system gray read wrong under matrix green) - Autocomplete/command rows: dropped the uniform gray wash that dulled the themed overlay panel - 'tap to reveal' caption follows the theme font instead of hardcoding monospaced - Headers use minHeight so two-line accessibility text sizes grow the bar instead of clipping inside it Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix main header expanding to fill the screen The header bar's fixed height was load-bearing: its children fill the bar with .frame(maxHeight: .infinity) tap targets, so switching to an open-ended minHeight let the header expand to swallow all available vertical space, centering the title mid-screen and crushing the timeline into the composer. Restore the fixed height — headerHeight is a @ScaledMetric, so it already grows with Dynamic Type. Reproduced and verified both layouts with an offscreen render harness. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * DM header: floating glass panel instead of muddy orange wash under glass Orange at 14% over the backdrop gradient reads as a gray-beige band, not a privacy signature. Under liquid glass the DM header now uses the same floating chrome panel as the main header; the private signature is already carried by the orange lock, caption, and composer accents. Matrix keeps its orange wash over the opaque themed surface, unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
jack
Claude Fable 5
parent
8296630cf3
commit
d285c6ad53
@@ -160,24 +160,23 @@ private struct ContentPeopleListView: View {
|
||||
.font(.bitchatSystem(size: 14))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
// .help maps to the accessibility *hint* on iOS, so the
|
||||
// button still needs a spoken name.
|
||||
.accessibilityLabel(
|
||||
String(localized: "content.accessibility.verification", comment: "Accessibility label for the verification QR button")
|
||||
)
|
||||
.help(
|
||||
String(localized: "content.help.verification", comment: "Help text for verification button")
|
||||
)
|
||||
}
|
||||
Button(action: {
|
||||
SheetCloseButton {
|
||||
withAnimation(.easeInOut(duration: TransportConfig.uiAnimationMediumSeconds)) {
|
||||
dismiss()
|
||||
showSidebar = false
|
||||
showVerifySheet = false
|
||||
privateConversationModel.endConversation()
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "xmark")
|
||||
.bitchatFont(size: 12, weight: .semibold)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("Close")
|
||||
}
|
||||
|
||||
let activeText = String.localizedStringWithFormat(
|
||||
@@ -199,13 +198,13 @@ private struct ContentPeopleListView: View {
|
||||
Text(subtitle)
|
||||
.foregroundColor(subtitleColor)
|
||||
Text(activeText)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(palette.secondary)
|
||||
}
|
||||
.bitchatFont(size: 12)
|
||||
} else {
|
||||
Text(activeText)
|
||||
.bitchatFont(size: 12)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(palette.secondary)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
@@ -341,6 +340,9 @@ private struct ContentPrivateChatSheetView: View {
|
||||
Image(systemName: headerState.isFavorite ? "star.fill" : "star")
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(headerState.isFavorite ? Color.yellow : palette.primary)
|
||||
// Same visual box + 44pt hit target as SheetCloseButton.
|
||||
.frame(width: 32, height: 32)
|
||||
.contentShape(Rectangle().inset(by: -6))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(
|
||||
@@ -354,30 +356,20 @@ private struct ContentPrivateChatSheetView: View {
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
Button(action: {
|
||||
SheetCloseButton {
|
||||
withAnimation(.easeInOut(duration: TransportConfig.uiAnimationMediumSeconds)) {
|
||||
privateConversationModel.endConversation()
|
||||
showSidebar = true
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "xmark")
|
||||
.bitchatFont(size: 12, weight: .semibold)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("Close")
|
||||
}
|
||||
.frame(height: headerHeight)
|
||||
// minHeight so scaled text at accessibility sizes grows the
|
||||
// bar instead of clipping inside it.
|
||||
.frame(minHeight: headerHeight)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 12)
|
||||
// Orange tint before themedSurface so it layers in front of the
|
||||
// (opaque, in matrix) themed background rather than behind it.
|
||||
// Glass has no opaque backing — themedSurface is a no-op there,
|
||||
// so the wash needs more opacity to read over the backdrop
|
||||
// gradient's blue/purple glows.
|
||||
.background(Color.orange.opacity(theme.usesGlassChrome ? 0.14 : 0.06))
|
||||
.themedSurface()
|
||||
.modifier(PrivateHeaderChrome())
|
||||
}
|
||||
|
||||
MessageListView(
|
||||
@@ -446,13 +438,19 @@ private struct ContentPrivateChatSheetView: View {
|
||||
HStack(spacing: 5) {
|
||||
Image(systemName: "lock.fill")
|
||||
.font(.bitchatSystem(size: 9))
|
||||
// Optical centering: lock.fill's ink is bottom-heavy, so
|
||||
// geometric centering reads low next to the caption text.
|
||||
.offset(y: -1)
|
||||
Text(verbatim: privacyCaptionText)
|
||||
.bitchatFont(size: 11, weight: .medium)
|
||||
}
|
||||
.foregroundColor(Color.orange)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 4)
|
||||
.background(Color.orange.opacity(0.08))
|
||||
// The orange text is signature enough; a tinted band here reads as a
|
||||
// stray strip against the untinted composer chrome below it, so the
|
||||
// caption sits on the same surface as the rest of the bottom chrome.
|
||||
.themedSurface()
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
|
||||
@@ -474,6 +472,28 @@ private struct ContentPrivateChatSheetView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Chrome for the private-chat header. Matrix keeps its orange privacy wash
|
||||
/// over an opaque themed surface. Glass gets the same floating panel as the
|
||||
/// main header instead: an orange wash over the backdrop gradient reads as a
|
||||
/// muddy gray-beige band, and the DM signature is already carried by the
|
||||
/// orange lock, caption, and composer accents.
|
||||
private struct PrivateHeaderChrome: ViewModifier {
|
||||
@Environment(\.appTheme) private var theme
|
||||
|
||||
@ViewBuilder
|
||||
func body(content: Content) -> some View {
|
||||
if theme.usesGlassChrome {
|
||||
content.themedChromePanel(edge: .top)
|
||||
} else {
|
||||
// Orange tint before themedSurface so it layers in front of the
|
||||
// opaque themed background rather than behind it.
|
||||
content
|
||||
.background(Color.orange.opacity(0.06))
|
||||
.themedSurface()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ContentPrivateHeaderInfoButton: View {
|
||||
@EnvironmentObject private var appChromeModel: AppChromeModel
|
||||
@ThemedPalette private var palette
|
||||
@@ -506,17 +526,27 @@ private struct ContentPrivateHeaderInfoButton: View {
|
||||
// Absence of a glyph was the only offline signal; say it.
|
||||
Text("mesh_peers.state.offline")
|
||||
.bitchatFont(size: 11)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(palette.secondary)
|
||||
}
|
||||
|
||||
Text(headerState.displayName)
|
||||
.bitchatFont(size: 16, weight: .medium)
|
||||
.foregroundColor(palette.primary)
|
||||
// Middle truncation keeps the identity suffix visible on
|
||||
// long nicknames instead of wrapping into the fixed-height
|
||||
// header.
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
|
||||
if let encryptionStatus = headerState.encryptionStatus,
|
||||
let icon = encryptionStatus.icon {
|
||||
Image(systemName: icon)
|
||||
.font(.bitchatSystem(size: 14))
|
||||
// Optical centering: the lock glyphs' ink is bottom-heavy
|
||||
// (solid body, thin shackle), so geometric centering reads
|
||||
// ~1pt low next to the name. The seal badge is symmetric
|
||||
// and needs no lift.
|
||||
.offset(y: icon.hasPrefix("lock") ? -1 : 0)
|
||||
.foregroundColor(
|
||||
encryptionStatus == .noiseVerified || encryptionStatus == .noiseSecured
|
||||
? palette.primary
|
||||
@@ -543,6 +573,6 @@ private struct ContentPrivateHeaderInfoButton: View {
|
||||
.accessibilityHint(
|
||||
String(localized: "content.accessibility.view_fingerprint_hint", comment: "Accessibility hint for viewing encryption fingerprint")
|
||||
)
|
||||
.frame(height: headerHeight)
|
||||
.frame(minHeight: headerHeight)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user