jsdw_ios/QuickLocation/Section/Home/TodayTrackDetail/TodayTrackDetailViewModel.s...

178 lines
5.8 KiB
Swift

//
// TodayTrackDetailViewModel.swift
// QuickLocation
//
import Foundation
import RxSwift
import RxCocoa
import SwiftDate
final class TodayTrackDetailViewModel {
struct DateItem {
let date: Date
let title: String
let key: String
}
let members: [GroupMemberModel]
let selectedMemberId: BehaviorRelay<String>
let selectedDateKey: BehaviorRelay<String>
let trips = BehaviorRelay<[ScheduleRecordModel]>(value: [])
let selectedTripId = BehaviorRelay<String?>(value: nil)
let displayedTrips = BehaviorRelay<[ScheduleRecordModel]>(value: [])
let isEmpty = BehaviorRelay<Bool>(value: true)
let isVipRequired = BehaviorRelay<Bool>(value: false)
let loading = BehaviorRelay<Bool>(value: false)
private let disposeBag = DisposeBag()
let dateItems: [DateItem]
private var requestToken = UUID()
init(members: [GroupMemberModel], selectedUserId: String, initialDate: Date = Date()) {
self.members = members
self.dateItems = Self.makeDateItems(around: initialDate)
self.selectedMemberId = BehaviorRelay(value: selectedUserId)
let defaultKey = Self.dayKey(Self.yesterday(of: initialDate))
self.selectedDateKey = BehaviorRelay(value: defaultKey)
bindSelection()
}
func start() {
reloadTrips()
}
var selectedMember: GroupMemberModel? {
members.first(where: { $0.user_id == selectedMemberId.value })
}
func selectTrip(_ trip: ScheduleRecordModel) {
selectedTripId.accept(trip.id)
applyDisplayedTrips()
}
func dateMillis(for key: String) -> Int64 {
guard let item = dateItems.first(where: { $0.key == key }) else {
return Self.dayStartMillis(Self.yesterday(of: Date()))
}
return Self.dayStartMillis(item.date)
}
private func bindSelection() {
Observable.merge(
selectedMemberId.asObservable().skip(1).map { _ in () },
selectedDateKey.asObservable().skip(1).map { _ in () }
)
.subscribe(onNext: { [weak self] in
self?.reloadTrips()
})
.disposed(by: disposeBag)
}
private func reloadTrips() {
let userId = selectedMemberId.value
let dateKey = selectedDateKey.value
selectedTripId.accept(nil)
trips.accept([])
displayedTrips.accept([])
isVipRequired.accept(false)
isEmpty.accept(false)
loadTrips(userId: userId, dateKey: dateKey)
}
private func loadTrips(userId: String, dateKey: String) {
if userId.isEmpty {
applyTrips([])
return
}
let token = UUID()
requestToken = token
loading.accept(true)
let date = dateMillis(for: dateKey)
UserService.phoneUsageTrips(userId: userId, date: date)
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] response in
guard let self, self.requestToken == token else { return }
self.applyTrips(response.trips)
}, onError: { [weak self] error in
guard let self, self.requestToken == token else { return }
if error.gatewayStatusCode == 22000 {
self.applyVipRequired()
return
}
self.applyTrips([])
})
.disposed(by: disposeBag)
}
private func applyTrips(_ list: [ScheduleRecordModel]) {
isVipRequired.accept(false)
let sorted = list.sorted { $0.start_time < $1.start_time }
trips.accept(sorted)
if selectedTripId.value == nil || !sorted.contains(where: { $0.id == selectedTripId.value }) {
selectedTripId.accept(sorted.first?.id)
}
applyDisplayedTrips()
loading.accept(false)
}
private func applyVipRequired() {
selectedTripId.accept(nil)
trips.accept([])
displayedTrips.accept([])
isEmpty.accept(false)
isVipRequired.accept(true)
loading.accept(false)
}
private func applyDisplayedTrips() {
let all = trips.value
if let id = selectedTripId.value, let one = all.first(where: { $0.id == id }) {
displayedTrips.accept([one])
} else {
displayedTrips.accept([])
}
isEmpty.accept(all.isEmpty)
}
private static func yesterday(of date: Date) -> Date {
var calendar = shanghaiCalendar()
let today = calendar.startOfDay(for: date)
return calendar.date(byAdding: .day, value: -1, to: today) ?? today
}
private static func dayKey(_ date: Date) -> String {
date.in(region: shanghaiRegion()).toFormat("yyyy-MM-dd")
}
static func dayStartMillis(_ date: Date) -> Int64 {
let start = shanghaiCalendar().startOfDay(for: date)
return Int64((start.timeIntervalSince1970 * 1000).rounded())
}
private static func makeDateItems(around date: Date) -> [DateItem] {
var calendar = shanghaiCalendar()
let today = calendar.startOfDay(for: date)
var items: [DateItem] = []
for offset in (-30 ... -1) {
guard let day = calendar.date(byAdding: .day, value: offset, to: today) else {
continue
}
let title = offset == -1 ? "昨天" : day.in(region: shanghaiRegion()).toFormat("MM-dd")
items.append(DateItem(date: day, title: title, key: dayKey(day)))
}
return items
}
private static func shanghaiCalendar() -> Calendar {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "Asia/Shanghai") ?? .current
return calendar
}
private static func shanghaiRegion() -> Region {
Region(zone: TimeZone(identifier: "Asia/Shanghai") ?? .current, locale: Locale(identifier: "zh_CN"))
}
}