mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 01:25:20 +00:00
Refresh typography and channel sheet styling
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
MARKETING_VERSION = 1.4.2
|
||||
MARKETING_VERSION = 1.4.3
|
||||
CURRENT_PROJECT_VERSION = 1
|
||||
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user