// // GroupViewController.swift // QuickLocation // import UIKit import RxSwift import RxCocoa import RxDataSources import SDCycleScrollView import OpenIMSDK final class GroupViewController: BaseViewController { override var isNavigationBarHidden: Bool { true } fileprivate var rootView: GroupView! private let viewModel = GroupViewModel() override func loadView() { rootView = GroupView(frame: UIScreen.main.bounds) view = rootView } override func viewDidLoad() { super.viewDidLoad() bindViewModel() reactiveAction() observeTableViews() 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 监听(仅一次):会话变化更新未读/时间;群组变化(SDK 同步到新群/退群)刷新列表 private func setupIMListenersIfNeeded() { guard !hasSetupIMListeners else { return } hasSetupIMListeners = true GroupIMService.shared.setConversationListener { [weak self] _ in GroupIMService.shared.getConversationList { conversations in self?.viewModel.updateConversations(conversations) } } // 群组同步完成(如刚创建/加入的群)后刷新群列表,解决业务接口先返回、SDK 后同步的时序问题 GroupIMService.shared.setGroupListener { [weak self] in GroupIMService.shared.getJoinedGroups { groups in self?.viewModel.loadIMGroups(groups) } } } private func refreshIMData() { let group = DispatchGroup() var groups: [OIMGroupInfo] = [] var conversations: [OIMConversationInfo] = [] group.enter() GroupIMService.shared.getJoinedGroups { g in groups = g group.leave() } group.enter() GroupIMService.shared.getConversationList { c in conversations = c group.leave() } group.notify(queue: .main) { [weak self] in DLToast.dismiss() self?.viewModel.loadIMGroups(groups, conversations: 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) } 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) rootView.createdTabLabel.rx.tapGesture .subscribe(onNext: { [weak self] _ in self?.switchToSegment(0) }) .disposed(by: disposeBag) rootView.joinedTabLabel.rx.tapGesture .subscribe(onNext: { [weak self] _ in self?.switchToSegment(1) }) .disposed(by: disposeBag) Observable.merge( rootView.createdTableView.rx.modelSelected(GroupCellData.self).asObservable(), rootView.joinedTableView.rx.modelSelected(GroupCellData.self).asObservable() ) .subscribe(onNext: { data in guard let groupId = data.groupInfo.groupID else { return } AppRouter.push(Route.groupChat, userInfo: ["groupId": groupId]) }) .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) } 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: - 内层 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.groupList = model.groups self.loadIMData() }.disposed(by: disposeBag) } }