Files
suixinkan_uikit/suixinkan/Features/Home/Services/HomeLocationStateStore.swift
汉秋 005349f8e6 模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 11:01:08 +08:00

192 lines
6.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// HomeLocationStateStore.swift
// suixinkan
//
import Foundation
/// 线 2 Android `LocationStateRepository`
final class HomeLocationStateStore {
static let shared = HomeLocationStateStore(store: .shared)
static let onlineDurationMillis: Int64 = 2 * 60 * 60 * 1000
private(set) var isOnline = false
private(set) var elapsedSeconds: Int64 = 0
private(set) var nextReportCountdownSeconds: Int64 = 0
private(set) var reminderMinutes = 0
private let store: AppStore
private var countdownTask: Task<Void, Never>?
private var anchorTimestamp: Int64?
private var observers: [ObjectIdentifier: StateObserver] = [:]
init(store: AppStore = .shared) {
self.store = store
reminderMinutes = store.location.reminderMinutes
isOnline = store.location.onlineStatus
}
deinit {
countdownTask?.cancel()
}
///
func observe(_ owner: AnyObject, handler: @escaping () -> Void) {
observers[ObjectIdentifier(owner)] = StateObserver(owner: owner, handler: handler)
}
///
func removeObserver(_ owner: AnyObject) {
observers.removeValue(forKey: ObjectIdentifier(owner))
}
/// 线
func restoreStateIfNeeded() {
reminderMinutes = store.location.reminderMinutes
guard store.location.onlineStatus else {
isOnline = false
stopCountdown()
notifyChange()
return
}
let lastTime = store.location.lastReportTime
guard lastTime > 0 else {
isOnline = false
store.location.onlineStatus = false
store.location.clearLastReportTime()
stopCountdown()
notifyChange()
return
}
let now = Int64(Date().timeIntervalSince1970 * 1000)
let elapsed = now - lastTime
if elapsed >= Self.onlineDurationMillis {
updateOnlineStatus(false)
store.location.clearLastReportTime()
notifyChange()
return
}
anchorTimestamp = lastTime
let remaining = Self.onlineDurationMillis - elapsed
elapsedSeconds = max(0, elapsed / 1000)
nextReportCountdownSeconds = max(0, remaining / 1000)
startCountdown(anchorTime: lastTime)
notifyChange()
}
/// 线
func updateOnlineStatus(_ online: Bool) {
isOnline = online
store.location.onlineStatus = online
if !online {
stopCountdown()
store.location.clearLastReportTime()
}
notifyChange()
}
///
func updateReminderMinutes(_ minutes: Int) {
reminderMinutes = minutes
store.location.reminderMinutes = minutes
notifyChange()
}
///
func startLocationReport(at timestamp: Int64 = Int64(Date().timeIntervalSince1970 * 1000)) {
anchorTimestamp = timestamp
store.location.lastReportTime = timestamp
isOnline = true
store.location.onlineStatus = true
elapsedSeconds = 0
nextReportCountdownSeconds = Self.onlineDurationMillis / 1000
startCountdown(anchorTime: timestamp)
notifyChange()
}
var countdownDisplayText: String {
formattedCountdown(from: nextReportCountdownSeconds)
}
/// Android `H:MM:SS`
var countdownDisplayTextSingleHour: String {
formattedCountdownSingleHour(from: nextReportCountdownSeconds)
}
/// `HH:MM:SS`
func formattedCountdown(from seconds: Int64) -> String {
let hours = seconds / 3600
let minutes = (seconds % 3600) / 60
let secs = seconds % 60
return String(format: "%02d:%02d:%02d", hours, minutes, secs)
}
/// `H:MM:SS`
func formattedCountdownSingleHour(from seconds: Int64) -> String {
let hours = seconds / 3600
let minutes = (seconds % 3600) / 60
let secs = seconds % 60
return String(format: "%d:%02d:%02d", hours, minutes, secs)
}
///
func shouldTriggerTimeoutReminder() -> Bool {
guard reminderMinutes > 0, isOnline else { return false }
let reminderSeconds = Int64(reminderMinutes * 60)
return nextReportCountdownSeconds > 0 && nextReportCountdownSeconds <= reminderSeconds
}
private func startCountdown(anchorTime: Int64) {
countdownTask?.cancel()
countdownTask = Task { [weak self] in
while !Task.isCancelled {
guard let self else { return }
guard self.isOnline else { return }
let now = Int64(Date().timeIntervalSince1970 * 1000)
let elapsed = now - anchorTime
let remaining = Self.onlineDurationMillis - elapsed
let elapsedSeconds = max(0, elapsed / 1000)
self.elapsedSeconds = elapsedSeconds
if remaining <= 0 {
self.nextReportCountdownSeconds = 0
self.updateOnlineStatus(false)
self.store.location.clearLastReportTime()
return
}
self.nextReportCountdownSeconds = max(0, remaining / 1000)
self.notifyChange()
try? await Task.sleep(nanoseconds: 1_000_000_000)
}
}
}
private func stopCountdown() {
countdownTask?.cancel()
countdownTask = nil
anchorTimestamp = nil
elapsedSeconds = 0
nextReportCountdownSeconds = 0
}
private func notifyChange() {
observers = observers.filter { $0.value.owner != nil }
observers.values.forEach { $0.handler() }
}
}
private final class StateObserver {
weak var owner: AnyObject?
let handler: () -> Void
init(owner: AnyObject, handler: @escaping () -> Void) {
self.owner = owner
self.handler = handler
}
}