Refresh typography and channel sheet styling

This commit is contained in:
jack
2025-09-24 12:32:11 +02:00
parent a41ec65f58
commit de906cb97c
4 changed files with 199 additions and 171 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
MARKETING_VERSION = 1.4.2
MARKETING_VERSION = 1.4.3
CURRENT_PROJECT_VERSION = 1
IPHONEOS_DEPLOYMENT_TARGET = 16.0
+9 -5
View File
@@ -20,15 +20,19 @@ private extension Font.TextStyle {
return .caption2
case ..<13.0:
return .caption
case ..<14.0:
case ..<13.75:
return .footnote
case ..<16.5:
case ..<15.5:
return .subheadline
case ..<17.5:
return .callout
case ..<19.5:
return .body
case ..<19.0:
case ..<22.5:
return .title3
case ..<23.0:
case ..<27.5:
return .title2
case ..<30.0:
case ..<34.0:
return .title
default:
return .largeTitle
+4 -2
View File
@@ -55,6 +55,8 @@ struct ContentView: View {
@State private var notesGeohash: String? = nil
@State private var sheetNotesCount: Int = 0
@ScaledMetric(relativeTo: .body) private var headerHeight: CGFloat = 44
@ScaledMetric(relativeTo: .subheadline) private var headerPeerIconSize: CGFloat = 11
@ScaledMetric(relativeTo: .subheadline) private var headerPeerCountFontSize: CGFloat = 12
// Timer-based refresh removed; use LocationChannelManager live updates instead
// Window sizes for rendering (infinite scroll up)
@State private var windowCountPublic: Int = 300
@@ -1216,10 +1218,10 @@ struct ContentView: View {
HStack(spacing: 4) {
// People icon with count
Image(systemName: "person.2.fill")
.font(.bitchatSystem(size: 11))
.font(.system(size: headerPeerIconSize, weight: .regular))
.accessibilityLabel("\(headerOtherPeersCount) people")
Text("\(headerOtherPeersCount)")
.font(.bitchatSystem(size: 12, design: .monospaced))
.font(.system(size: headerPeerCountFontSize, weight: .regular, design: .monospaced))
.accessibilityHidden(true)
}
.foregroundColor(headerCountColor)
+74 -52
View File
@@ -107,17 +107,18 @@ struct LocationChannelsSheet: View {
}
private var channelList: some View {
List {
// Mesh option first (no bookmark)
ScrollView {
LazyVStack(spacing: 0) {
channelRow(title: meshTitleWithCount(), subtitlePrefix: "#bluetooth • \(bluetoothRangeString())", isSelected: isMeshSelected, titleColor: standardBlue, titleBold: meshCount() > 0) {
manager.select(ChannelID.mesh)
isPresented = false
}
.listRowInsets(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0))
.padding(.vertical, 6)
// Nearby options
if !manager.availableChannels.isEmpty {
ForEach(manager.availableChannels.filter { $0.level != .building }) { channel in
let nearby = manager.availableChannels.filter { $0.level != .building }
if !nearby.isEmpty {
ForEach(nearby) { channel in
sectionDivider
let coverage = coverageString(forPrecision: channel.geohash.count)
let nameBase = locationName(for: channel.level)
let namePart = nameBase.map { formattedNamePrefix(for: channel.level) + $0 }
@@ -138,23 +139,71 @@ struct LocationChannelsSheet: View {
.padding(.leading, 8)
}
) {
// Selecting a suggested nearby channel is not a teleport. Persist this.
manager.markTeleported(for: channel.geohash, false)
manager.select(ChannelID.location(channel))
isPresented = false
}
.listRowInsets(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0))
.padding(.vertical, 6)
}
} else {
HStack {
sectionDivider
HStack(spacing: 8) {
ProgressView()
Text("finding nearby channels…")
.font(.bitchatSystem(size: 12, design: .monospaced))
}
.listRowInsets(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 10)
}
// Custom geohash teleport
sectionDivider
customTeleportSection
.padding(.vertical, 8)
let bookmarkedList = bookmarks.bookmarks
if !bookmarkedList.isEmpty {
sectionDivider
bookmarkedSection(bookmarkedList)
.padding(.vertical, 8)
}
if manager.permissionState == LocationChannelManager.PermissionState.authorized {
sectionDivider
torToggleSection
.padding(.top, 12)
Button(action: {
openSystemLocationSettings()
}) {
Text("remove location access")
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(Color(red: 0.75, green: 0.1, blue: 0.1))
.frame(maxWidth: .infinity)
.padding(.vertical, 6)
.background(Color.red.opacity(0.08))
.cornerRadius(6)
}
.buttonStyle(.plain)
.padding(.vertical, 8)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 6)
.background(backgroundColor)
}
.background(backgroundColor)
}
private var sectionDivider: some View {
Rectangle()
.fill(dividerColor)
.frame(height: 1)
}
private var dividerColor: Color {
colorScheme == .dark ? Color.white.opacity(0.12) : Color.black.opacity(0.08)
}
private var customTeleportSection: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 2) {
Text("#")
@@ -168,7 +217,6 @@ struct LocationChannelsSheet: View {
#endif
.font(.bitchatSystem(size: 14, design: .monospaced))
.onChange(of: customGeohash) { newValue in
// Allow only geohash base32 characters, strip '#', limit length
let allowed = Set("0123456789bcdefghjkmnpqrstuvwxyz")
let filtered = newValue
.lowercased()
@@ -180,14 +228,16 @@ struct LocationChannelsSheet: View {
customGeohash = filtered
}
}
let normalized = customGeohash.trimmingCharacters(in: .whitespacesAndNewlines).lowercased().replacingOccurrences(of: "#", with: "")
let normalized = customGeohash
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased()
.replacingOccurrences(of: "#", with: "")
let isValid = validateGeohash(normalized)
Button(action: {
let gh = normalized
guard isValid else { customError = "invalid geohash"; return }
let level = levelForLength(gh.count)
let ch = GeohashChannel(level: level, geohash: gh)
// Mark this selection as a manual teleport
manager.markTeleported(for: ch.geohash, true)
manager.select(ChannelID.location(ch))
isPresented = false
@@ -202,6 +252,7 @@ struct LocationChannelsSheet: View {
.buttonStyle(.plain)
.font(.bitchatSystem(size: 14, design: .monospaced))
.padding(.vertical, 6)
.padding(.horizontal, 10)
.background(Color.secondary.opacity(0.12))
.cornerRadius(6)
.opacity(isValid ? 1.0 : 0.4)
@@ -213,16 +264,15 @@ struct LocationChannelsSheet: View {
.foregroundColor(.red)
}
}
.listRowInsets(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0))
}
// Bookmarked geohashes
if !bookmarks.bookmarks.isEmpty {
private func bookmarkedSection(_ entries: [String]) -> some View {
VStack(alignment: .leading, spacing: 8) {
Text("bookmarked")
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(.secondary)
VStack(spacing: 6) {
ForEach(bookmarks.bookmarks, id: \.self) { gh in
LazyVStack(spacing: 0) {
ForEach(Array(entries.enumerated()), id: \.offset) { index, gh in
let level = levelForLength(gh.count)
let channel = GeohashChannel(level: level, geohash: gh)
let coverage = coverageString(forPrecision: gh.count)
@@ -242,7 +292,6 @@ struct LocationChannelsSheet: View {
.padding(.leading, 8)
}
) {
// For bookmarked selection, mark teleported based on regional membership
let inRegional = manager.availableChannels.contains { $0.geohash == gh }
if !inRegional && !manager.availableChannels.isEmpty {
manager.markTeleported(for: gh, true)
@@ -252,42 +301,17 @@ struct LocationChannelsSheet: View {
manager.select(ChannelID.location(channel))
isPresented = false
}
.padding(.vertical, 6)
.onAppear { bookmarks.resolveNameIfNeeded(for: gh) }
if index < entries.count - 1 {
sectionDivider
}
}
}
.padding(12)
.background(Color.secondary.opacity(0.12))
.cornerRadius(8)
}
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0))
}
// Footer action inside the list
if manager.permissionState == LocationChannelManager.PermissionState.authorized {
torToggleSection
.listRowInsets(EdgeInsets(top: 6, leading: 0, bottom: 4, trailing: 0))
Button(action: {
openSystemLocationSettings()
}) {
Text("remove location access")
.font(.bitchatSystem(size: 12, design: .monospaced))
.foregroundColor(Color(red: 0.75, green: 0.1, blue: 0.1))
.frame(maxWidth: .infinity)
.padding(.vertical, 6)
.background(Color.red.opacity(0.08))
.cornerRadius(6)
}
.buttonStyle(.plain)
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 4, leading: 0, bottom: 10, trailing: 0))
}
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(backgroundColor)
}
private func isSelected(_ channel: GeohashChannel) -> Bool {
if case .location(let ch) = manager.selectedChannel {
@@ -437,8 +461,6 @@ extension LocationChannelsSheet {
.padding(12)
.background(Color.secondary.opacity(0.12))
.cornerRadius(8)
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
}
private var standardGreen: Color {