接入首页位置上报与在线倒计时,并优化上报交互体验。
持久化服务端 expired 过期时间戳以正确恢复倒计时,切换在线/离线与上报时展示定位 Loading,移除重复的成功 Alert,并将 Toast 调整为居中半透明样式。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
13
suixinkan/App/AMapConfig.swift
Normal file
13
suixinkan/App/AMapConfig.swift
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// AMapConfig.swift
|
||||
// suixinkan
|
||||
//
|
||||
// 高德地图 Key 配置。请在高德控制台申请 iOS 平台 Key 后替换 apiKey。
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum AMapConfig {
|
||||
/// 占位 Key,与 Android 工程一致;上线前请替换为 iOS 平台专用 Key。
|
||||
static let apiKey = "79e82527e55a4927992f79efe93c112b"
|
||||
}
|
||||
@ -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。
|
||||
|
||||
## 导航规则
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ struct suixinkanApp: App {
|
||||
|
||||
init() {
|
||||
AppUITestLaunchState.resetIfNeeded()
|
||||
AMapBootstrap.configure(apiKey: AMapConfig.apiKey)
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
|
||||
Reference in New Issue
Block a user