修复离线路径误弹位置上报超时窗,并优化提醒设置与定位 Loading 展示。

离线或倒计时未进入提醒窗口时不再弹出超时提醒;全局 Loading 支持按需展示定位文案,提前提醒选项抽成共用组件。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 16:22:55 +08:00
parent 560b52fcc8
commit 63fb0462d6
11 changed files with 188 additions and 108 deletions

View File

@ -71,7 +71,7 @@ Core 模块提供跨业务复用的基础能力,包括网络请求、缓存存
Loading 的可观察状态只在 `GlobalLoadingOverlayHost` 内部订阅,并由 `RootView` 挂载到应用根部。这样切换 Loading 显隐时,只会刷新根部 Overlay不会让当前页面、Tab 根视图或业务子视图形成观察依赖。
当前全局 Loading UI 不展示文案;`message` 字段保留为内部展示状态,便于后续需要时恢复文案展示
当前全局 Loading 默认只展示动画;调用方传入 `showsMessage: true` 时,才会在动画下方展示 `message` 文案(如位置上报的「获取定位中」)
全局 Loading 只用于阻塞型等待,例如冷启动恢复、登录、首屏加载、提交表单和核销。列表加载更多、上传进度、按钮内局部反馈继续保留在页面局部状态中。

View File

@ -16,10 +16,12 @@ final class GlobalLoadingCenter {
fileprivate let state = GlobalLoadingState()
/// Loading
func show(message: String = "") {
func show(message: String = "", showsMessage: Bool = false) {
if !message.isEmpty {
state.message = message
}
state.messageDisplayLevels.append(showsMessage)
state.showsMessage = state.messageDisplayLevels.contains(true)
state.activeCount += 1
state.isVisible = true
}
@ -27,9 +29,15 @@ final class GlobalLoadingCenter {
/// Loading
func hide() {
state.activeCount = max(0, state.activeCount - 1)
if !state.messageDisplayLevels.isEmpty {
state.messageDisplayLevels.removeLast()
}
state.showsMessage = state.messageDisplayLevels.contains(true)
guard state.activeCount == 0 else { return }
state.isVisible = false
state.message = ""
state.messageDisplayLevels = []
state.showsMessage = false
}
/// Loading Loading
@ -41,9 +49,10 @@ final class GlobalLoadingCenter {
/// Loading
func withLoading<T>(
message: String = "",
showsMessage: Bool = false,
operation: () async throws -> T
) async rethrows -> T {
show(message: message)
show(message: message, showsMessage: showsMessage)
defer { hide() }
return try await operation()
}
@ -52,10 +61,11 @@ final class GlobalLoadingCenter {
func withOptionalLoading<T>(
_ enabled: Bool,
message: String = "",
showsMessage: Bool = false,
operation: () async throws -> T
) async rethrows -> T {
if enabled {
return try await withLoading(message: message, operation: operation)
return try await withLoading(message: message, showsMessage: showsMessage, operation: operation)
}
return try await operation()
}
@ -66,7 +76,8 @@ final class GlobalLoadingCenter {
GlobalLoadingSnapshot(
isVisible: state.isVisible,
message: state.message,
activeCount: state.activeCount
activeCount: state.activeCount,
showsMessage: state.showsMessage
)
}
#endif
@ -77,7 +88,9 @@ final class GlobalLoadingCenter {
fileprivate final class GlobalLoadingState: ObservableObject {
@Published fileprivate var isVisible = false
@Published fileprivate var message = ""
@Published fileprivate var showsMessage = false
@Published fileprivate var activeCount = 0
fileprivate var messageDisplayLevels: [Bool] = []
}
/// Lottie Loading loading.json
@ -146,6 +159,14 @@ private struct GlobalLoadingOverlayHost: View {
VStack(spacing: 14) {
loadingAnimation
if state.showsMessage, !state.message.isEmpty {
Text(state.message)
.font(.system(size: 15, weight: .medium))
.foregroundStyle(AppDesign.textPrimary)
.multilineTextAlignment(.center)
.padding(.horizontal, 8)
.padding(.bottom, 6)
}
}
.padding(.horizontal, 10)
.padding(.vertical, 10)
@ -155,10 +176,12 @@ private struct GlobalLoadingOverlayHost: View {
.transition(.opacity)
.zIndex(10_000)
.accessibilityElement(children: .combine)
.accessibilityLabel("加载中")
.accessibilityLabel(state.showsMessage && !state.message.isEmpty ? state.message : "加载中")
}
}
.animation(.easeInOut(duration: 0.2), value: state.isVisible)
.animation(.easeInOut(duration: 0.2), value: state.showsMessage)
.animation(.easeInOut(duration: 0.2), value: state.message)
}
@ViewBuilder
@ -200,6 +223,7 @@ struct GlobalLoadingSnapshot: Equatable {
let isVisible: Bool
let message: String
let activeCount: Int
let showsMessage: Bool
}
#endif

View File

@ -0,0 +1,29 @@
//
// ReminderTimeOptionsMenu.swift
// suixinkan
//
// Created by Codex on 2026/6/29.
//
import SwiftUI
///
/// 使 `Menu` `confirmationDialog` ScrollView popover
struct ReminderTimeOptionsMenu<Label: View>: View {
let onSelect: (Int) -> Void
@ViewBuilder let label: () -> Label
private let options = [0, 5, 10, 15, 30]
var body: some View {
Menu {
ForEach(options, id: \.self) { minute in
Button(minute == 0 ? "不提醒" : "\(minute)分钟") {
onSelect(minute)
}
}
} label: {
label()
}
}
}