接入首页位置上报与在线倒计时,并优化上报交互体验。
持久化服务端 expired 过期时间戳以正确恢复倒计时,切换在线/离线与上报时展示定位 Loading,移除重复的成功 Alert,并将 Toast 调整为居中半透明样式。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -11,6 +11,7 @@ import SwiftUI
|
||||
/// 位置上报页面,支持当前位置上报、标记点上报、在线状态和提醒设置。
|
||||
struct LocationReportView: View {
|
||||
@EnvironmentObject private var accountContext: AccountContext
|
||||
@EnvironmentObject private var homeLocationViewModel: HomeLocationViewModel
|
||||
@Environment(\.accountSnapshotStore) private var snapshotStore
|
||||
@Environment(\.locationReportAPI) private var locationReportAPI
|
||||
@EnvironmentObject private var router: RouterPath
|
||||
@ -19,6 +20,8 @@ struct LocationReportView: View {
|
||||
|
||||
@StateObject private var viewModel = LocationReportViewModel()
|
||||
@State private var locationProvider = ForegroundLocationProvider()
|
||||
@State private var showOnlineDialog = false
|
||||
@State private var showReminderDialog = false
|
||||
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
|
||||
var body: some View {
|
||||
@ -46,11 +49,36 @@ struct LocationReportView: View {
|
||||
.accessibilityLabel("上报历史")
|
||||
}
|
||||
}
|
||||
.onReceive(timer) { _ in viewModel.tick() }
|
||||
.onReceive(timer) { _ in homeLocationViewModel.tick() }
|
||||
.task {
|
||||
homeLocationViewModel.restoreState()
|
||||
guard viewModel.currentCoordinate == nil else { return }
|
||||
await locateCurrent()
|
||||
}
|
||||
.alert("切换在线状态", isPresented: $showOnlineDialog) {
|
||||
Button("取消", role: .cancel) {}
|
||||
Button("确定") {
|
||||
Task {
|
||||
await globalLoading.withLoading(message: "正在定位...") {
|
||||
await homeLocationViewModel.confirmOnlineToggle(
|
||||
staffId: staffId,
|
||||
scenicId: scenicId,
|
||||
toast: toastCenter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text(homeLocationViewModel.isOnline ? "是否确认切换为离线状态?离线后将暂停位置上报。" : "是否确认切换为在线状态?在线后将开始位置上报和计时。")
|
||||
}
|
||||
.confirmationDialog("提前提醒时间", isPresented: $showReminderDialog, titleVisibility: .visible) {
|
||||
ForEach([0, 5, 10, 15, 30], id: \.self) { minute in
|
||||
Button(minute == 0 ? "不提醒" : "\(minute)分钟") {
|
||||
homeLocationViewModel.updateReminderMinutes(minute)
|
||||
}
|
||||
}
|
||||
Button("取消", role: .cancel) {}
|
||||
}
|
||||
}
|
||||
|
||||
/// 在线状态和倒计时区域。
|
||||
@ -58,24 +86,28 @@ struct LocationReportView: View {
|
||||
VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: AppMetrics.Spacing.xSmall) {
|
||||
Text(viewModel.isOnline ? "当前在线" : "当前离线")
|
||||
Text(homeLocationViewModel.isOnline ? "当前在线" : "当前离线")
|
||||
.font(.system(size: AppMetrics.FontSize.title3, weight: .semibold))
|
||||
.foregroundStyle(AppDesign.textPrimary)
|
||||
Text(viewModel.countdownText)
|
||||
Text(countdownText)
|
||||
.font(.system(size: AppMetrics.FontSize.subheadline))
|
||||
.foregroundStyle(AppDesign.textSecondary)
|
||||
}
|
||||
Spacer()
|
||||
Toggle("", isOn: Binding(
|
||||
get: { viewModel.isOnline },
|
||||
set: { value in
|
||||
Task { await toggleOnline(value) }
|
||||
}
|
||||
))
|
||||
.labelsHidden()
|
||||
Button {
|
||||
showOnlineDialog = true
|
||||
} label: {
|
||||
Text(homeLocationViewModel.isOnline ? "在线" : "离线")
|
||||
.font(.system(size: AppMetrics.FontSize.caption, weight: .medium))
|
||||
.foregroundStyle(homeLocationViewModel.isOnline ? Color(hex: 0xF0FDF4) : Color(hex: 0x7B8EAA))
|
||||
.padding(.horizontal, AppMetrics.Spacing.small)
|
||||
.padding(.vertical, AppMetrics.Spacing.xxSmall + 2)
|
||||
.background(homeLocationViewModel.isOnline ? Color(hex: 0x22C55E) : Color(hex: 0xF4F4F4), in: RoundedRectangle(cornerRadius: 4))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
if !viewModel.lastReportText.isEmpty {
|
||||
Text("上次上报:\(viewModel.lastReportText)")
|
||||
if !homeLocationViewModel.reportTimeText.isEmpty {
|
||||
Text("上次上报:\(homeLocationViewModel.reportTimeText)")
|
||||
.font(.system(size: AppMetrics.FontSize.caption))
|
||||
.foregroundStyle(AppDesign.placeholder)
|
||||
}
|
||||
@ -84,6 +116,13 @@ struct LocationReportView: View {
|
||||
.background(.white, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||||
}
|
||||
|
||||
private var countdownText: String {
|
||||
guard homeLocationViewModel.secondsUntilReport > 0 else { return "可立即上报" }
|
||||
let hours = homeLocationViewModel.secondsUntilReport / 3600
|
||||
let minutes = (homeLocationViewModel.secondsUntilReport % 3600) / 60
|
||||
return "\(hours)小时\(minutes)分钟后可再次提醒"
|
||||
}
|
||||
|
||||
/// 当前定位区域。
|
||||
private var currentLocationSection: some View {
|
||||
VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) {
|
||||
@ -149,8 +188,14 @@ struct LocationReportView: View {
|
||||
VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) {
|
||||
Text("提醒设置")
|
||||
.font(.system(size: AppMetrics.FontSize.title3, weight: .semibold))
|
||||
Stepper("提前 \(viewModel.reminderMinutes) 分钟提醒", value: $viewModel.reminderMinutes, in: 5...120, step: 5)
|
||||
.font(.system(size: AppMetrics.FontSize.subheadline))
|
||||
Button {
|
||||
showReminderDialog = true
|
||||
} label: {
|
||||
Text(homeLocationViewModel.reminderMinutes == 0 ? "不提醒" : "提前 \(homeLocationViewModel.reminderMinutes) 分钟提醒")
|
||||
.font(.system(size: AppMetrics.FontSize.subheadline))
|
||||
.foregroundStyle(AppDesign.primary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.padding(AppMetrics.Spacing.medium)
|
||||
.background(.white, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||||
@ -177,7 +222,7 @@ struct LocationReportView: View {
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.disabled(viewModel.isSubmitting)
|
||||
.disabled(viewModel.isSubmitting || homeLocationViewModel.isReporting)
|
||||
}
|
||||
|
||||
/// 坐标和地址展示。
|
||||
@ -211,20 +256,16 @@ struct LocationReportView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换在线状态。
|
||||
private func toggleOnline(_ value: Bool) async {
|
||||
let success = await globalLoading.withOptionalLoading(value) {
|
||||
await viewModel.setOnline(value, staffId: staffId, scenicId: scenicId, api: locationReportAPI)
|
||||
}
|
||||
if !success {
|
||||
toastCenter.show(viewModel.errorMessage ?? "状态上报失败")
|
||||
}
|
||||
}
|
||||
|
||||
/// 提交指定类型的位置上报。
|
||||
private func submit(type: LocationReportType) async {
|
||||
let success = await globalLoading.withLoading {
|
||||
await viewModel.submit(type: type, staffId: staffId, scenicId: scenicId, api: locationReportAPI)
|
||||
await viewModel.submit(
|
||||
type: type,
|
||||
staffId: staffId,
|
||||
scenicId: scenicId,
|
||||
homeLocationViewModel: homeLocationViewModel,
|
||||
toast: toastCenter
|
||||
)
|
||||
}
|
||||
toastCenter.show(success ? "上报成功" : (viewModel.errorMessage ?? "上报失败"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user