jsdw_ios/QuickLocation/Section/LockDistract/LockDistractVC.swift

374 lines
13 KiB
Swift

//
// 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)
}
}