mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-22 07:16:03 +00:00
* Add a persistent connectivity banner and stop the radar lying Two "the app looks fine while it isn't" problems from the UX audit: - Bluetooth-off had exactly one signal: a dismissible alert. Once dismissed, the app looked completely normal — empty timeline, radar sweeping "searching for people" — while the radio was off. The radar animation now requires a radio that can actually scan, and a persistent red banner under the header carries the state instead: off (tap → settings), denied (tap → settings), or unsupported (mesh unavailable; location channels still work). - A Tor bootstrap stall was only ever announced inside geohash timelines (addGeohashOnlySystemMessage), so someone sitting in #mesh or a DM whose messages route over Nostr got silence and hanging "sent" states. The stall now also drives a chrome-level torBlocked flag and the same banner: "tor can't connect — internet features are paused. mesh still works." The banner renders nothing when everything is healthy, and .unknown/ .resetting radio states deliberately stay quiet — a false "bluetooth is off" flash at launch would be the same kind of lie. Priority and quiet-start behavior are pinned by ConnectivityIssueTests. 4 new strings, all 30 locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Review fixes: banner in the sheet, real fix targets, tor-clear tests - The people/DM sheet covers the root header, so the connectivity banner vanished exactly where people sit longest. It now mirrors into the sheet via a top safe-area inset over both the people list and the DM view. - "tap to fix" for powered-off Bluetooth opened the macOS privacy permission pane, where an already-authorized person can't fix anything. New SystemSettings.bluetoothPower routes to the Bluetooth pane; denied still goes to the privacy anchor. The one-shot alert's settings button routes by state the same way. - Tests pin that torBlocked clears on tor-ready and on preference change (no stale "tor can't connect" after toggling tor off). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
97 lines
3.9 KiB
Swift
97 lines
3.9 KiB
Swift
//
|
|
// ChatViewModel+Tor.swift
|
|
// bitchat
|
|
//
|
|
// Tor lifecycle handling for ChatViewModel
|
|
//
|
|
|
|
import Foundation
|
|
import Combine
|
|
import Tor
|
|
|
|
extension ChatViewModel {
|
|
|
|
// MARK: - Tor notifications
|
|
|
|
@objc func handleTorWillStart() {
|
|
Task { @MainActor in
|
|
// A fresh attempt can stall again, so let it be reported again.
|
|
self.torStallAnnounced = false
|
|
if !self.torStatusAnnounced && TorManager.shared.torEnforced {
|
|
self.torStatusAnnounced = true
|
|
// Post only in geohash channels (queue if not active)
|
|
self.addGeohashOnlySystemMessage(
|
|
String(localized: "system.tor.starting", comment: "System message when Tor is starting")
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func handleTorWillRestart() {
|
|
Task { @MainActor in
|
|
self.torRestartPending = true
|
|
// Post only in geohash channels (queue if not active)
|
|
self.addGeohashOnlySystemMessage(
|
|
String(localized: "system.tor.restarting", comment: "System message when Tor is restarting")
|
|
)
|
|
}
|
|
}
|
|
|
|
@objc func handleTorDidBecomeReady() {
|
|
Task { @MainActor in
|
|
self.torStallAnnounced = false
|
|
self.torBlocked = false
|
|
// Only announce "restarted" if we actually restarted this session
|
|
if self.torRestartPending {
|
|
// Post only in geohash channels (queue if not active)
|
|
self.addGeohashOnlySystemMessage(
|
|
String(localized: "system.tor.restarted", comment: "System message when Tor has restarted")
|
|
)
|
|
self.torRestartPending = false
|
|
} else if TorManager.shared.torEnforced && !self.torInitialReadyAnnounced {
|
|
// Initial start completed
|
|
self.addGeohashOnlySystemMessage(
|
|
String(localized: "system.tor.started", comment: "System message when Tor has started")
|
|
)
|
|
self.torInitialReadyAnnounced = true
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Bootstrap spent its whole deadline without connecting. Say so rather
|
|
/// than leaving "starting tor…" on screen indefinitely: on a network that
|
|
/// blocks Tor this is the terminal state, and someone needs to know that
|
|
/// internet features are stalled while the mesh still works.
|
|
@objc func handleTorBootstrapDidStall() {
|
|
Task { @MainActor in
|
|
guard TorManager.shared.torEnforced else { return }
|
|
// torEnforced is a compile-time constant in release builds; the
|
|
// runtime preference is what says whether anyone is waiting on
|
|
// Tor. Turning Tor off mid-bootstrap must not read as blocking.
|
|
guard NetworkActivationService.persistedTorPreference() else { return }
|
|
self.torBlocked = true
|
|
guard !self.torStallAnnounced else { return }
|
|
self.torStallAnnounced = true
|
|
self.addGeohashOnlySystemMessage(
|
|
String(
|
|
localized: "system.tor.blocked",
|
|
defaultValue: "tor could not connect — this network may be blocking it. mesh messaging still works; location channels and internet delivery are paused until tor gets through.",
|
|
comment: "System message shown when Tor bootstrap runs out its deadline without connecting, which is what a network that blocks Tor looks like"
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
@objc func handleTorPreferenceChanged(_: Notification) {
|
|
Task { @MainActor in
|
|
self.torStatusAnnounced = false
|
|
self.torInitialReadyAnnounced = false
|
|
self.torRestartPending = false
|
|
self.torStallAnnounced = false
|
|
// Turning tor off means nobody is waiting on it; turning it on
|
|
// starts a fresh attempt. Either way the stall banner resets.
|
|
self.torBlocked = false
|
|
}
|
|
}
|
|
}
|