接入首页位置上报与在线倒计时,并优化上报交互体验。
持久化服务端 expired 过期时间戳以正确恢复倒计时,切换在线/离线与上报时展示定位 Loading,移除重复的成功 Alert,并将 Toast 调整为居中半透明样式。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -79,7 +79,7 @@ Loading 的可观察状态只在 `GlobalLoadingOverlayHost` 内部订阅,并
|
||||
|
||||
`ToastCenter` 只用于展示轻量提示文案,业务页面通过 `toastCenter.show(...)` 发出提示命令,不直接读取 Toast 展示状态。
|
||||
|
||||
Toast UI 是顶部全宽横幅,背景使用不透明主色并延伸到顶部安全区和屏幕左右边;文案居中展示,无关闭按钮,默认 2.2 秒自动隐藏。新的 Toast 会覆盖旧 Toast 并重新计时。
|
||||
Toast UI 是屏幕中央的黑色半透明圆角卡片,左侧带警告图标,文案 16pt;以透明度淡入淡出,无关闭按钮,默认 2.2 秒自动隐藏。新的 Toast 会覆盖旧 Toast 并重新计时。
|
||||
|
||||
## Upload
|
||||
|
||||
|
||||
108
suixinkan/Core/Location/LocationStateStore.swift
Normal file
108
suixinkan/Core/Location/LocationStateStore.swift
Normal file
@ -0,0 +1,108 @@
|
||||
//
|
||||
// LocationStateStore.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/29.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 位置上报本地状态存储,持久化在线状态、过期时间、上次上报时间和提醒设置。
|
||||
final class LocationStateStore {
|
||||
static let onlineDurationSeconds = 7200
|
||||
/// 区分 Unix 时间戳与剩余秒数的阈值。
|
||||
private static let expireTimestampThreshold = 1_000_000_000
|
||||
|
||||
private let defaults: UserDefaults
|
||||
private let lastReportTimeKey = "key_last_location_report_time"
|
||||
private let expireTimeKey = "key_location_expire_time"
|
||||
private let onlineStatusKey = "key_online_status"
|
||||
private let reminderMinutesKey = "key_location_reminder_minutes"
|
||||
|
||||
/// 初始化位置状态存储,并允许测试注入独立 UserDefaults。
|
||||
init(defaults: UserDefaults = .standard) {
|
||||
self.defaults = defaults
|
||||
}
|
||||
|
||||
/// 读取是否在线。
|
||||
func loadOnlineStatus() -> Bool {
|
||||
defaults.bool(forKey: onlineStatusKey)
|
||||
}
|
||||
|
||||
/// 保存是否在线。
|
||||
func saveOnlineStatus(_ isOnline: Bool) {
|
||||
defaults.set(isOnline, forKey: onlineStatusKey)
|
||||
}
|
||||
|
||||
/// 读取上次上报时间戳(毫秒)。
|
||||
func loadLastReportTime() -> TimeInterval {
|
||||
defaults.double(forKey: lastReportTimeKey)
|
||||
}
|
||||
|
||||
/// 保存上次上报时间戳(毫秒)。
|
||||
func saveLastReportTime(_ timestamp: TimeInterval) {
|
||||
defaults.set(timestamp, forKey: lastReportTimeKey)
|
||||
}
|
||||
|
||||
/// 清除上次上报时间。
|
||||
func clearLastReportTime() {
|
||||
defaults.removeObject(forKey: lastReportTimeKey)
|
||||
}
|
||||
|
||||
/// 读取服务端返回的过期 Unix 时间戳(秒)。
|
||||
func loadExpireTime() -> Int {
|
||||
defaults.integer(forKey: expireTimeKey)
|
||||
}
|
||||
|
||||
/// 保存服务端返回的过期 Unix 时间戳(秒)。
|
||||
func saveExpireTime(_ timestamp: Int) {
|
||||
defaults.set(timestamp, forKey: expireTimeKey)
|
||||
}
|
||||
|
||||
/// 清除过期时间。
|
||||
func clearExpireTime() {
|
||||
defaults.removeObject(forKey: expireTimeKey)
|
||||
}
|
||||
|
||||
/// 读取提前提醒分钟数,默认 0 表示不提醒。
|
||||
func loadReminderMinutes() -> Int {
|
||||
guard defaults.object(forKey: reminderMinutesKey) != nil else { return 0 }
|
||||
return defaults.integer(forKey: reminderMinutesKey)
|
||||
}
|
||||
|
||||
/// 保存提前提醒分钟数。
|
||||
func saveReminderMinutes(_ minutes: Int) {
|
||||
defaults.set(minutes, forKey: reminderMinutesKey)
|
||||
}
|
||||
|
||||
/// 将服务端 expired 规范化为过期 Unix 时间戳(秒)。
|
||||
func normalizedExpireTimestamp(from raw: Int, now: Date = Date()) -> Int {
|
||||
guard raw > 0 else {
|
||||
return Int(now.timeIntervalSince1970) + Self.onlineDurationSeconds
|
||||
}
|
||||
if raw > Self.expireTimestampThreshold {
|
||||
return raw
|
||||
}
|
||||
return Int(now.timeIntervalSince1970) + raw
|
||||
}
|
||||
|
||||
/// 根据过期时间戳计算剩余倒计时秒数;已过期返回 0。
|
||||
func remainingSeconds(untilExpireTime expireTime: Int, now: Date = Date()) -> Int {
|
||||
guard expireTime > 0 else { return 0 }
|
||||
let nowSeconds = Int(now.timeIntervalSince1970)
|
||||
return max(0, expireTime - nowSeconds)
|
||||
}
|
||||
|
||||
/// 根据上次上报时间计算剩余倒计时秒数;已过期返回 0。仅用于无过期时间戳时的兼容恢复。
|
||||
func remainingSeconds(since anchorTime: TimeInterval, now: Date = Date()) -> Int {
|
||||
let elapsed = now.timeIntervalSince1970 * 1000 - anchorTime
|
||||
let remainingMillis = Double(Self.onlineDurationSeconds) * 1000 - elapsed
|
||||
guard remainingMillis > 0 else { return 0 }
|
||||
return Int(remainingMillis / 1000)
|
||||
}
|
||||
|
||||
/// 判断上次上报是否仍在 2 小时在线窗口内。
|
||||
func isWithinOnlineWindow(since anchorTime: TimeInterval, now: Date = Date()) -> Bool {
|
||||
remainingSeconds(since: anchorTime, now: now) > 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user