fix: move public trust caption to a localized caption band (#1064)

Ask 6 — SE-width header overflow: the mesh trust caption lived as a
`.fixedSize(horizontal: true)` `Text` inside the header's trailing
`.layoutPriority(3)` (non-compressible) cluster, so on a narrow (SE-width)
header it forced the row to overflow. Relocated it out of the header into a
persistent full-width caption band under the public timeline — the parity twin
of the DM sheet's `privacyCaption` band (#1366): same structure (full width,
`.themedSurface()`, centered, combined a11y), rendered above the composer in
both the glass and matrix layouts, mesh-only (geohash/location channels carry no
such caption). It is muted (`palette.secondary`), not orange, because orange is
the DM privacy signal and this surface is deliberately public. `ContentView`
gains `locationChannelsModel` (already injected on its environment in
`BitchatApp`) to gate the mesh condition.

Ask 7 — untranslatable hardcoded vocabulary: the caption and its accessibility
label were `Text(verbatim:)` literals ("public · unencrypted" / "public,
unencrypted") that bypassed `Localizable.xcstrings`. Replaced with two new
localized keys — `content.header.public_caption` ("public · nearby", visible,
`·` separator matching the DM caption band's "private · end-to-end encrypted")
and `content.header.public_caption.a11y` ("public, nearby", VoiceOver, comma
form). Both reuse the established public-surface vocabulary from #1357's
`content.input.placeholder.mesh` ("… public, nearby") rather than coining a new
"unencrypted" string. Only the `en` source value is seeded; other locales are
left for translators, matching the existing DM-caption keys.

NOT locally compiled (Swift/iOS; CI runs Build iOS + Swift Tests). Visual
placement/spacing of the new band is unverified — needs an SE-width eyeball.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ecgang 2026-07-06 10:41:04 -07:00
parent e3acdfb599
commit 87f1561f34
3 changed files with 49 additions and 10 deletions

View File

@ -18051,6 +18051,30 @@
}
}
},
"content.header.public_caption" : {
"comment" : "Trust caption under the public mesh timeline: this channel is public and reaches nearby devices. Reuses the composer placeholder vocabulary (content.input.placeholder.mesh).",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "public · nearby"
}
}
}
},
"content.header.public_caption.a11y" : {
"comment" : "Accessibility label for the public mesh trust caption; comma form of content.header.public_caption for VoiceOver.",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "public, nearby"
}
}
}
},
"content.help.verification" : {
"extractionState" : "manual",
"localizations" : {

View File

@ -186,15 +186,6 @@ struct ContentHeaderView: View {
}
.buttonStyle(.plain)
if case .mesh = locationChannelsModel.selectedChannel {
Text(verbatim: "public · unencrypted")
.bitchatFont(size: 10)
.foregroundColor(palette.secondary)
.lineLimit(1)
.fixedSize(horizontal: true, vertical: false)
.accessibilityLabel(Text(verbatim: "public, unencrypted"))
}
Button(action: {
withAnimation(.easeInOut(duration: TransportConfig.uiAnimationMediumSeconds)) {
showSidebar.toggle()

View File

@ -35,6 +35,7 @@ struct ContentView: View {
@EnvironmentObject private var privateConversationModel: PrivateConversationModel
@EnvironmentObject private var verificationModel: VerificationModel
@EnvironmentObject private var conversationUIModel: ConversationUIModel
@EnvironmentObject private var locationChannelsModel: LocationChannelsModel
@StateObject private var voiceRecordingVM = VoiceRecordingViewModel()
@State private var messageText = ""
@ -239,7 +240,10 @@ struct ContentView: View {
}
.safeAreaInset(edge: .bottom, spacing: 0) {
if selectedPrivatePeerID == nil {
composerView
VStack(spacing: 0) {
meshPrivacyCaption
composerView
}
}
}
} else {
@ -260,12 +264,32 @@ struct ContentView: View {
Divider()
if selectedPrivatePeerID == nil {
meshPrivacyCaption
composerView
}
}
}
}
/// Persistent trust caption under the PUBLIC mesh timeline the parity
/// twin of the DM sheet's `privacyCaption` (#1366). Moved here out of the
/// header's non-compressible trailing cluster, where its `.fixedSize` text
/// overflowed narrow (SE-width) headers. Mesh-only: geohash/location
/// channels carry no such caption. Muted rather than orange orange is the
/// DM privacy signal; this surface is deliberately public.
@ViewBuilder
private var meshPrivacyCaption: some View {
if case .mesh = locationChannelsModel.selectedChannel {
Text("content.header.public_caption")
.bitchatFont(size: 11, weight: .medium)
.foregroundColor(palette.secondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 4)
.themedSurface()
.accessibilityLabel(Text("content.header.public_caption.a11y"))
}
}
private var headerView: some View {
ContentHeaderView(
showSidebar: $appChromeModel.showSidebar,