import Foundation /// Levels of location channels mapped to geohash precisions. enum GeohashChannelLevel: CaseIterable, Codable, Equatable { case building 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 .building: return 8 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 .building: return String(localized: "location_levels.building", comment: "Name for building-level location channel") case .block: return String(localized: "location_levels.block", comment: "Name for block-level location channel") case .neighborhood: return String(localized: "location_levels.neighborhood", comment: "Name for neighborhood-level location channel") case .city: return String(localized: "location_levels.city", comment: "Name for city-level location channel") case .province: return String(localized: "location_levels.province", comment: "Name for province-level location channel") case .region: return String(localized: "location_levels.region", comment: "Name for region-level location channel") } } } // 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 "building": self = .building 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 8: self = .building 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 .building: try container.encode("building") 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 } } var isMesh: Bool { switch self { case .mesh: true case .location: false } } var isLocation: Bool { switch self { case .mesh: false case .location: true } } }