// // LockDistractVC.swift // QuickLocation // import UIKit import RxSwift import RxCocoa final class LockDistractVC: BaseViewController { private var rootView: LockDistractView! private var groupModel: GroupModel? private var ownedGroups: [GroupInfoModel] = [] private var activeGroupKey: String = "" private var members: [GroupMemberModel] = [] private var selectedIndex = 0 private var lockAppsDisposable: Disposable? private let lockStateSubscriptionOwner = UUID() override var isNavigationBarHidden: Bool { true } override func loadView() { rootView = LockDistractView(frame: UIScreen.main.bounds) view = rootView } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor(hexStr: "#FAFAFA") bind() loadGroupMembers() } @MainActor deinit { MQTTService.shared.updateLockStateSubscriptions( owner: lockStateSubscriptionOwner, userIds: [] ) } private func bind() { rootView.navView.onBackTapped = { [weak self] in self?.navigationController?.popViewController(animated: true) } rootView.switchGroupBtn.rx.tap .subscribe(onNext: { [weak self] in self?.switchGroup() }) .disposed(by: disposeBag) rootView.memberPrevBtn.rx.tap .subscribe(onNext: { [weak self] in self?.stepMember(-1) }) .disposed(by: disposeBag) rootView.memberNextBtn.rx.tap .subscribe(onNext: { [weak self] in self?.stepMember(1) }) .disposed(by: disposeBag) rootView.notFetchedView.rx.tap .subscribe(onNext: { _ in LockDistractPermissionPopView.show() }).disposed(by: disposeBag) rootView.onPrivilegeAction = { [weak self] in guard let self else { return } if self.selectedMember?.user_id == AppContextManager.shared.userId { AppRouter.push(Route.privacySettings) } else { DLToast.show(text: "已提醒") } } rootView.lockBtn.rx.tap .subscribe(onNext: { [weak self] in self?.performPrimaryAction() }) .disposed(by: disposeBag) NotificationCenter.default.rx.notification(.lockDistractAppsDidChange) .observe(on: MainScheduler.instance) .subscribe(onNext: { [weak self] notification in guard let self else { return } let groupKey = notification.userInfo?[LockDistractAppsChangeUserInfoKey.groupKey] as? String ?? "" let userId = notification.userInfo?[LockDistractAppsChangeUserInfoKey.userId] as? String ?? "" guard groupKey.isEmpty || groupKey == self.activeGroupKey, userId.isEmpty || userId == self.selectedMember?.user_id else { return } self.loadLockableApps() }) .disposed(by: disposeBag) NotificationCenter.default.rx.notification(UIApplication.didBecomeActiveNotification) .observe(on: MainScheduler.instance) .subscribe(onNext: { [weak self] _ in self?.loadLockableApps() }) .disposed(by: disposeBag) } private func loadGroupMembers() { GroupService.groupInfo().subscribe(onNext: { [weak self] response in guard let self, let model = response.model else { return } self.applyOwnedGroups(from: model) }).disposed(by: disposeBag) } private func applyOwnedGroups(from model: GroupModel) { let owned = model.groups.filter(\.is_owner) guard !owned.isEmpty else { MQTTService.shared.updateLockStateSubscriptions( owner: lockStateSubscriptionOwner, userIds: [] ) DLToast.show(text: "只有圈主才能使用此功能") { [weak self] in self?.navigationController?.popViewController(animated: true) } return } groupModel = model ownedGroups = owned if owned.contains(where: { $0.group_key == model.default_group_key }) { activeGroupKey = model.default_group_key } else { activeGroupKey = owned[0].group_key } updateGroupName() loadUsers(for: activeGroupKey) } private func ownedGroupModelForPicker() -> GroupModel? { guard var model = groupModel else { return nil } model.groups = ownedGroups model.default_group_key = activeGroupKey return model } private func loadUsers(for groupKey: String) { guard !groupKey.isEmpty else { applyMembers([]) return } GroupService.groupUsers(groupKey: groupKey) .subscribe(onNext: { [weak self] response in guard response.isValid(for: groupKey) else { DLToast.showError(text: response.message ?? "获取圈子成员失败") return } self?.applyMembers(response.list) }, onError: { error in DLToast.showError(text: error.localizedDescription) }) .disposed(by: disposeBag) } private func applyMembers(_ members: [GroupMemberModel]) { self.members = members MQTTService.shared.updateLockStateSubscriptions( owner: lockStateSubscriptionOwner, userIds: members.map(\.user_id) ) selectedIndex = 0 refreshMember() rootView.setTodayLockCount(0) loadLockableApps() } private func stepMember(_ delta: Int) { guard !members.isEmpty else { return } let count = members.count selectedIndex = (selectedIndex + delta + count) % count refreshMember() loadLockableApps() } private func refreshMember() { guard members.indices.contains(selectedIndex) else { rootView.configureMember(name: " ") rootView.configureLocked(false) rootView.setPrivilegeDenied(false, isCurrentUser: false) return } let m = members[selectedIndex] let name = m.showName let isSelf = m.user_id.trimmed == AppContextManager.shared.userId.trimmed rootView.configureMember( name: name, headPic: m.displayHeadPic, isCurrentUser: isSelf ) rootView.configureLocked(false) rootView.setPrivilegeDenied(!m.extra.privilege.app_lock, isCurrentUser: isSelf) } private func updateGroupName() { let name = groupModel?.groups.first(where: { $0.group_key == activeGroupKey })?.name ?? "" rootView.setGroupName(name) } private func switchGroup() { guard let pickerModel = ownedGroupModelForPicker() else { loadGroupMembers() return } GroupListPopView.show( groupModel: pickerModel, currentMembers: members, selectedKey: activeGroupKey ) { [weak self] groupKey in guard let self, let key = groupKey, key != self.activeGroupKey else { return } self.activeGroupKey = key self.updateGroupName() self.loadUsers(for: key) } } private var selectedMember: GroupMemberModel? { members.indices.contains(selectedIndex) ? members[selectedIndex] : nil } private func loadLockableApps() { lockAppsDisposable?.dispose() rootView.configureLockableApps([]) guard let member = selectedMember, !activeGroupKey.isEmpty else { return } if !member.extra.privilege.app_lock { rootView.setTodayLockCount(0) return } let targetUserId = member.user_id let requestGroupKey = activeGroupKey lockAppsDisposable = UserService.phoneLockApps(userId: targetUserId, groupKey: requestGroupKey) .subscribe(onNext: { [weak self] response in guard let self, self.activeGroupKey == requestGroupKey, self.selectedMember?.user_id == targetUserId else { return } let apps = response.model?.apps ?? [] let lockedCount = apps.filter(\.locked).count let action = self.primaryAction( locked: lockedCount > 0, fromUser: response.resolvedFromUser, member: member ) self.rootView.configureLockableApps(apps) self.rootView.configureLocked(lockedCount > 0, action: action) self.rootView.setTodayLockCount(lockedCount) }, onError: { [weak self] error in guard let self, self.activeGroupKey == requestGroupKey, self.selectedMember?.user_id == targetUserId else { return } self.rootView.configureLockableApps([]) self.rootView.configureLocked(false) self.rootView.setTodayLockCount(0) DLToast.show(text: error.gatewayMessage ?? "获取可锁应用失败") }) lockAppsDisposable?.disposed(by: disposeBag) } private func primaryAction( locked: Bool, fromUser: String, member: GroupMemberModel ) -> LockDistractPrimaryAction { guard locked else { return .lock } let currentUserId = AppContextManager.shared.userId.trimmed let lockOwnerId = fromUser.trimmed if !currentUserId.isEmpty, lockOwnerId == currentUserId { return .unlock } if member.user_id.trimmed == currentUserId { return .requestUnlock } return .hidden } private func requestUnlockForLockedApps() { let lockedApps = rootView.lockableApps.filter(\.locked) let tokens = lockedApps.map(\.token).filter { !$0.isEmpty } let os = lockedApps.first(where: { !$0.os.isEmpty })?.os ?? "ios" guard !activeGroupKey.isEmpty, !tokens.isEmpty else { DLToast.show(text: "锁定数据缺失") return } DLToast.showLoading() UserService.requestPhoneUnlock(os: os, groupKey: activeGroupKey, tokens: tokens) .subscribe(onNext: { _ in DLToast.show(text: "已向圈主发送解锁请求") }, onError: { error in DLToast.show(text: error.gatewayMessage ?? "发送失败") }) .disposed(by: disposeBag) } private func unlockLockedApps() { guard let member = selectedMember else { DLToast.show(text: "解锁数据缺失") return } let lockedApps = rootView.lockableApps.filter(\.locked) let tokens = lockedApps.map(\.token).filter { !$0.isEmpty } let os = lockedApps.first(where: { !$0.os.isEmpty })?.os ?? "ios" guard !activeGroupKey.isEmpty, !tokens.isEmpty else { DLToast.show(text: "解锁数据缺失") return } DLToast.showLoading() rootView.lockBtn.isEnabled = false UserService.phoneUnlock( os: os, groupKey: activeGroupKey, userId: member.user_id, tokens: tokens ) .subscribe(onNext: { [weak self] _ in self?.rootView.lockBtn.isEnabled = true DLToast.show(text: "已解锁") self?.loadLockableApps() }, onError: { [weak self] error in self?.rootView.lockBtn.isEnabled = true DLToast.show(text: error.gatewayMessage ?? "解锁失败") }) .disposed(by: disposeBag) } private func performPrimaryAction() { switch rootView.primaryAction { case .requestUnlock: requestUnlockForLockedApps() case .unlock: unlockLockedApps() case .lock: lockSelectedApps() case .hidden: break } } private func lockSelectedApps() { guard let member = selectedMember, !activeGroupKey.isEmpty else { DLToast.show(text: "请选择要锁定的成员") return } let apps = rootView.selectedLockApps let tokens = apps.map(\.token).filter { !$0.isEmpty } guard !tokens.isEmpty else { DLToast.show(text: "请选择要锁定的应用") return } let os = apps.first(where: { !$0.os.isEmpty })?.os ?? "ios" DLToast.showLoading() rootView.lockBtn.isEnabled = false UserService.phoneLock( os: os, groupKey: activeGroupKey, userId: member.user_id, tokens: tokens, iconIndex: rootView.selectedLockIconIndex, message: rootView.lockMessage ) .subscribe(onNext: { [weak self] _ in self?.rootView.lockBtn.isEnabled = true DLToast.show(text: "锁定成功") self?.loadLockableApps() }, onError: { [weak self] error in self?.rootView.lockBtn.isEnabled = true DLToast.show(text: error.gatewayMessage ?? "锁定失败") }) .disposed(by: disposeBag) } }