Upgrade scenic picker with search, location sorting, permission apply/status, and blocking home dialog; add cooperation order module and order source picker improvements with unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
122 lines
3.8 KiB
Swift
122 lines
3.8 KiB
Swift
//
|
||
// ScenicSelectionViewModel.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 权限申请审核中状态,对齐 Android role-apply pending status。
|
||
enum PermissionApplyPendingStatus {
|
||
static let reviewing = 1
|
||
static let rejected = 3
|
||
}
|
||
|
||
/// 景区选择 ViewModel,对齐 Android `ScenicSelectionViewModel`。
|
||
final class ScenicSelectionViewModel {
|
||
|
||
private(set) var spots: [ScenicSpotDisplayItem] = []
|
||
private(set) var searchQuery = ""
|
||
private(set) var currentLocationText = "最近的景区"
|
||
private(set) var isLoading = false
|
||
|
||
var onStateChange: (() -> Void)?
|
||
var onShowMessage: ((String) -> Void)?
|
||
var onSelectionComplete: (() -> Void)?
|
||
var onNavigateApply: (() -> Void)?
|
||
var onNavigateApplyStatus: ((String) -> Void)?
|
||
|
||
private let appStore: AppStore
|
||
private let locationProvider: HomeLocationProvider
|
||
private var currentLat: Double?
|
||
private var currentLng: Double?
|
||
|
||
init(appStore: AppStore = .shared, locationProvider: HomeLocationProvider = HomeLocationProvider()) {
|
||
self.appStore = appStore
|
||
self.locationProvider = locationProvider
|
||
}
|
||
|
||
func loadFromLocal() {
|
||
let scenicList = resolvedScenicList()
|
||
spots = ScenicSpotDisplayMapper.buildItems(
|
||
scenicList: scenicList,
|
||
searchQuery: searchQuery,
|
||
currentLat: currentLat,
|
||
currentLng: currentLng,
|
||
selectedScenicId: appStore.currentScenicId
|
||
)
|
||
notifyStateChange()
|
||
}
|
||
|
||
func onSearchQueryChange(_ query: String) {
|
||
searchQuery = query
|
||
loadFromLocal()
|
||
}
|
||
|
||
func startLocation() async {
|
||
isLoading = true
|
||
notifyStateChange()
|
||
do {
|
||
let snapshot = try await locationProvider.requestSnapshot()
|
||
currentLat = snapshot.latitude
|
||
currentLng = snapshot.longitude
|
||
let address = snapshot.address.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
currentLocationText = address.isEmpty ? "最近的景区" : address
|
||
loadFromLocal()
|
||
} catch HomeLocationProviderError.permissionDenied {
|
||
onShowMessage?("未授予定位权限,无法获取附近景区")
|
||
} catch {
|
||
onShowMessage?("定位失败: \(error.localizedDescription)")
|
||
}
|
||
isLoading = false
|
||
notifyStateChange()
|
||
}
|
||
|
||
func selectSpot(_ spot: ScenicSpotDisplayItem) {
|
||
appStore.currentScenicId = spot.id
|
||
appStore.currentScenicName = spot.name
|
||
NotificationCenter.default.post(
|
||
name: NotificationName.scenicDidChange,
|
||
object: nil,
|
||
userInfo: [
|
||
NotificationUserInfoKey.scenicId: spot.id,
|
||
NotificationUserInfoKey.scenicName: spot.name,
|
||
]
|
||
)
|
||
loadFromLocal()
|
||
onSelectionComplete?()
|
||
}
|
||
|
||
func onApplyNow(api: HomeAPI) async {
|
||
isLoading = true
|
||
notifyStateChange()
|
||
defer {
|
||
isLoading = false
|
||
notifyStateChange()
|
||
}
|
||
do {
|
||
let pendingList = try await api.roleApplyPending()
|
||
if let first = pendingList.first,
|
||
first.status == PermissionApplyPendingStatus.reviewing
|
||
|| first.status == PermissionApplyPendingStatus.rejected {
|
||
onNavigateApplyStatus?(first.code)
|
||
} else {
|
||
onNavigateApply?()
|
||
}
|
||
} catch {
|
||
onNavigateApply?()
|
||
}
|
||
}
|
||
|
||
private func resolvedScenicList() -> [ScenicInfo] {
|
||
let cached = appStore.roleScenicList()
|
||
if !cached.isEmpty { return cached }
|
||
let rolePermissionList = appStore.rolePermissionList()
|
||
guard !rolePermissionList.isEmpty else { return [] }
|
||
return appStore.matchRolePermissionItem(in: rolePermissionList)?.scenic ?? []
|
||
}
|
||
|
||
private func notifyStateChange() {
|
||
onStateChange?()
|
||
}
|
||
}
|