From 16fdb7b49e8e554dff4eb7188ce1d19f6dbc45e3 Mon Sep 17 00:00:00 2001 From: jack Date: Fri, 22 Aug 2025 12:34:44 +0200 Subject: [PATCH] Teleport persistence: store per-geohash teleported state in UserDefaults; initialize on startup; OR with location-derived status; sheet writes persisted flag on select/teleport --- bitchat/Services/LocationChannelManager.swift | 36 +++++++++++++++++-- bitchat/Views/LocationChannelsSheet.swift | 6 ++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/bitchat/Services/LocationChannelManager.swift b/bitchat/Services/LocationChannelManager.swift index 5c76ff6a..322a21a8 100644 --- a/bitchat/Services/LocationChannelManager.swift +++ b/bitchat/Services/LocationChannelManager.swift @@ -21,6 +21,7 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa private var lastLocation: CLLocation? private var refreshTimer: Timer? private let userDefaultsKey = "locationChannel.selected" + private let teleportedStoreKey = "locationChannel.teleportedSet" private var isGeocoding: Bool = false // Published state for UI bindings @@ -31,6 +32,9 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa @Published var teleported: Bool = false @Published private(set) var locationNames: [GeohashChannelLevel: String] = [:] + // Persisted set of geohashes that were selected via teleport + private var teleportedSet: Set = [] + private override init() { super.init() cl.delegate = self @@ -41,6 +45,15 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa let channel = try? JSONDecoder().decode(ChannelID.self, from: data) { selectedChannel = channel } + // Load persisted teleported set + if let data = UserDefaults.standard.data(forKey: teleportedStoreKey), + let arr = try? JSONDecoder().decode([String].self, from: data) { + teleportedSet = Set(arr) + } + // Initialize teleported flag from persisted state if a location channel is selected + if case .location(let ch) = selectedChannel { + teleported = teleportedSet.contains(ch.geohash) + } let status: CLAuthorizationStatus if #available(iOS 14.0, *) { status = cl.authorizationStatus @@ -104,6 +117,24 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa if let data = try? JSONEncoder().encode(channel) { UserDefaults.standard.set(data, forKey: self.userDefaultsKey) } + // Update teleported flag based on persisted state for immediate UI behavior + switch channel { + case .mesh: + self.teleported = false + case .location(let ch): + self.teleported = self.teleportedSet.contains(ch.geohash) + } + } + } + + // Mark or unmark a geohash as teleported in persistence and update current flag if relevant + func markTeleported(for geohash: String, _ flag: Bool) { + if flag { teleportedSet.insert(geohash) } else { teleportedSet.remove(geohash) } + if let data = try? JSONEncoder().encode(Array(teleportedSet)) { + UserDefaults.standard.set(data, forKey: teleportedStoreKey) + } + if case .location(let ch) = selectedChannel, ch.geohash == geohash { + Task { @MainActor in self.teleported = flag } } } @@ -164,13 +195,14 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa } Task { @MainActor in self.availableChannels = result - // Recompute teleported status based on current location vs selected channel + // Recompute teleported status based on persisted state OR current location vs selected channel switch self.selectedChannel { case .mesh: self.teleported = false case .location(let ch): + let persisted = self.teleportedSet.contains(ch.geohash) let currentGH = Geohash.encode(latitude: coord.latitude, longitude: coord.longitude, precision: ch.level.precision) - self.teleported = (currentGH != ch.geohash) + self.teleported = persisted || (currentGH != ch.geohash) } } } diff --git a/bitchat/Views/LocationChannelsSheet.swift b/bitchat/Views/LocationChannelsSheet.swift index 84d4fd58..da88c0a2 100644 --- a/bitchat/Views/LocationChannelsSheet.swift +++ b/bitchat/Views/LocationChannelsSheet.swift @@ -107,8 +107,8 @@ struct LocationChannelsSheet: View { let subtitlePrefix = "#\(channel.geohash) • \(coverage)" let highlight = viewModel.geohashParticipantCount(for: channel.geohash) > 0 channelRow(title: geohashTitleWithCount(for: channel), subtitlePrefix: subtitlePrefix, subtitleName: namePart, isSelected: isSelected(channel), titleBold: highlight) { - // Selecting a suggested nearby channel is not a teleport - manager.teleported = false + // Selecting a suggested nearby channel is not a teleport. Persist this. + manager.markTeleported(for: channel.geohash, false) manager.select(ChannelID.location(channel)) isPresented = false } @@ -153,7 +153,7 @@ struct LocationChannelsSheet: View { let level = levelForLength(gh.count) let ch = GeohashChannel(level: level, geohash: gh) // Mark this selection as a manual teleport - manager.teleported = true + manager.markTeleported(for: ch.geohash, true) manager.select(ChannelID.location(ch)) isPresented = false }) {