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

持久化服务端 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,13 @@
//
// AMapConfig.swift
// suixinkan
//
// Key iOS Key apiKey
//
import Foundation
enum AMapConfig {
/// Key Android 线 iOS Key
static let apiKey = "79e82527e55a4927992f79efe93c112b"
}

View File

@ -84,7 +84,7 @@ App 模块负责应用入口、根视图切换、全局状态注入、主导航
全局 Toast 由 `RootView` 挂载在页面最上层,业务页面只调用 `toastCenter.show(...)` 发出提示命令。
Toast 展示为顶部全宽横幅,背景使用不透明主色并延伸到屏幕顶部、左边和右边;文案居中展示,不提供关闭按钮,默认 2.2 秒后自动消失。连续展示新 Toast 时会覆盖旧文案并重新计时,旧的自动隐藏任务不会影响新的 Toast。
Toast 展示为屏幕中央的黑色半透明圆角卡片(`Color.black.opacity(0.78)`),左侧带警告图标,文案 16pt 展示,以透明度淡入淡出,不提供关闭按钮,默认 2.2 秒后自动消失。连续展示新 Toast 时会覆盖旧文案并重新计时,旧的自动隐藏任务不会影响新的 Toast。
## 导航规则

View File

@ -44,6 +44,7 @@ struct RootView: View {
@State private var assetsAPI: AssetsAPI
@State private var punchPointAPI: PunchPointAPI
@State private var locationReportAPI: LocationReportAPI
@StateObject private var homeLocationViewModel: HomeLocationViewModel
@State private var cooperationOrderAPI: CooperationOrderAPI
@State private var authSessionCoordinator: AuthSessionCoordinator
@State private var sessionBootstrapper: SessionBootstrapper
@ -82,7 +83,9 @@ struct RootView: View {
_inviteAPI = State(initialValue: InviteAPI(client: apiClient))
_assetsAPI = State(initialValue: AssetsAPI(client: apiClient))
_punchPointAPI = State(initialValue: PunchPointAPI(client: apiClient))
_locationReportAPI = State(initialValue: LocationReportAPI(client: apiClient))
let locationReportAPI = LocationReportAPI(client: apiClient)
_locationReportAPI = State(initialValue: locationReportAPI)
_homeLocationViewModel = StateObject(wrappedValue: HomeLocationViewModel(api: locationReportAPI))
_cooperationOrderAPI = State(initialValue: CooperationOrderAPI(client: apiClient))
_authSessionCoordinator = State(
initialValue: AuthSessionCoordinator(
@ -146,6 +149,7 @@ struct RootView: View {
.environment(\.assetsAPI, assetsAPI)
.environment(\.punchPointAPI, punchPointAPI)
.environment(\.locationReportAPI, locationReportAPI)
.environmentObject(homeLocationViewModel)
.environment(\.cooperationOrderAPI, cooperationOrderAPI)
.environment(\.authSessionCoordinator, authSessionCoordinator)
.task {

View File

@ -62,18 +62,17 @@ final class ToastCenter: ObservableObject {
#endif
}
/// Toast Toast
/// Toast Toast
private struct GlobalToastOverlay: ViewModifier {
@EnvironmentObject private var toastCenter: ToastCenter
func body(content: Content) -> some View {
content
.overlay(alignment: .top) {
GeometryReader { proxy in
.overlay {
Group {
if let message = toastCenter.message {
toastBanner(message, topInset: proxy.safeAreaInsets.top)
.transition(.move(edge: .top).combined(with: .opacity))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
toastBanner(message)
.transition(.opacity)
}
}
.allowsHitTesting(false)
@ -81,22 +80,23 @@ private struct GlobalToastOverlay: ViewModifier {
.animation(.easeInOut(duration: 0.18), value: toastCenter.message)
}
/// Toast
private func toastBanner(_ message: String, topInset: CGFloat) -> some View {
HStack {
/// Toast
private func toastBanner(_ message: String) -> some View {
HStack(spacing: 10) {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 16, weight: .semibold))
Text(message)
.font(.system(size: 14, weight: .medium))
.lineLimit(2)
.multilineTextAlignment(.center)
.font(.system(size: 16, weight: .medium))
.lineLimit(3)
.multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .center)
}
.foregroundStyle(.white)
.padding(.horizontal, AppMetrics.Spacing.pageHorizontal)
.padding(.top, topInset + AppMetrics.Spacing.small)
.padding(.bottom, AppMetrics.Spacing.small)
.frame(maxWidth: .infinity, alignment: .center)
.background(AppDesign.primary.ignoresSafeArea(edges: .top))
.padding(.horizontal, 20)
.padding(.vertical, 14)
.background(Color.black.opacity(0.78), in: RoundedRectangle(cornerRadius: 12))
.padding(.horizontal, 40)
}
}

View File

@ -14,6 +14,7 @@ struct suixinkanApp: App {
init() {
AppUITestLaunchState.resetIfNeeded()
AMapBootstrap.configure(apiKey: AMapConfig.apiKey)
}
var body: some Scene {