Migrate from iOS 17 Observation to iOS 16-compatible Combine architecture.

Lower deployment target to iOS 16, replace @Observable with ObservableObject, add navigation and UI compatibility shims, and include login smoke UI tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 10:16:35 +08:00
parent c32a610ee0
commit 26f4d0e671
127 changed files with 2320 additions and 1465 deletions

View File

@ -11,17 +11,17 @@ import UniformTypeIdentifiers
///
struct ProjectManagementView: View {
@Environment(AccountContext.self) private var accountContext
@Environment(AccountSnapshotStore.self) private var snapshotStore
@Environment(ProjectAPI.self) private var projectAPI
@EnvironmentObject private var accountContext: AccountContext
@Environment(\.accountSnapshotStore) private var snapshotStore
@Environment(\.projectAPI) private var projectAPI
@Environment(\.globalLoading) private var globalLoading
@State private var viewModel = ProjectManagementViewModel()
@StateObject private var viewModel = ProjectManagementViewModel()
var body: some View {
VStack(spacing: 0) {
filterBar
if accountContext.currentScenic?.id == nil {
ContentUnavailableView("缺少景区", systemImage: "map", description: Text("请选择景区后再管理项目。"))
AppContentUnavailableView("缺少景区", systemImage: "map", description: Text("请选择景区后再管理项目。"))
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
projectList
@ -66,7 +66,7 @@ struct ProjectManagementView: View {
ScrollView {
LazyVStack(spacing: AppMetrics.Spacing.medium) {
if viewModel.items.isEmpty && !viewModel.loading {
ContentUnavailableView("暂无项目", systemImage: "folder", description: Text("可点击右上角创建项目。"))
AppContentUnavailableView("暂无项目", systemImage: "folder", description: Text("可点击右上角创建项目。"))
.frame(maxWidth: .infinity, minHeight: 260)
}
ForEach(viewModel.items) { item in
@ -110,10 +110,10 @@ struct ProjectManagementView: View {
///
struct StoreProjectManagementView: View {
@Environment(AccountSnapshotStore.self) private var snapshotStore
@Environment(ProjectAPI.self) private var projectAPI
@Environment(\.accountSnapshotStore) private var snapshotStore
@Environment(\.projectAPI) private var projectAPI
@Environment(\.globalLoading) private var globalLoading
@State private var viewModel = StoreProjectManagementViewModel()
@StateObject private var viewModel = StoreProjectManagementViewModel()
var body: some View {
ScrollView {
@ -127,7 +127,7 @@ struct StoreProjectManagementView: View {
.buttonStyle(.plain)
}
if viewModel.filteredItems.isEmpty && !viewModel.loading {
ContentUnavailableView("暂无店铺项目", systemImage: "folder", description: Text("可点击右上角新建店铺项目。"))
AppContentUnavailableView("暂无店铺项目", systemImage: "folder", description: Text("可点击右上角新建店铺项目。"))
.frame(minHeight: 260)
}
}
@ -196,7 +196,7 @@ struct StoreProjectManagementView: View {
///
struct ProjectDetailView: View {
@Environment(ProjectAPI.self) private var projectAPI
@Environment(\.projectAPI) private var projectAPI
@Environment(\.globalLoading) private var globalLoading
let projectId: Int
let storeMode: Bool
@ -209,7 +209,7 @@ struct ProjectDetailView: View {
if let detail {
ProjectDetailContentView(detail: detail)
} else if let errorMessage {
ContentUnavailableView("加载失败", systemImage: "exclamationmark.triangle", description: Text(errorMessage))
AppContentUnavailableView("加载失败", systemImage: "exclamationmark.triangle", description: Text(errorMessage))
.frame(minHeight: 360)
} else {
ProgressView()
@ -247,14 +247,14 @@ struct ProjectDetailView: View {
///
struct ProjectEditorView: View {
@Environment(\.dismiss) private var dismiss
@Environment(AccountContext.self) private var accountContext
@Environment(AccountSnapshotStore.self) private var snapshotStore
@Environment(ScenicSpotContext.self) private var scenicSpotContext
@Environment(ProjectAPI.self) private var projectAPI
@Environment(OSSUploadService.self) private var uploadService
@EnvironmentObject private var accountContext: AccountContext
@Environment(\.accountSnapshotStore) private var snapshotStore
@EnvironmentObject private var scenicSpotContext: ScenicSpotContext
@Environment(\.projectAPI) private var projectAPI
@Environment(\.ossUploadService) private var uploadService
@Environment(\.globalLoading) private var globalLoading
let projectId: Int?
@State private var viewModel = ProjectEditorViewModel(mode: .create)
@StateObject private var viewModel = ProjectEditorViewModel(mode: .create)
@State private var coverItem: PhotosPickerItem?
@State private var carouselItems: [PhotosPickerItem] = []
@State private var loadingDetail = false
@ -291,10 +291,10 @@ struct ProjectEditorView: View {
.task {
await prepare()
}
.onChange(of: coverItem) { _, newValue in
.onChange(of: coverItem) { newValue in
Task { await loadCover(newValue) }
}
.onChange(of: carouselItems) { _, newValue in
.onChange(of: carouselItems) { newValue in
Task { await loadCarousel(newValue) }
}
}
@ -387,7 +387,7 @@ struct ProjectEditorView: View {
defer { loadingDetail = false }
do {
let detail = try await projectAPI.projectDetail(id: projectId)
viewModel = ProjectEditorViewModel(mode: .edit(detail))
viewModel.apply(detail)
} catch {
viewModel.errorMessage = error.localizedDescription
}
@ -418,12 +418,12 @@ struct ProjectEditorView: View {
///
struct StoreProjectEditorView: View {
@Environment(\.dismiss) private var dismiss
@Environment(AccountSnapshotStore.self) private var snapshotStore
@Environment(ProjectAPI.self) private var projectAPI
@Environment(OSSUploadService.self) private var uploadService
@Environment(\.accountSnapshotStore) private var snapshotStore
@Environment(\.projectAPI) private var projectAPI
@Environment(\.ossUploadService) private var uploadService
@Environment(\.globalLoading) private var globalLoading
let projectId: Int?
@State private var viewModel = StoreProjectEditorViewModel(mode: .create)
@StateObject private var viewModel = StoreProjectEditorViewModel(mode: .create)
@State private var coverItem: PhotosPickerItem?
@State private var carouselItems: [PhotosPickerItem] = []
@ -454,10 +454,10 @@ struct StoreProjectEditorView: View {
}
}
.task { await prepare() }
.onChange(of: coverItem) { _, newValue in
.onChange(of: coverItem) { newValue in
Task { viewModel.coverImage = await ProjectPickerLoader.loadImage(from: newValue) }
}
.onChange(of: carouselItems) { _, newValue in
.onChange(of: carouselItems) { newValue in
Task { viewModel.carouselImages = await ProjectPickerLoader.loadImages(from: newValue) }
}
}
@ -545,7 +545,7 @@ struct StoreProjectEditorView: View {
if let projectId {
do {
let detail = try await projectAPI.storeManagerProjectDetail(id: projectId)
viewModel = StoreProjectEditorViewModel(mode: .edit(detail))
viewModel.apply(detail)
} catch {
viewModel.errorMessage = error.localizedDescription
}