mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 23:45:18 +00:00
Support Dynamic Type across chat surfaces (#664)
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Provides Dynamic Type aware font helpers that map existing fixed sizes onto
|
||||
/// preferred text styles so the UI scales with user accessibility settings.
|
||||
extension Font {
|
||||
static func bitchatSystem(size: CGFloat, weight: Font.Weight = .regular, design: Font.Design = .default) -> Font {
|
||||
let style = Font.TextStyle.bitchatPreferredStyle(for: size)
|
||||
var font = Font.system(style, design: design)
|
||||
if weight != .regular {
|
||||
font = font.weight(weight)
|
||||
}
|
||||
return font
|
||||
}
|
||||
}
|
||||
|
||||
private extension Font.TextStyle {
|
||||
static func bitchatPreferredStyle(for size: CGFloat) -> Font.TextStyle {
|
||||
switch size {
|
||||
case ..<11.5:
|
||||
return .caption2
|
||||
case ..<13.0:
|
||||
return .caption
|
||||
case ..<14.0:
|
||||
return .footnote
|
||||
case ..<16.5:
|
||||
return .body
|
||||
case ..<19.0:
|
||||
return .title3
|
||||
case ..<23.0:
|
||||
return .title2
|
||||
case ..<30.0:
|
||||
return .title
|
||||
default:
|
||||
return .largeTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3323,7 +3323,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
senderStyle.foregroundColor = baseColor
|
||||
// Bold the user's own nickname
|
||||
let fontWeight: Font.Weight = isSelf ? .bold : .medium
|
||||
senderStyle.font = .system(size: 14, weight: fontWeight, design: .monospaced)
|
||||
senderStyle.font = .bitchatSystem(size: 14, weight: fontWeight, design: .monospaced)
|
||||
// Make sender clickable: encode senderPeerID into a custom URL
|
||||
if let spid = message.senderPeerID, let url = URL(string: "bitchat://user/\(spid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? spid)") {
|
||||
senderStyle.link = url
|
||||
@@ -3358,8 +3358,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
var plainStyle = AttributeContainer()
|
||||
plainStyle.foregroundColor = baseColor
|
||||
plainStyle.font = isSelf
|
||||
? .system(size: 14, weight: .bold, design: .monospaced)
|
||||
: .system(size: 14, design: .monospaced)
|
||||
? .bitchatSystem(size: 14, weight: .bold, design: .monospaced)
|
||||
: .bitchatSystem(size: 14, design: .monospaced)
|
||||
result.append(AttributedString(content).mergingAttributes(plainStyle))
|
||||
} else {
|
||||
// Reuse compiled regexes and detector
|
||||
@@ -3455,8 +3455,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
var beforeStyle = AttributeContainer()
|
||||
beforeStyle.foregroundColor = baseColor
|
||||
beforeStyle.font = isSelf
|
||||
? .system(size: 14, weight: .bold, design: .monospaced)
|
||||
: .system(size: 14, design: .monospaced)
|
||||
? .bitchatSystem(size: 14, weight: .bold, design: .monospaced)
|
||||
: .bitchatSystem(size: 14, design: .monospaced)
|
||||
if isMentioned {
|
||||
beforeStyle.font = beforeStyle.font?.bold()
|
||||
}
|
||||
@@ -3486,7 +3486,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
return false
|
||||
}()
|
||||
var mentionStyle = AttributeContainer()
|
||||
mentionStyle.font = .system(size: 14, weight: isSelf ? .bold : .semibold, design: .monospaced)
|
||||
mentionStyle.font = .bitchatSystem(size: 14, weight: isSelf ? .bold : .semibold, design: .monospaced)
|
||||
let mentionColor: Color = isMentionToMe ? .orange : baseColor
|
||||
mentionStyle.foregroundColor = mentionColor
|
||||
// Emit '@'
|
||||
@@ -3530,8 +3530,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
}()
|
||||
var tagStyle = AttributeContainer()
|
||||
tagStyle.font = isSelf
|
||||
? .system(size: 14, weight: .bold, design: .monospaced)
|
||||
: .system(size: 14, design: .monospaced)
|
||||
? .bitchatSystem(size: 14, weight: .bold, design: .monospaced)
|
||||
: .bitchatSystem(size: 14, design: .monospaced)
|
||||
tagStyle.foregroundColor = baseColor
|
||||
if isGeohash && !attachedToMention && standalone, let url = URL(string: "bitchat://geohash/\(token)") {
|
||||
tagStyle.link = url
|
||||
@@ -3544,21 +3544,21 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
var spacer = AttributeContainer()
|
||||
spacer.foregroundColor = baseColor
|
||||
spacer.font = isSelf
|
||||
? .system(size: 14, weight: .bold, design: .monospaced)
|
||||
: .system(size: 14, design: .monospaced)
|
||||
? .bitchatSystem(size: 14, weight: .bold, design: .monospaced)
|
||||
: .bitchatSystem(size: 14, design: .monospaced)
|
||||
result.append(AttributedString(" ").mergingAttributes(spacer))
|
||||
} else if type == "lightning" || type == "bolt11" || type == "lnurl" {
|
||||
// Skip inline invoice/link; a styled chip is rendered below the message
|
||||
var spacer = AttributeContainer()
|
||||
spacer.foregroundColor = baseColor
|
||||
spacer.font = isSelf
|
||||
? .system(size: 14, weight: .bold, design: .monospaced)
|
||||
: .system(size: 14, design: .monospaced)
|
||||
? .bitchatSystem(size: 14, weight: .bold, design: .monospaced)
|
||||
: .bitchatSystem(size: 14, design: .monospaced)
|
||||
result.append(AttributedString(" ").mergingAttributes(spacer))
|
||||
} else {
|
||||
// Keep URL styling and make it tappable via .link attribute
|
||||
var matchStyle = AttributeContainer()
|
||||
matchStyle.font = .system(size: 14, weight: isSelf ? .bold : .semibold, design: .monospaced)
|
||||
matchStyle.font = .bitchatSystem(size: 14, weight: isSelf ? .bold : .semibold, design: .monospaced)
|
||||
if type == "url" {
|
||||
matchStyle.foregroundColor = isSelf ? .orange : .blue
|
||||
matchStyle.underlineStyle = .single
|
||||
@@ -3582,8 +3582,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
var remainingStyle = AttributeContainer()
|
||||
remainingStyle.foregroundColor = baseColor
|
||||
remainingStyle.font = isSelf
|
||||
? .system(size: 14, weight: .bold, design: .monospaced)
|
||||
: .system(size: 14, design: .monospaced)
|
||||
? .bitchatSystem(size: 14, weight: .bold, design: .monospaced)
|
||||
: .bitchatSystem(size: 14, design: .monospaced)
|
||||
if isMentioned {
|
||||
remainingStyle.font = remainingStyle.font?.bold()
|
||||
}
|
||||
@@ -3595,21 +3595,21 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
let timestamp = AttributedString(" [\(message.formattedTimestamp)]")
|
||||
var timestampStyle = AttributeContainer()
|
||||
timestampStyle.foregroundColor = Color.gray.opacity(0.7)
|
||||
timestampStyle.font = .system(size: 10, design: .monospaced)
|
||||
timestampStyle.font = .bitchatSystem(size: 10, design: .monospaced)
|
||||
result.append(timestamp.mergingAttributes(timestampStyle))
|
||||
} else {
|
||||
// System message
|
||||
var contentStyle = AttributeContainer()
|
||||
contentStyle.foregroundColor = Color.gray
|
||||
let content = AttributedString("* \(message.content) *")
|
||||
contentStyle.font = .system(size: 12, design: .monospaced).italic()
|
||||
contentStyle.font = .bitchatSystem(size: 12, design: .monospaced).italic()
|
||||
result.append(content.mergingAttributes(contentStyle))
|
||||
|
||||
// Add timestamp at the end for system messages too
|
||||
let timestamp = AttributedString(" [\(message.formattedTimestamp)]")
|
||||
var timestampStyle = AttributeContainer()
|
||||
timestampStyle.foregroundColor = Color.gray.opacity(0.5)
|
||||
timestampStyle.font = .system(size: 10, design: .monospaced)
|
||||
timestampStyle.font = .bitchatSystem(size: 10, design: .monospaced)
|
||||
result.append(timestamp.mergingAttributes(timestampStyle))
|
||||
}
|
||||
|
||||
@@ -3629,14 +3629,14 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
let content = AttributedString("* \(message.content) *")
|
||||
var contentStyle = AttributeContainer()
|
||||
contentStyle.foregroundColor = Color.gray
|
||||
contentStyle.font = .system(size: 12, design: .monospaced).italic()
|
||||
contentStyle.font = .bitchatSystem(size: 12, design: .monospaced).italic()
|
||||
result.append(content.mergingAttributes(contentStyle))
|
||||
|
||||
// Add timestamp at the end for system messages
|
||||
let timestamp = AttributedString(" [\(message.formattedTimestamp)]")
|
||||
var timestampStyle = AttributeContainer()
|
||||
timestampStyle.foregroundColor = Color.gray.opacity(0.5)
|
||||
timestampStyle.font = .system(size: 10, design: .monospaced)
|
||||
timestampStyle.font = .bitchatSystem(size: 10, design: .monospaced)
|
||||
result.append(timestamp.mergingAttributes(timestampStyle))
|
||||
} else {
|
||||
let sender = AttributedString("<@\(message.sender)> ")
|
||||
@@ -3646,7 +3646,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
senderStyle.foregroundColor = primaryColor
|
||||
// Bold the user's own nickname
|
||||
let fontWeight: Font.Weight = message.sender == nickname ? .bold : .medium
|
||||
senderStyle.font = .system(size: 12, weight: fontWeight, design: .monospaced)
|
||||
senderStyle.font = .bitchatSystem(size: 12, weight: fontWeight, design: .monospaced)
|
||||
result.append(sender.mergingAttributes(senderStyle))
|
||||
|
||||
|
||||
@@ -3670,7 +3670,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
let beforeText = String(contentText[lastEndIndex..<range.lowerBound])
|
||||
if !beforeText.isEmpty {
|
||||
var normalStyle = AttributeContainer()
|
||||
normalStyle.font = .system(size: 14, design: .monospaced)
|
||||
normalStyle.font = .bitchatSystem(size: 14, design: .monospaced)
|
||||
normalStyle.foregroundColor = isDark ? Color.white : Color.black
|
||||
processedContent.append(AttributedString(beforeText).mergingAttributes(normalStyle))
|
||||
}
|
||||
@@ -3679,7 +3679,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
// Add the mention with highlight
|
||||
let mentionText = String(contentText[range])
|
||||
var mentionStyle = AttributeContainer()
|
||||
mentionStyle.font = .system(size: 14, weight: .semibold, design: .monospaced)
|
||||
mentionStyle.font = .bitchatSystem(size: 14, weight: .semibold, design: .monospaced)
|
||||
mentionStyle.foregroundColor = Color.orange
|
||||
processedContent.append(AttributedString(mentionText).mergingAttributes(mentionStyle))
|
||||
|
||||
@@ -3691,7 +3691,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
if lastEndIndex < contentText.endIndex {
|
||||
let remainingText = String(contentText[lastEndIndex...])
|
||||
var normalStyle = AttributeContainer()
|
||||
normalStyle.font = .system(size: 14, design: .monospaced)
|
||||
normalStyle.font = .bitchatSystem(size: 14, design: .monospaced)
|
||||
normalStyle.foregroundColor = isDark ? Color.white : Color.black
|
||||
processedContent.append(AttributedString(remainingText).mergingAttributes(normalStyle))
|
||||
}
|
||||
@@ -3702,7 +3702,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
let relay = AttributedString(" (via \(originalSender))")
|
||||
var relayStyle = AttributeContainer()
|
||||
relayStyle.foregroundColor = primaryColor.opacity(0.7)
|
||||
relayStyle.font = .system(size: 11, design: .monospaced)
|
||||
relayStyle.font = .bitchatSystem(size: 11, design: .monospaced)
|
||||
result.append(relay.mergingAttributes(relayStyle))
|
||||
}
|
||||
|
||||
@@ -3710,7 +3710,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
|
||||
let timestamp = AttributedString(" [\(message.formattedTimestamp)]")
|
||||
var timestampStyle = AttributeContainer()
|
||||
timestampStyle.foregroundColor = Color.gray.opacity(0.7)
|
||||
timestampStyle.font = .system(size: 10, design: .monospaced)
|
||||
timestampStyle.font = .bitchatSystem(size: 10, design: .monospaced)
|
||||
result.append(timestamp.mergingAttributes(timestampStyle))
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ struct AppInfoView: View {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 13, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
@@ -106,11 +106,11 @@ struct AppInfoView: View {
|
||||
// Header
|
||||
VStack(alignment: .center, spacing: 8) {
|
||||
Text(Strings.appName)
|
||||
.font(.system(size: 32, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 32, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
|
||||
Text(Strings.tagline)
|
||||
.font(.system(size: 16, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, design: .monospaced))
|
||||
.foregroundColor(secondaryTextColor)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -125,7 +125,7 @@ struct AppInfoView: View {
|
||||
Text(instruction)
|
||||
}
|
||||
}
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ struct AppInfoView: View {
|
||||
.foregroundColor(Color.red)
|
||||
|
||||
Text(Strings.Warning.message)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(Color.red)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
@@ -211,7 +211,7 @@ struct SectionHeader: View {
|
||||
|
||||
var body: some View {
|
||||
Text(title)
|
||||
.font(.system(size: 16, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
@@ -234,17 +234,17 @@ struct FeatureRow: View {
|
||||
var body: some View {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 20))
|
||||
.font(.bitchatSystem(size: 20))
|
||||
.foregroundColor(textColor)
|
||||
.frame(width: 30)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.font(.system(size: 14, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
|
||||
Text(description)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(secondaryTextColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
@@ -254,6 +254,16 @@ struct FeatureRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
#Preview("Default") {
|
||||
AppInfoView()
|
||||
}
|
||||
|
||||
#Preview("Dynamic Type XXL") {
|
||||
AppInfoView()
|
||||
.environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge)
|
||||
}
|
||||
|
||||
#Preview("Dynamic Type XS") {
|
||||
AppInfoView()
|
||||
.environment(\.sizeCategory, .extraSmall)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ struct ContentView: View {
|
||||
@State private var textFieldSelection: NSRange? = nil
|
||||
@FocusState private var isTextFieldFocused: Bool
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
||||
@State private var showPeerList = false
|
||||
@State private var showSidebar = false
|
||||
@State private var sidebarDragOffset: CGFloat = 0
|
||||
@@ -53,6 +54,7 @@ struct ContentView: View {
|
||||
@State private var showLocationNotes = false
|
||||
@State private var notesGeohash: String? = nil
|
||||
@State private var sheetNotesCount: Int = 0
|
||||
@ScaledMetric(relativeTo: .body) private var headerHeight: CGFloat = 44
|
||||
// Timer-based refresh removed; use LocationChannelManager live updates instead
|
||||
// Window sizes for rendering (infinite scroll up)
|
||||
@State private var windowCountPublic: Int = 300
|
||||
@@ -63,14 +65,18 @@ struct ContentView: View {
|
||||
private var backgroundColor: Color {
|
||||
colorScheme == .dark ? Color.black : Color.white
|
||||
}
|
||||
|
||||
|
||||
private var textColor: Color {
|
||||
colorScheme == .dark ? Color.green : Color(red: 0, green: 0.5, blue: 0)
|
||||
}
|
||||
|
||||
|
||||
private var secondaryTextColor: Color {
|
||||
colorScheme == .dark ? Color.green.opacity(0.8) : Color(red: 0, green: 0.5, blue: 0).opacity(0.8)
|
||||
}
|
||||
|
||||
private var headerLineLimit: Int? {
|
||||
dynamicTypeSize.isAccessibilitySize ? 2 : 1
|
||||
}
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
@@ -328,7 +334,7 @@ struct ContentView: View {
|
||||
if isExpanded { expandedMessageIDs.remove(message.id) }
|
||||
else { expandedMessageIDs.insert(message.id) }
|
||||
}
|
||||
.font(.system(size: 11, weight: .medium, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, weight: .medium, design: .monospaced))
|
||||
.foregroundColor(Color.blue)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
@@ -706,7 +712,7 @@ struct ContentView: View {
|
||||
}) {
|
||||
HStack {
|
||||
Text(suggestion)
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.fontWeight(.medium)
|
||||
Spacer()
|
||||
@@ -764,14 +770,14 @@ struct ContentView: View {
|
||||
HStack {
|
||||
// Show all aliases together
|
||||
Text(info.commands.joined(separator: ", "))
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.fontWeight(.medium)
|
||||
|
||||
// Show syntax if any
|
||||
if let syntax = info.syntax {
|
||||
Text(syntax)
|
||||
.font(.system(size: 10, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 10, design: .monospaced))
|
||||
.foregroundColor(secondaryTextColor.opacity(0.8))
|
||||
}
|
||||
|
||||
@@ -779,7 +785,7 @@ struct ContentView: View {
|
||||
|
||||
// Show description
|
||||
Text(info.description)
|
||||
.font(.system(size: 10, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 10, design: .monospaced))
|
||||
.foregroundColor(secondaryTextColor)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
@@ -802,7 +808,7 @@ struct ContentView: View {
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
TextField("type a message...", text: $messageText)
|
||||
.textFieldStyle(.plain)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.focused($isTextFieldFocused)
|
||||
.padding(.leading, 12)
|
||||
@@ -877,7 +883,7 @@ struct ContentView: View {
|
||||
|
||||
Button(action: sendMessage) {
|
||||
Image(systemName: "arrow.up.circle.fill")
|
||||
.font(.system(size: 20))
|
||||
.font(.bitchatSystem(size: 20))
|
||||
.foregroundColor(messageText.isEmpty ? Color.gray :
|
||||
viewModel.selectedPrivateChatPeer != nil
|
||||
? Color.orange : textColor)
|
||||
@@ -918,20 +924,20 @@ struct ContentView: View {
|
||||
// Header - match main toolbar height
|
||||
HStack {
|
||||
Text("PEOPLE")
|
||||
.font(.system(size: 16, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
Spacer()
|
||||
// Show QR in mesh on all platforms
|
||||
if case .mesh = locationManager.selectedChannel {
|
||||
Button(action: { showVerifySheet = true }) {
|
||||
Image(systemName: "qrcode")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help("Verification: show my QR or scan a friend")
|
||||
}
|
||||
}
|
||||
.frame(height: 44) // Match header height
|
||||
.frame(height: headerHeight) // Match header height
|
||||
.padding(.horizontal, 12)
|
||||
.background(backgroundColor.opacity(0.95))
|
||||
|
||||
@@ -1082,7 +1088,7 @@ struct ContentView: View {
|
||||
private var mainHeaderView: some View {
|
||||
HStack(spacing: 0) {
|
||||
Text("bitchat/")
|
||||
.font(.system(size: 18, weight: .medium, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 18, weight: .medium, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.onTapGesture(count: 3) {
|
||||
// PANIC: Triple-tap to clear all data
|
||||
@@ -1095,12 +1101,12 @@ struct ContentView: View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("@")
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(secondaryTextColor)
|
||||
|
||||
TextField("nickname", text: $viewModel.nickname)
|
||||
.textFieldStyle(.plain)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.frame(maxWidth: 80)
|
||||
.foregroundColor(textColor)
|
||||
.focused($isNicknameFieldFocused)
|
||||
@@ -1139,7 +1145,7 @@ struct ContentView: View {
|
||||
if viewModel.hasAnyUnreadMessages {
|
||||
Button(action: { viewModel.openMostRelevantPrivateChat() }) {
|
||||
Image(systemName: "envelope.fill")
|
||||
.font(.system(size: 12))
|
||||
.font(.bitchatSystem(size: 12))
|
||||
.foregroundColor(Color.orange)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@@ -1159,7 +1165,7 @@ struct ContentView: View {
|
||||
let currentCount = (notesCounter.count ?? 0)
|
||||
let hasNotes = (!notesCounter.initialLoadComplete ? max(currentCount, sheetNotesCount) : currentCount) > 0
|
||||
Image(systemName: "long.text.page.and.pencil")
|
||||
.font(.system(size: 12))
|
||||
.font(.bitchatSystem(size: 12))
|
||||
.foregroundColor(hasNotes ? textColor : Color.gray)
|
||||
.padding(.top, 1)
|
||||
}
|
||||
@@ -1173,7 +1179,7 @@ struct ContentView: View {
|
||||
if case .location(let ch) = locationManager.selectedChannel {
|
||||
Button(action: { GeohashBookmarksStore.shared.toggle(ch.geohash) }) {
|
||||
Image(systemName: GeohashBookmarksStore.shared.isBookmarked(ch.geohash) ? "bookmark.fill" : "bookmark")
|
||||
.font(.system(size: 12))
|
||||
.font(.bitchatSystem(size: 12))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("Toggle bookmark for #\(ch.geohash)")
|
||||
@@ -1196,9 +1202,9 @@ struct ContentView: View {
|
||||
}
|
||||
}()
|
||||
Text(badgeText)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(badgeColor)
|
||||
.lineLimit(1)
|
||||
.lineLimit(headerLineLimit)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
.layoutPriority(2)
|
||||
.accessibilityLabel("location channels")
|
||||
@@ -1210,15 +1216,15 @@ struct ContentView: View {
|
||||
HStack(spacing: 4) {
|
||||
// People icon with count
|
||||
Image(systemName: "person.2.fill")
|
||||
.font(.system(size: 11))
|
||||
.font(.bitchatSystem(size: 11))
|
||||
.accessibilityLabel("\(headerOtherPeersCount) people")
|
||||
Text("\(headerOtherPeersCount)")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
.foregroundColor(headerCountColor)
|
||||
.padding(.leading, 2)
|
||||
.lineLimit(1)
|
||||
.lineLimit(headerLineLimit)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
|
||||
// QR moved to the PEOPLE header in the sidebar when on mesh channel
|
||||
@@ -1235,7 +1241,7 @@ struct ContentView: View {
|
||||
.environmentObject(viewModel)
|
||||
}
|
||||
}
|
||||
.frame(height: 44)
|
||||
.frame(height: headerHeight)
|
||||
.padding(.horizontal, 12)
|
||||
.sheet(isPresented: $showLocationChannelsSheet) {
|
||||
LocationChannelsSheet(isPresented: $showLocationChannelsSheet)
|
||||
@@ -1251,22 +1257,22 @@ struct ContentView: View {
|
||||
VStack(spacing: 12) {
|
||||
HStack {
|
||||
Text("notes")
|
||||
.font(.system(size: 16, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
|
||||
Spacer()
|
||||
Button(action: { showLocationNotes = false }) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 13, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("Close")
|
||||
}
|
||||
.frame(height: 44)
|
||||
.frame(height: headerHeight)
|
||||
.padding(.horizontal, 12)
|
||||
.background(backgroundColor.opacity(0.95))
|
||||
Text("location unavailable")
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(secondaryTextColor)
|
||||
Button("enable location") {
|
||||
LocationChannelManager.shared.enableLocationChannels()
|
||||
@@ -1417,19 +1423,19 @@ struct ContentView: View {
|
||||
case .bluetoothConnected:
|
||||
// Radio icon for mesh connection
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(textColor)
|
||||
.accessibilityLabel("Connected via mesh")
|
||||
case .meshReachable:
|
||||
// point.3 filled icon for reachable via mesh (not directly connected)
|
||||
Image(systemName: "point.3.filled.connected.trianglepath.dotted")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(textColor)
|
||||
.accessibilityLabel("Reachable via mesh")
|
||||
case .nostrAvailable:
|
||||
// Purple globe for Nostr
|
||||
Image(systemName: "globe")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(.purple)
|
||||
.accessibilityLabel("Available via Nostr")
|
||||
case .offline:
|
||||
@@ -1439,25 +1445,25 @@ struct ContentView: View {
|
||||
} else if viewModel.meshService.isPeerReachable(headerPeerID) {
|
||||
// Fallback: reachable via mesh but not in current peer list
|
||||
Image(systemName: "point.3.filled.connected.trianglepath.dotted")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(textColor)
|
||||
.accessibilityLabel("Reachable via mesh")
|
||||
} else if isNostrAvailable {
|
||||
// Fallback to Nostr if peer not in list but is mutual favorite
|
||||
Image(systemName: "globe")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(.purple)
|
||||
.accessibilityLabel("Available via Nostr")
|
||||
} else if viewModel.meshService.isPeerConnected(headerPeerID) || viewModel.connectedPeers.contains(headerPeerID) {
|
||||
// Fallback: if peer lookup is missing but mesh reports connected, show radio
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(textColor)
|
||||
.accessibilityLabel("Connected via mesh")
|
||||
}
|
||||
|
||||
Text("\(privatePeerNick)")
|
||||
.font(.system(size: 16, weight: .medium, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, weight: .medium, design: .monospaced))
|
||||
.foregroundColor(textColor) // Dynamic encryption status icon (hide for geohash DMs)
|
||||
if !privatePeerID.hasPrefix("nostr_") {
|
||||
// Use short peer ID if available for encryption status (sessions keyed by short ID)
|
||||
@@ -1470,7 +1476,7 @@ struct ContentView: View {
|
||||
let encryptionStatus = viewModel.getEncryptionStatus(for: statusPeerID)
|
||||
if let icon = encryptionStatus.icon {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(encryptionStatus == .noiseVerified ? textColor :
|
||||
encryptionStatus == .noiseSecured ? textColor :
|
||||
Color.red)
|
||||
@@ -1492,7 +1498,7 @@ struct ContentView: View {
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 12))
|
||||
.font(.bitchatSystem(size: 12))
|
||||
.foregroundColor(textColor)
|
||||
.frame(width: 44, height: 44, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
@@ -1508,7 +1514,7 @@ struct ContentView: View {
|
||||
viewModel.toggleFavorite(peerID: headerPeerID)
|
||||
}) {
|
||||
Image(systemName: viewModel.isFavorite(peerID: headerPeerID) ? "star.fill" : "star")
|
||||
.font(.system(size: 16))
|
||||
.font(.bitchatSystem(size: 16))
|
||||
.foregroundColor(viewModel.isFavorite(peerID: headerPeerID) ? Color.yellow : textColor)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@@ -1517,7 +1523,7 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 44)
|
||||
.frame(height: headerHeight)
|
||||
.padding(.horizontal, 12)
|
||||
.background(backgroundColor.opacity(0.95))
|
||||
}
|
||||
@@ -1574,7 +1580,7 @@ private struct PaymentChipView: View {
|
||||
HStack(spacing: 6) {
|
||||
Text(emoji)
|
||||
Text(label)
|
||||
.font(.system(size: 12, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, weight: .semibold, design: .monospaced))
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
.padding(.horizontal, 12)
|
||||
@@ -1615,20 +1621,20 @@ struct DeliveryStatusView: View {
|
||||
switch status {
|
||||
case .sending:
|
||||
Image(systemName: "circle")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(secondaryTextColor.opacity(0.6))
|
||||
|
||||
case .sent:
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(secondaryTextColor.opacity(0.6))
|
||||
|
||||
case .delivered(let nickname, _):
|
||||
HStack(spacing: -2) {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
}
|
||||
.foregroundColor(textColor.opacity(0.8))
|
||||
.help("Delivered to \(nickname)")
|
||||
@@ -1636,25 +1642,25 @@ struct DeliveryStatusView: View {
|
||||
case .read(let nickname, _):
|
||||
HStack(spacing: -2) {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 10, weight: .bold))
|
||||
.font(.bitchatSystem(size: 10, weight: .bold))
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 10, weight: .bold))
|
||||
.font(.bitchatSystem(size: 10, weight: .bold))
|
||||
}
|
||||
.foregroundColor(Color(red: 0.0, green: 0.478, blue: 1.0)) // Bright blue
|
||||
.help("Read by \(nickname)")
|
||||
|
||||
case .failed(let reason):
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(Color.red.opacity(0.8))
|
||||
.help("Failed: \(reason)")
|
||||
|
||||
case .partiallyDelivered(let reached, let total):
|
||||
HStack(spacing: 1) {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
Text("\(reached)/\(total)")
|
||||
.font(.system(size: 10, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 10, design: .monospaced))
|
||||
}
|
||||
.foregroundColor(secondaryTextColor.opacity(0.6))
|
||||
.help("Delivered to \(reached) of \(total) members")
|
||||
|
||||
@@ -27,14 +27,14 @@ struct FingerprintView: View {
|
||||
// Header
|
||||
HStack {
|
||||
Text("SECURITY VERIFICATION")
|
||||
.font(.system(size: 16, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.font(.bitchatSystem(size: 14, weight: .semibold))
|
||||
}
|
||||
.foregroundColor(textColor)
|
||||
}
|
||||
@@ -66,17 +66,17 @@ struct FingerprintView: View {
|
||||
HStack {
|
||||
if let icon = encryptionStatus.icon {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 20))
|
||||
.font(.bitchatSystem(size: 20))
|
||||
.foregroundColor(encryptionStatus == .noiseVerified ? Color.green : textColor)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(peerNickname)
|
||||
.font(.system(size: 18, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 18, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
|
||||
Text(encryptionStatus.description)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(textColor.opacity(0.7))
|
||||
}
|
||||
|
||||
@@ -89,12 +89,12 @@ struct FingerprintView: View {
|
||||
// Their fingerprint
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("THEIR FINGERPRINT:")
|
||||
.font(.system(size: 12, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(textColor.opacity(0.7))
|
||||
|
||||
if let fingerprint = viewModel.getFingerprint(for: statusPeerID) {
|
||||
Text(formatFingerprint(fingerprint))
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(nil)
|
||||
@@ -115,7 +115,7 @@ struct FingerprintView: View {
|
||||
}
|
||||
} else {
|
||||
Text("not available - handshake in progress")
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(Color.orange)
|
||||
.padding()
|
||||
}
|
||||
@@ -124,12 +124,12 @@ struct FingerprintView: View {
|
||||
// My fingerprint
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("YOUR FINGERPRINT:")
|
||||
.font(.system(size: 12, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(textColor.opacity(0.7))
|
||||
|
||||
let myFingerprint = viewModel.getMyFingerprint()
|
||||
Text(formatFingerprint(myFingerprint))
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(nil)
|
||||
@@ -156,14 +156,14 @@ struct FingerprintView: View {
|
||||
|
||||
VStack(spacing: 12) {
|
||||
Text(isVerified ? "✓ VERIFIED" : "⚠️ NOT VERIFIED")
|
||||
.font(.system(size: 14, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(isVerified ? Color.green : Color.orange)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Text(isVerified ?
|
||||
"you have verified this person's identity." :
|
||||
"compare these fingerprints with \(peerNickname) using a secure channel.")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(textColor.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(nil)
|
||||
@@ -176,7 +176,7 @@ struct FingerprintView: View {
|
||||
dismiss()
|
||||
}) {
|
||||
Text("MARK AS VERIFIED")
|
||||
.font(.system(size: 14, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
@@ -190,7 +190,7 @@ struct FingerprintView: View {
|
||||
dismiss()
|
||||
}) {
|
||||
Text("REMOVE VERIFICATION")
|
||||
.font(.system(size: 14, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 10)
|
||||
|
||||
@@ -12,7 +12,7 @@ struct GeohashPeopleList: View {
|
||||
if viewModel.visibleGeohashPeople().isEmpty {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("nobody around...")
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(secondaryTextColor)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 12)
|
||||
@@ -51,30 +51,30 @@ struct GeohashPeopleList: View {
|
||||
let icon = teleported ? "face.dashed" : "mappin.and.ellipse"
|
||||
let assignedColor = viewModel.colorForNostrPubkey(person.id, isDark: colorScheme == .dark)
|
||||
let rowColor: Color = isMe ? .orange : assignedColor
|
||||
Image(systemName: icon).font(.system(size: 12)).foregroundColor(rowColor)
|
||||
Image(systemName: icon).font(.bitchatSystem(size: 12)).foregroundColor(rowColor)
|
||||
|
||||
let (base, suffix) = splitSuffix(from: person.displayName)
|
||||
HStack(spacing: 0) {
|
||||
Text(base)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.fontWeight(isMe ? .bold : .regular)
|
||||
.foregroundColor(rowColor)
|
||||
if !suffix.isEmpty {
|
||||
let suffixColor = isMe ? Color.orange.opacity(0.6) : rowColor.opacity(0.6)
|
||||
Text(suffix)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(suffixColor)
|
||||
}
|
||||
if isMe {
|
||||
Text(" (you)")
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(rowColor)
|
||||
}
|
||||
}
|
||||
if let me = myHex, person.id != me {
|
||||
if viewModel.isGeohashUserBlocked(pubkeyHexLowercased: person.id) {
|
||||
Image(systemName: "nosign")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(.red)
|
||||
.help("Blocked in geochash")
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ struct LocationChannelsSheet: View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
Text("#location channels")
|
||||
.font(.system(size: 18, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 18, design: .monospaced))
|
||||
Spacer()
|
||||
closeButton
|
||||
}
|
||||
Text("chat with people near you using geohash channels. only a coarse geohash is shared, never exact gps. your IP address is hidden by routing all traffic over tor.")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Group {
|
||||
@@ -35,7 +35,7 @@ struct LocationChannelsSheet: View {
|
||||
case LocationChannelManager.PermissionState.notDetermined:
|
||||
Button(action: { manager.enableLocationChannels() }) {
|
||||
Text("get location and my geohashes")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(standardGreen)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 6)
|
||||
@@ -46,7 +46,7 @@ struct LocationChannelsSheet: View {
|
||||
case LocationChannelManager.PermissionState.denied, LocationChannelManager.PermissionState.restricted:
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("location permission denied. enable in settings to use location channels.")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
Button("open settings") { openSystemLocationSettings() }
|
||||
.buttonStyle(.plain)
|
||||
@@ -99,7 +99,7 @@ struct LocationChannelsSheet: View {
|
||||
private var closeButton: some View {
|
||||
Button(action: { isPresented = false }) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 13, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@@ -132,7 +132,7 @@ struct LocationChannelsSheet: View {
|
||||
trailingAccessory: {
|
||||
Button(action: { bookmarks.toggle(channel.geohash) }) {
|
||||
Image(systemName: bookmarks.isBookmarked(channel.geohash) ? "bookmark.fill" : "bookmark")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.leading, 8)
|
||||
@@ -149,7 +149,7 @@ struct LocationChannelsSheet: View {
|
||||
HStack {
|
||||
ProgressView()
|
||||
Text("finding nearby channels…")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
}
|
||||
.listRowInsets(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0))
|
||||
}
|
||||
@@ -158,7 +158,7 @@ struct LocationChannelsSheet: View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack(spacing: 2) {
|
||||
Text("#")
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
TextField("geohash", text: $customGeohash)
|
||||
#if os(iOS)
|
||||
@@ -166,7 +166,7 @@ struct LocationChannelsSheet: View {
|
||||
.autocorrectionDisabled(true)
|
||||
.keyboardType(.asciiCapable)
|
||||
#endif
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.onChange(of: customGeohash) { newValue in
|
||||
// Allow only geohash base32 characters, strip '#', limit length
|
||||
let allowed = Set("0123456789bcdefghjkmnpqrstuvwxyz")
|
||||
@@ -194,13 +194,13 @@ struct LocationChannelsSheet: View {
|
||||
}) {
|
||||
HStack(spacing: 6) {
|
||||
Text("teleport")
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
Image(systemName: "face.dashed")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.padding(.vertical, 6)
|
||||
.background(Color.secondary.opacity(0.12))
|
||||
.cornerRadius(6)
|
||||
@@ -209,7 +209,7 @@ struct LocationChannelsSheet: View {
|
||||
}
|
||||
if let err = customError {
|
||||
Text(err)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
@@ -219,7 +219,7 @@ struct LocationChannelsSheet: View {
|
||||
if !bookmarks.bookmarks.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("bookmarked")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
VStack(spacing: 6) {
|
||||
ForEach(bookmarks.bookmarks, id: \.self) { gh in
|
||||
@@ -236,7 +236,7 @@ struct LocationChannelsSheet: View {
|
||||
trailingAccessory: {
|
||||
Button(action: { bookmarks.toggle(gh) }) {
|
||||
Image(systemName: bookmarks.isBookmarked(gh) ? "bookmark.fill" : "bookmark")
|
||||
.font(.system(size: 14))
|
||||
.font(.bitchatSystem(size: 14))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.leading, 8)
|
||||
@@ -271,7 +271,7 @@ struct LocationChannelsSheet: View {
|
||||
openSystemLocationSettings()
|
||||
}) {
|
||||
Text("remove location access")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(Color(red: 0.75, green: 0.1, blue: 0.1))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 6)
|
||||
@@ -319,12 +319,12 @@ struct LocationChannelsSheet: View {
|
||||
let parts = splitTitleAndCount(title)
|
||||
HStack(spacing: 4) {
|
||||
Text(parts.base)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.fontWeight(titleBold ? .bold : .regular)
|
||||
.foregroundColor(titleColor ?? Color.primary)
|
||||
if let count = parts.countSuffix, !count.isEmpty {
|
||||
Text(count)
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,7 @@ struct LocationChannelsSheet: View {
|
||||
return subtitlePrefix
|
||||
}()
|
||||
Text(subtitleFull)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
@@ -343,7 +343,7 @@ struct LocationChannelsSheet: View {
|
||||
Spacer()
|
||||
if isSelected {
|
||||
Text("✔︎")
|
||||
.font(.system(size: 16, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, design: .monospaced))
|
||||
.foregroundColor(standardGreen)
|
||||
}
|
||||
trailingAccessory()
|
||||
@@ -425,10 +425,10 @@ extension LocationChannelsSheet {
|
||||
Toggle(isOn: torToggleBinding) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("tor routing")
|
||||
.font(.system(size: 12, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(.primary)
|
||||
Text("hides your ip for location channels. recommended: on.")
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
@@ -459,7 +459,7 @@ private struct IRCToggleStyle: ToggleStyle {
|
||||
Spacer()
|
||||
Text(configuration.isOn ? "on" : "off")
|
||||
.textCase(.uppercase)
|
||||
.font(.system(size: 12, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, weight: .semibold, design: .monospaced))
|
||||
.foregroundColor(configuration.isOn ? accent : .secondary)
|
||||
.padding(.vertical, 4)
|
||||
.padding(.horizontal, 10)
|
||||
|
||||
@@ -7,6 +7,7 @@ struct LocationNotesView: View {
|
||||
let onNotesCountChanged: ((Int) -> Void)?
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
||||
@ObservedObject private var locationManager = LocationChannelManager.shared
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var draft: String = ""
|
||||
@@ -20,6 +21,7 @@ struct LocationNotesView: View {
|
||||
|
||||
private var backgroundColor: Color { colorScheme == .dark ? .black : .white }
|
||||
private var accentGreen: Color { colorScheme == .dark ? .green : Color(red: 0, green: 0.5, blue: 0) }
|
||||
private var maxDraftLines: Int { dynamicTypeSize.isAccessibilitySize ? 5 : 3 }
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
@@ -79,7 +81,7 @@ struct LocationNotesView: View {
|
||||
private var closeButton: some View {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 13, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
|
||||
.frame(width: 32, height: 32)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@@ -91,30 +93,30 @@ struct LocationNotesView: View {
|
||||
return VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(spacing: 12) {
|
||||
Text("#\(geohash) • \(count) \(count == 1 ? "note" : "notes")")
|
||||
.font(.system(size: 18, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 18, design: .monospaced))
|
||||
Spacer()
|
||||
closeButton
|
||||
}
|
||||
if let building = locationManager.locationNames[.building], !building.isEmpty {
|
||||
Text(building)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(accentGreen)
|
||||
} else if let block = locationManager.locationNames[.block], !block.isEmpty {
|
||||
Text(block)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(accentGreen)
|
||||
}
|
||||
Text("add short permanent notes to this location for other visitors to find.")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
if manager.state == .loading && !manager.initialLoadComplete {
|
||||
Text("loading recent notes…")
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
} else if manager.state == .noRelays {
|
||||
Text("geo relays unavailable; notes paused")
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
@@ -152,16 +154,16 @@ struct LocationNotesView: View {
|
||||
return VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(spacing: 6) {
|
||||
Text("@\(baseName)")
|
||||
.font(.system(size: 12, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, weight: .semibold, design: .monospaced))
|
||||
if !ts.isEmpty {
|
||||
Text(ts)
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Text(note.content)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
@@ -170,12 +172,12 @@ struct LocationNotesView: View {
|
||||
private var noRelaysRow: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("no geo relays nearby")
|
||||
.font(.system(size: 13, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
|
||||
Text("notes rely on geo relays. check connection and try again.")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
Button("retry") { manager.refresh() }
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
@@ -185,7 +187,7 @@ struct LocationNotesView: View {
|
||||
HStack(spacing: 10) {
|
||||
ProgressView()
|
||||
Text("loading notes…")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
}
|
||||
@@ -195,9 +197,9 @@ struct LocationNotesView: View {
|
||||
private var emptyRow: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("no notes yet")
|
||||
.font(.system(size: 13, weight: .semibold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 13, weight: .semibold, design: .monospaced))
|
||||
Text("be the first to add one for this spot.")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
@@ -207,13 +209,13 @@ struct LocationNotesView: View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
Text(message)
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
Spacer()
|
||||
}
|
||||
Button("dismiss") { manager.clearError() }
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.padding(.vertical, 6)
|
||||
@@ -223,12 +225,12 @@ struct LocationNotesView: View {
|
||||
HStack(alignment: .top, spacing: 10) {
|
||||
TextField("add a note for this place", text: $draft, axis: .vertical)
|
||||
.textFieldStyle(.plain)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.lineLimit(3, reservesSpace: true)
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.lineLimit(maxDraftLines, reservesSpace: true)
|
||||
.padding(.vertical, 6)
|
||||
Button(action: send) {
|
||||
Image(systemName: "arrow.up.circle.fill")
|
||||
.font(.system(size: 20))
|
||||
.font(.bitchatSystem(size: 20))
|
||||
.foregroundColor(sendButtonEnabled ? accentGreen : .secondary)
|
||||
}
|
||||
.padding(.top, 2)
|
||||
|
||||
@@ -15,7 +15,7 @@ struct MeshPeerList: View {
|
||||
if viewModel.allPeers.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("nobody around...")
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(secondaryTextColor)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 12)
|
||||
@@ -45,27 +45,27 @@ struct MeshPeerList: View {
|
||||
let baseColor = isMe ? Color.orange : assigned
|
||||
if isMe {
|
||||
Image(systemName: "person.fill")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(baseColor)
|
||||
} else if peer.isConnected {
|
||||
// Mesh-connected peer: radio icon
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(baseColor)
|
||||
} else if peer.isReachable {
|
||||
// Mesh-reachable (relayed): point.3 icon
|
||||
Image(systemName: "point.3.filled.connected.trianglepath.dotted")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(baseColor)
|
||||
} else if peer.isMutualFavorite {
|
||||
// Mutual favorite reachable via Nostr: globe icon (purple)
|
||||
Image(systemName: "globe")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(.purple)
|
||||
} else {
|
||||
// Fallback icon for others (dimmed)
|
||||
Image(systemName: "person")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(secondaryTextColor)
|
||||
}
|
||||
|
||||
@@ -73,19 +73,19 @@ struct MeshPeerList: View {
|
||||
let (base, suffix) = splitSuffix(from: displayName)
|
||||
HStack(spacing: 0) {
|
||||
Text(base)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(baseColor)
|
||||
if !suffix.isEmpty {
|
||||
let suffixColor = isMe ? Color.orange.opacity(0.6) : baseColor.opacity(0.6)
|
||||
Text(suffix)
|
||||
.font(.system(size: 14, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(suffixColor)
|
||||
}
|
||||
}
|
||||
|
||||
if !isMe, viewModel.isPeerBlocked(peer.id) {
|
||||
Image(systemName: "nosign")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(.red)
|
||||
.help("Blocked")
|
||||
}
|
||||
@@ -94,7 +94,7 @@ struct MeshPeerList: View {
|
||||
if peer.isConnected {
|
||||
if let icon = item.enc.icon {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(baseColor)
|
||||
}
|
||||
} else {
|
||||
@@ -102,12 +102,12 @@ struct MeshPeerList: View {
|
||||
if let fp = viewModel.getFingerprint(for: peer.id),
|
||||
viewModel.verifiedFingerprints.contains(fp) {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(baseColor)
|
||||
} else if let icon = item.enc.icon {
|
||||
// Fallback to whatever status says (likely lock if we had a past session)
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(baseColor)
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ struct MeshPeerList: View {
|
||||
// Unread message indicator for this peer
|
||||
if !isMe, item.hasUnread {
|
||||
Image(systemName: "envelope.fill")
|
||||
.font(.system(size: 10))
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(.orange)
|
||||
.help("New messages")
|
||||
}
|
||||
@@ -126,7 +126,7 @@ struct MeshPeerList: View {
|
||||
if !isMe {
|
||||
Button(action: { onToggleFavorite(peer.id) }) {
|
||||
Image(systemName: (peer.favoriteStatus?.isFavorite ?? false) ? "star.fill" : "star")
|
||||
.font(.system(size: 12))
|
||||
.font(.bitchatSystem(size: 12))
|
||||
.foregroundColor((peer.favoriteStatus?.isFavorite ?? false) ? .yellow : secondaryTextColor)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
@@ -16,7 +16,7 @@ struct MyQRView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
Text("scan to verify me")
|
||||
.font(.system(size: 16, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
|
||||
|
||||
VStack(spacing: 10) {
|
||||
QRCodeImage(data: qrString, size: 240)
|
||||
@@ -24,7 +24,7 @@ struct MyQRView: View {
|
||||
|
||||
// Non-scrolling, fully visible URL (wraps across lines)
|
||||
Text(qrString)
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 11, design: .monospaced))
|
||||
.textSelection(.enabled)
|
||||
.multilineTextAlignment(.leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
@@ -60,7 +60,7 @@ struct QRCodeImage: View {
|
||||
.frame(width: size, height: size)
|
||||
.overlay(
|
||||
Text("qr unavailable")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
.foregroundColor(.gray)
|
||||
)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ struct QRScanView: View {
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
#else
|
||||
Text("paste qr content to validate:")
|
||||
.font(.system(size: 14, weight: .medium, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, weight: .medium, design: .monospaced))
|
||||
TextEditor(text: $input)
|
||||
.frame(height: 100)
|
||||
.border(Color.gray.opacity(0.4))
|
||||
@@ -255,7 +255,7 @@ struct VerificationSheetView: View {
|
||||
// Top header (always at top)
|
||||
HStack {
|
||||
Text("VERIFY")
|
||||
.font(.system(size: 14, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 14, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(accentColor)
|
||||
Spacer()
|
||||
Button(action: {
|
||||
@@ -263,7 +263,7 @@ struct VerificationSheetView: View {
|
||||
isPresented = false
|
||||
}) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.font(.bitchatSystem(size: 14, weight: .semibold))
|
||||
.foregroundColor(accentColor)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@@ -279,7 +279,7 @@ struct VerificationSheetView: View {
|
||||
if showingScanner {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("scan a friend's qr")
|
||||
.font(.system(size: 16, weight: .bold, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 16, weight: .bold, design: .monospaced))
|
||||
.frame(maxWidth: .infinity)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(accentColor)
|
||||
@@ -310,13 +310,13 @@ struct VerificationSheetView: View {
|
||||
if showingScanner {
|
||||
Button(action: { showingScanner = false }) {
|
||||
Label("show my qr", systemImage: "qrcode")
|
||||
.font(.system(size: 13, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 13, design: .monospaced))
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
} else {
|
||||
Button(action: { showingScanner = true }) {
|
||||
Label("scan someone else's qr", systemImage: "camera.viewfinder")
|
||||
.font(.system(size: 13, weight: .medium, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 13, weight: .medium, design: .monospaced))
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.tint(.gray)
|
||||
@@ -328,7 +328,7 @@ struct VerificationSheetView: View {
|
||||
viewModel.verifiedFingerprints.contains(fp) {
|
||||
Button(action: { viewModel.unverifyFingerprint(for: pid) }) {
|
||||
Label("remove verification", systemImage: "minus.circle")
|
||||
.font(.system(size: 12, design: .monospaced))
|
||||
.font(.bitchatSystem(size: 12, design: .monospaced))
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.tint(.gray)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import SwiftUI
|
||||
import XCTest
|
||||
@testable import bitchat
|
||||
|
||||
final class FontBitchatTests: XCTestCase {
|
||||
func testMonospacedMapping() {
|
||||
XCTAssertEqual(Font.bitchatSystem(size: 10, design: .monospaced), Font.system(.caption2, design: .monospaced))
|
||||
XCTAssertEqual(Font.bitchatSystem(size: 14, design: .monospaced), Font.system(.body, design: .monospaced))
|
||||
XCTAssertEqual(Font.bitchatSystem(size: 20, design: .monospaced), Font.system(.title2, design: .monospaced))
|
||||
}
|
||||
|
||||
func testWeightIsPreserved() {
|
||||
let bold = Font.bitchatSystem(size: 14, weight: .bold, design: .monospaced)
|
||||
XCTAssertEqual(bold, Font.system(.body, design: .monospaced).weight(.bold))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user