diff --git a/bitchat/Localizable.xcstrings b/bitchat/Localizable.xcstrings index 7301e613..eb7cbd5b 100644 --- a/bitchat/Localizable.xcstrings +++ b/bitchat/Localizable.xcstrings @@ -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" : { diff --git a/bitchat/Views/ContentHeaderView.swift b/bitchat/Views/ContentHeaderView.swift index fe027b67..eb188ed7 100644 --- a/bitchat/Views/ContentHeaderView.swift +++ b/bitchat/Views/ContentHeaderView.swift @@ -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() diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 6d3c16dd..503a2bf7 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -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,