// // GroupViewController.swift // QuickLocation // import UIKit import RxSwift import RxCocoa import RxDataSources import SDCycleScrollView import ObjectMapper final class GroupViewController: BaseViewController { override var isNavigationBarHidden: Bool { true } fileprivate var rootView: GroupView! private let viewModel = GroupViewModel() /// 行程页当前圈子数据 private var itineraryGroupModel: GroupModel? private var itinerarySchedules: [ScheduleModel] = [] override func loadView() { rootView = GroupView(frame: UIScreen.main.bounds) view = rootView } override func viewDidLoad() { super.viewDidLoad() bindViewModel() reactiveAction() observeTableViews() bindItineraryPage() requestRecommandGroup() requestGroupInfo() guard let config = AppContextManager.shared.systemConfig else { return } rootView.cycleScrollView.imageURLStringsGroup = config.groupBannerList } // MARK: - OpenIM private var hasSetupIMListeners = false /// 进入页面加载 IM 会话:圈子集合已由业务接口给出,这里只补未读/最后一条 private func loadIMData() { GroupIMService.shared.ensureLogin { [weak self] success in guard let self = self else { return } guard success else { return } self.setupIMListenersIfNeeded() self.refreshIMData() } } /// 注册 IM 监听(仅一次):会话变化更新未读/时间;群组变化后重拉会话 private func setupIMListenersIfNeeded() { guard !hasSetupIMListeners else { return } hasSetupIMListeners = true GroupIMService.shared.setConversationListener { [weak self] conversations in DispatchQueue.main.async { self?.viewModel.updateConversations(conversations) } } GroupIMService.shared.setGroupListener { [weak self] in GroupIMService.shared.getConversationList { conversations in DispatchQueue.main.async { self?.viewModel.loadConversations(conversations) } } } } private func refreshIMData() { GroupIMService.shared.getConversationList { [weak self] conversations in DispatchQueue.main.async { DLToast.dismiss() self?.viewModel.loadConversations(conversations) } } } // MARK: - Bindings private func bindViewModel() { viewModel.output.hotGroups .observe(on: MainScheduler.asyncInstance) .bind(to: rootView.hotGroupsCollectionView.rx.items(dataSource: hotGroupDataSource)) .disposed(by: disposeBag) viewModel.output.createdSections .observe(on: MainScheduler.asyncInstance) .bind(to: rootView.createdTableView.rx.items(dataSource: createdDataSource)) .disposed(by: disposeBag) viewModel.output.joinedSections .observe(on: MainScheduler.asyncInstance) .bind(to: rootView.joinedTableView.rx.items(dataSource: joinedDataSource)) .disposed(by: disposeBag) rootView.hotGroupsCollectionView.rx.modelSelected(GroupInfoModel.self) .subscribe(viewModel.hotGroupCellAction.inputs) .disposed(by: disposeBag) } private func reactiveAction() { rootView.createGroupBtn.rx.tapGesture .subscribe(onNext: { _ in AppRouter.push(Route.createGroup) }) .disposed(by: disposeBag) rootView.joinGroupBtn.rx.tapGesture .subscribe(onNext: { _ in AppRouter.push(Route.joinGroup) }) .disposed(by: disposeBag) // 子 Tab:全部圈子(0=joined) / 我的圈子(1=created) rootView.joinedTabLabel.rx.tapGesture .subscribe(onNext: { [weak self] _ in self?.switchToSegment(0) }) .disposed(by: disposeBag) rootView.createdTabLabel.rx.tapGesture .subscribe(onNext: { [weak self] _ in self?.switchToSegment(1) }) .disposed(by: disposeBag) // 顶栏:圈子 / 行程 rootView.circleTabLabel.rx.tapGesture .subscribe(onNext: { [weak self] _ in self?.rootView.selectTopTab(at: 0) }) .disposed(by: disposeBag) rootView.itineraryTabLabel.rx.tapGesture .subscribe(onNext: { [weak self] _ in guard let self = self else { return } self.rootView.selectTopTab(at: 1) self.refreshItineraryPage() }) .disposed(by: disposeBag) Observable.merge( rootView.createdTableView.rx.modelSelected(GroupCellData.self).asObservable(), rootView.joinedTableView.rx.modelSelected(GroupCellData.self).asObservable() ) .subscribe(onNext: { data in let groupId = data.group.group_key guard !groupId.isEmpty else { return } AppRouter.push(Route.groupChat, userInfo: ["groupId": groupId]) }) .disposed(by: disposeBag) // MQTT join/leave/dismiss 后刷新群列表 NotificationCenter.default.rx.notification(.RefreshIMGroupListNotification) .subscribe(onNext: { [weak self] _ in self?.requestGroupInfo() }) .disposed(by: disposeBag) NotificationCenter.default.rx.notification(.RefreshUserConfigNotification) .subscribe(onNext: { [weak self] _ in self?.requestRecommandGroup() self?.requestGroupInfo() guard let config = AppContextManager.shared.systemConfig else { return } self?.rootView.cycleScrollView.imageURLStringsGroup = config.groupBannerList }) .disposed(by: disposeBag) NotificationCenter.default.rx.notification(.RefreshGroupInfoNotification) .subscribe(onNext: { [weak self] _ in self?.refreshItineraryPage() }) .disposed(by: disposeBag) } private func bindItineraryPage() { let page = rootView.itineraryPage page.onSwitchGroup = { [weak self] in self?.showSwitchGroupPop() } page.onSelectSchedule = { model in AppRouter.push(Route.scheduleDetail, userInfo: ["scheduleJson": model.toJSON()]) } page.onDeleteSchedule = { [weak self] model in self?.deleteSchedule(model) } } private func switchToSegment(_ index: Int) { rootView.selectSegment(at: index) let offset = CGPoint(x: CGFloat(index) * rootView.segmentScrollView.bounds.width, y: 0) rootView.segmentScrollView.setContentOffset(offset, animated: false) } // MARK: - 行程页 private func refreshItineraryPage() { GroupService.groupInfo().subscribe(onNext: { [weak self] response in guard let self = self, let model = response.model else { return } self.itineraryGroupModel = model let current = model.groups.first(where: { $0.group_key == model.default_group_key }) self.rootView.itineraryPage.updateGroupName(current?.name ?? "") self.requestItinerarySchedules(groupKey: model.default_group_key) }).disposed(by: disposeBag) } private func requestItinerarySchedules(groupKey: String) { guard !groupKey.isEmpty else { itinerarySchedules = [] rootView.itineraryPage.reloadSchedules([]) return } ItineraryService.groupScheduleList(groupKey: groupKey) .subscribe(onNext: { [weak self] response in guard let self = self else { return } self.itinerarySchedules = response.list self.rootView.itineraryPage.reloadSchedules(response.list) }) .disposed(by: disposeBag) } private func showSwitchGroupPop() { guard let groupModel = itineraryGroupModel else { refreshItineraryPage() return } GroupListPopView.show(groupModel: groupModel) { [weak self] groupKey in guard let self = self, let key = groupKey else { return } GroupService.operate(opType: "setdefault", requestData: ["group_key": key]) .subscribe(onNext: { _ in NotificationCenter.default.post(name: .RefreshGroupInfoNotification, object: nil) }) .disposed(by: self.disposeBag) } } private func deleteSchedule(_ model: ScheduleModel) { guard !model.id.isEmpty else { return } DLToast.showLoading() ItineraryService.delete(id: model.id) .subscribe(onNext: { [weak self] _ in DLToast.dismiss() guard let self = self else { return } self.itinerarySchedules.removeAll { $0.id == model.id } self.rootView.itineraryPage.reloadSchedules(self.itinerarySchedules) }, onError: { _ in DLToast.dismiss() }) .disposed(by: disposeBag) } // MARK: - 内层 tableView 滚动 private func observeTableViews() { rootView.createdTableView.rx.didScroll .observe(on: MainScheduler.asyncInstance) .subscribe(onNext: { [weak self] in guard let self = self else { return } self.rootView.handleTableViewScroll(self.rootView.createdTableView) }) .disposed(by: disposeBag) rootView.joinedTableView.rx.didScroll .observe(on: MainScheduler.asyncInstance) .subscribe(onNext: { [weak self] in guard let self = self else { return } self.rootView.handleTableViewScroll(self.rootView.joinedTableView) }) .disposed(by: disposeBag) } // MARK: - dataSource private lazy var hotGroupDataSource: RxCollectionViewSectionedReloadDataSource = { RxCollectionViewSectionedReloadDataSource { datasource, collectionView, indexPath, model in let cell: HotGroupCell = collectionView.dequeueReusableCell(for: indexPath) cell.configure(model) return cell } }() private lazy var createdDataSource: RxTableViewSectionedReloadDataSource = { RxTableViewSectionedReloadDataSource { _, tableView, indexPath, model in let cell: CircleGroupCell = tableView.dequeueReusableCell(for: indexPath) cell.configure(model) return cell } }() private lazy var joinedDataSource: RxTableViewSectionedReloadDataSource = { RxTableViewSectionedReloadDataSource { _, tableView, indexPath, model in let cell: CircleGroupCell = tableView.dequeueReusableCell(for: indexPath) cell.configure(model) return cell } }() // MARK: - API private func requestRecommandGroup() { GroupService.recommand(count: 5).subscribe(onNext: { response in self.viewModel.loadHotGroupData(response.list) }).disposed(by: disposeBag) } private func requestGroupInfo() { GroupService.groupInfo().subscribe { response in guard let model = response.model else { return } self.viewModel.applyBusinessGroups(model.groups) self.loadIMData() }.disposed(by: disposeBag) } }