接入首页位置上报与在线倒计时,并优化上报交互体验。

持久化服务端 expired 过期时间戳以正确恢复倒计时,切换在线/离线与上报时展示定位 Loading,移除重复的成功 Alert,并将 Toast 调整为居中半透明样式。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 15:59:59 +08:00
parent d9f897038d
commit 560b52fcc8
19 changed files with 1028 additions and 204 deletions

View 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
}
}