// // 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 let selectedDateKey: BehaviorRelay let trips = BehaviorRelay<[ScheduleRecordModel]>(value: []) let selectedTripId = BehaviorRelay(value: nil) let displayedTrips = BehaviorRelay<[ScheduleRecordModel]>(value: []) let isEmpty = BehaviorRelay(value: true) let loading = BehaviorRelay(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([]) 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] _ in guard let self, self.requestToken == token else { return } self.applyTrips([]) }) .disposed(by: disposeBag) } private func applyTrips(_ list: [ScheduleRecordModel]) { 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 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")) } }