fix(ghostgram): fix Always Online + implement Ghost Mode mutual exclusion

Bugs fixed:
- Always Online had no effect after toggle — updatePresence was only called
  on app focus changes, not on settings changes. Fixed by subscribing to
  MiscSettingsManager notifications in AccountPresenceManagerImpl.

- Ghost Mode + Always Online conflict: if Ghost Mode was enabled, the early
  return in updatePresence completely blocked Always Online logic.

Changes:
- ManagedAccountPresence: priority chain Always Online > Ghost Mode > default.
  Subscribes to GhostMode/MiscSettings notifications, refreshes presence
  immediately on any change. 30s keep-alive timer for Always Online.
- GhostModeManager: enabling Ghost Mode auto-disables alwaysOnline via
  disableAlwaysOnlineForMutualExclusion(). No recursion via guard flag.
- MiscSettingsManager: enabling alwaysOnline auto-disables Ghost Mode via
  disableForMutualExclusion(). No recursion via guard flag.
- MiscController: subscribes to GhostModeManager notifications to refresh
  UI when Ghost Mode is auto-disabled by Always Online.
- GhostModeController: subscribes to MiscSettings notifications to refresh
  UI when Ghost Mode is auto-disabled by Always Online.
This commit is contained in:
ichmagmaus 812
2026-02-23 23:17:25 +01:00
parent db53826061
commit 019945f9da
5 changed files with 204 additions and 76 deletions
@@ -300,6 +300,28 @@ public func ghostModeController(context: AccountContext) -> ViewController {
}
)
// Refresh UI when Always Online is enabled externally and auto-disables Ghost Mode
// the isEnabled flip happens in GhostModeManager from MiscSettingsManager context,
// so we need to pull fresh values from the manager.
let miscSettingsChangedSignal: Signal<Void, NoError> = Signal { subscriber in
let observer = NotificationCenter.default.addObserver(
forName: MiscSettingsManager.settingsChangedNotification,
object: nil,
queue: .main
) { _ in
updateState { state in
state.isEnabled = GhostModeManager.shared.isEnabled
state.hideReadReceipts = GhostModeManager.shared.hideReadReceipts
state.hideStoryViews = GhostModeManager.shared.hideStoryViews
state.hideOnlineStatus = GhostModeManager.shared.hideOnlineStatus
state.hideTypingIndicator = GhostModeManager.shared.hideTypingIndicator
state.forceOffline = GhostModeManager.shared.forceOffline
}
}
return ActionDisposable { NotificationCenter.default.removeObserver(observer) }
}
let _ = miscSettingsChangedSignal.start()
let signal = combineLatest(
context.sharedContext.presentationData,
statePromise.get()
@@ -291,12 +291,34 @@ public func miscController(context: AccountContext) -> ViewController {
toggleAlwaysOnline: {
let newValue = !MiscSettingsManager.shared.alwaysOnline
MiscSettingsManager.shared.alwaysOnline = newValue
// State will be refreshed via notification if Ghost Mode got auto-disabled
updateState { state in
state.alwaysOnline = newValue
}
}
)
// Refresh UI when Ghost Mode is auto-disabled by mutual exclusion
// the toggle flip happens externally, so we must pull fresh values from the managers.
let ghostModeChangedSignal: Signal<Void, NoError> = Signal { subscriber in
let observer = NotificationCenter.default.addObserver(
forName: GhostModeManager.settingsChangedNotification,
object: nil,
queue: .main
) { _ in
updateState { state in
state.isEnabled = MiscSettingsManager.shared.isEnabled
state.bypassCopyProtection = MiscSettingsManager.shared.bypassCopyProtection
state.disableViewOnceAutoDelete = MiscSettingsManager.shared.disableViewOnceAutoDelete
state.bypassScreenshotProtection = MiscSettingsManager.shared.bypassScreenshotProtection
state.blockAds = MiscSettingsManager.shared.blockAds
state.alwaysOnline = MiscSettingsManager.shared.alwaysOnline
}
}
return ActionDisposable { NotificationCenter.default.removeObserver(observer) }
}
let _ = ghostModeChangedSignal.start()
let signal = combineLatest(
context.sharedContext.presentationData,
statePromise.get()