mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 11:25:20 +00:00
* Remove link previews: link URLs inline\n\n- Delete LinkPreviewView and all references\n- Strip preview usage from ContentView\n- Make detected URLs tappable via .link attribute\n- Add MessageTextHelpers for tokens/length checks\n- Wire helpers into iOS/macOS targets * Peer list UX: match message colors, use map pin icon, stable ordering\n\n- Color list names and icons with same peer color as messages\n- Default icon mappin; teleported uses face.dashed (iOS)\n- Geohash: move teleported peers to bottom\n- Maintain stable order: append new peers, remove missing\n- Mesh list: replace state icons with colored mappin/person * Fix peer list build errors: remove ambiguous Set type, avoid guard in ViewBuilder, remove duplicate bindings, and simplify person lookup * Peer list: remove onChange expressions to avoid type-check issues; rely on ObservedObject updates * Peer lists: simplify view structure to satisfy SwiftUI type checker (replace Group with VStack, flatten body) * Peer lists: move ordering mutations to onAppear/onChange, keep body pure; fix result builder errors * Fix: remove trailing modifiers outside if/else to avoid ViewBuilder type issues * Color parity: fix seed cache to include color scheme; normalize Nostr seeds; switch to mappin.circle icon * Peer colors: ensure exact match with message colors\n\n- Mesh list uses same seed logic as messages (getNoiseKeyForShortID fallback)\n- Color cache keyed by theme; normalize nostr seeds\n- Icons: use mappin.and.ellipse (default) and face.dashed for teleported\n- Teleport tag: accept "teleported" in addition to "teleport" and presence events * Fix geohash color mismatch: populate nostrKeyMapping before building messages in resubscribe; also honor t,teleport tag for teleported state * Message actions: username tap opens sheet via clickable AttributedString; message tap inserts @mention; rename to 'direct message' * Cashu UX: suppress 'Show more' when message contains a Cashu token (chips handle rendering) * Cashu detection: broaden regex (allow '.') + shorter min length; avoid long-text fallback when Cashu present so chips render and no full blob * Geohash levels: rename region->province, country->region; update labels, precision, UI, manager mapping, tests; add Codable backward-compat for renamed cases * Fix braces in LocationChannel.swift: close enum before extension to satisfy file-scope declarations * Geohash links: make #geohash tappable and open that channel (bitchat://geohash/<gh>); handle in ContentView.onOpenURL with validation and selection * Underline tappable #geohash mentions for clarity * Geochat: sanitize timeline on channel switch to remove any blank-content entries (prevents blank rows) * Xcode project: sync sources after UI changes (remove LinkPreview, add helpers, update build refs) --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
108 lines
3.1 KiB
Swift
108 lines
3.1 KiB
Swift
import Foundation
|
|
|
|
/// Levels of location channels mapped to geohash precisions.
|
|
enum GeohashChannelLevel: CaseIterable, Codable, Equatable {
|
|
case block
|
|
case neighborhood
|
|
case city
|
|
case province // previously .region
|
|
case region // previously .country
|
|
|
|
/// Geohash length used for this level.
|
|
var precision: Int {
|
|
switch self {
|
|
case .block: return 7
|
|
case .neighborhood: return 6
|
|
case .city: return 5
|
|
case .province: return 4
|
|
case .region: return 2
|
|
}
|
|
}
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .block: return "Block"
|
|
case .neighborhood: return "Neighborhood"
|
|
case .city: return "City"
|
|
case .province: return "Province"
|
|
case .region: return "Region"
|
|
}
|
|
}
|
|
}
|
|
// Backward-compatible Codable for renamed cases
|
|
extension GeohashChannelLevel {
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.singleValueContainer()
|
|
if let raw = try? container.decode(String.self) {
|
|
switch raw {
|
|
case "block": self = .block
|
|
case "neighborhood": self = .neighborhood
|
|
case "city": self = .city
|
|
case "region": self = .province // old "region" maps to new .province
|
|
case "country": self = .region // old "country" maps to new .region
|
|
case "province": self = .province
|
|
default:
|
|
self = .block
|
|
}
|
|
} else if let precision = try? container.decode(Int.self) {
|
|
switch precision {
|
|
case 7: self = .block
|
|
case 6: self = .neighborhood
|
|
case 5: self = .city
|
|
case 4: self = .province
|
|
case 0...3: self = .region
|
|
default: self = .block
|
|
}
|
|
} else {
|
|
self = .block
|
|
}
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
switch self {
|
|
case .block: try container.encode("block")
|
|
case .neighborhood: try container.encode("neighborhood")
|
|
case .city: try container.encode("city")
|
|
case .province: try container.encode("province")
|
|
case .region: try container.encode("region")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A computed geohash channel option.
|
|
struct GeohashChannel: Codable, Equatable, Hashable, Identifiable {
|
|
let level: GeohashChannelLevel
|
|
let geohash: String
|
|
|
|
var id: String { "\(level)-\(geohash)" }
|
|
|
|
var displayName: String {
|
|
"\(level.displayName) • \(geohash)"
|
|
}
|
|
}
|
|
|
|
/// Identifier for current public chat channel (mesh or a location geohash).
|
|
enum ChannelID: Equatable, Codable {
|
|
case mesh
|
|
case location(GeohashChannel)
|
|
|
|
/// Human readable name for UI.
|
|
var displayName: String {
|
|
switch self {
|
|
case .mesh:
|
|
return "Mesh"
|
|
case .location(let ch):
|
|
return ch.displayName
|
|
}
|
|
}
|
|
|
|
/// Nostr tag value for scoping (geohash), if applicable.
|
|
var nostrGeohashTag: String? {
|
|
switch self {
|
|
case .mesh: return nil
|
|
case .location(let ch): return ch.geohash
|
|
}
|
|
}
|
|
}
|