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