181 lines
5.8 KiB
Swift
181 lines
5.8 KiB
Swift
//
|
||
// CreateScheduleVM.swift
|
||
// QuickLocation
|
||
//
|
||
// Created by 八条 on 2026/6/24.
|
||
//
|
||
|
||
import RxSwift
|
||
import RxCocoa
|
||
import RxDataSources
|
||
import SwiftDate
|
||
|
||
// MARK: - 行程点编辑模型
|
||
struct SchedulePointItem: IdentifiableType, Equatable {
|
||
let identity: String = UUID().uuidString
|
||
var locationName: String = ""
|
||
var address: String = ""
|
||
var latitude: Double = 0
|
||
var longitude: Double = 0
|
||
var province: String = ""
|
||
var city: String = ""
|
||
var district: String = ""
|
||
var street: String = ""
|
||
var country: String = ""
|
||
var formatted_address: String = ""
|
||
var expectedTime: Date?
|
||
var remark: String = ""
|
||
}
|
||
|
||
enum SchedulePointRole: Equatable {
|
||
case start
|
||
case middle
|
||
case end
|
||
}
|
||
|
||
enum CreateScheduleListItem: IdentifiableType, Equatable {
|
||
case point(index: Int, item: SchedulePointItem, role: SchedulePointRole)
|
||
case add
|
||
|
||
var identity: String {
|
||
switch self {
|
||
case .point(_, let item, _):
|
||
return item.identity
|
||
case .add:
|
||
return "__add__"
|
||
}
|
||
}
|
||
}
|
||
|
||
typealias SchedulePointSection = SectionModel<String, CreateScheduleListItem>
|
||
|
||
class CreateScheduleVM {
|
||
|
||
let disposeBag = DisposeBag()
|
||
|
||
let scheduModel: ScheduleModel?
|
||
|
||
// MARK: - Input
|
||
let addPointTapped = PublishSubject<Void>()
|
||
let deletePointAt = PublishSubject<Int>()
|
||
let movePoint = PublishSubject<(from: Int, to: Int)>()
|
||
let updatePoint = PublishSubject<(index: Int, item: SchedulePointItem)>()
|
||
let selectedDate = BehaviorRelay<Date>(value: Date())
|
||
var selectedGroupKeys: [String] = []
|
||
|
||
/// 切换圈子选中状态(已选则删除,未选则添加)
|
||
func toggleGroupKey(_ key: String) {
|
||
if let idx = selectedGroupKeys.firstIndex(of: key) {
|
||
selectedGroupKeys.remove(at: idx)
|
||
} else {
|
||
selectedGroupKeys.append(key)
|
||
}
|
||
}
|
||
|
||
/// 删除指定圈子 key
|
||
func removeGroupKey(_ key: String) {
|
||
selectedGroupKeys.removeAll { $0 == key }
|
||
}
|
||
|
||
// MARK: - Output
|
||
let pointsRelay: BehaviorRelay<[SchedulePointItem]>
|
||
let listItems: Observable<[CreateScheduleListItem]>
|
||
let dateString: Observable<String>
|
||
|
||
init(_ model: ScheduleModel?) {
|
||
self.scheduModel = model
|
||
|
||
if let m = model, !m.points.isEmpty {
|
||
var items = m.points.map { $0.toSchedulePointItem() }
|
||
while items.count < 2 {
|
||
items.append(SchedulePointItem())
|
||
}
|
||
pointsRelay = BehaviorRelay<[SchedulePointItem]>(value: items)
|
||
selectedDate.accept(Date(timeIntervalSince1970: TimeInterval(m.timestamp) / 1000))
|
||
selectedGroupKeys = m.groups.map { $0.group_key }
|
||
} else {
|
||
pointsRelay = BehaviorRelay<[SchedulePointItem]>(value: [SchedulePointItem(), SchedulePointItem()])
|
||
}
|
||
|
||
dateString = selectedDate
|
||
.map { $0.toFormat("yyyy年MM月dd日") }
|
||
|
||
listItems = pointsRelay.map { Self.buildListItems(from: $0) }
|
||
|
||
// 添加行程点:插入到终点之前
|
||
addPointTapped
|
||
.withLatestFrom(pointsRelay)
|
||
.map { list in
|
||
var new = list
|
||
let insertAt = max(new.count - 1, 1)
|
||
new.insert(SchedulePointItem(), at: insertAt)
|
||
return new
|
||
}
|
||
.bind(to: pointsRelay)
|
||
.disposed(by: disposeBag)
|
||
|
||
// 删除行程点:仅允许删中间点,且始终保留始+终
|
||
deletePointAt
|
||
.withLatestFrom(pointsRelay) { ($1, $0) }
|
||
.subscribe(onNext: { [weak self] list, idx in
|
||
guard list.count > 2, idx > 0, idx < list.count - 1 else { return }
|
||
var new = list
|
||
new.remove(at: idx)
|
||
self?.pointsRelay.accept(new)
|
||
})
|
||
.disposed(by: disposeBag)
|
||
|
||
// 中间点排序(始/终固定)
|
||
movePoint
|
||
.withLatestFrom(pointsRelay) { ($1, $0) }
|
||
.subscribe(onNext: { [weak self] list, move in
|
||
guard list.count > 2 else { return }
|
||
let from = move.from
|
||
let to = move.to
|
||
let middleRange = 1..<(list.count - 1)
|
||
guard middleRange.contains(from), middleRange.contains(to), from != to else { return }
|
||
var new = list
|
||
let item = new.remove(at: from)
|
||
new.insert(item, at: to)
|
||
self?.pointsRelay.accept(new)
|
||
})
|
||
.disposed(by: disposeBag)
|
||
|
||
// 更新某个行程点
|
||
updatePoint
|
||
.withLatestFrom(pointsRelay) { ($1, $0) }
|
||
.subscribe(onNext: { [weak self] list, update in
|
||
guard update.index < list.count else { return }
|
||
var new = list
|
||
new[update.index] = update.item
|
||
self?.pointsRelay.accept(new)
|
||
})
|
||
.disposed(by: disposeBag)
|
||
}
|
||
|
||
static func buildListItems(from points: [SchedulePointItem]) -> [CreateScheduleListItem] {
|
||
guard points.count >= 2 else {
|
||
return points.enumerated().map { idx, item in
|
||
let role: SchedulePointRole = idx == 0 ? .start : .end
|
||
return .point(index: idx, item: item, role: role)
|
||
}
|
||
}
|
||
var rows: [CreateScheduleListItem] = []
|
||
rows.append(.point(index: 0, item: points[0], role: .start))
|
||
if points.count > 2 {
|
||
for i in 1..<(points.count - 1) {
|
||
rows.append(.point(index: i, item: points[i], role: .middle))
|
||
}
|
||
}
|
||
rows.append(.add)
|
||
rows.append(.point(index: points.count - 1, item: points[points.count - 1], role: .end))
|
||
return rows
|
||
}
|
||
|
||
static func role(at index: Int, total: Int) -> SchedulePointRole {
|
||
if index == 0 { return .start }
|
||
if index == total - 1 { return .end }
|
||
return .middle
|
||
}
|
||
}
|