// // AppRestrictVC.swift // QuickLocation // import FamilyControls import UIKit import RxSwift import RxCocoa import ManagedSettings @available(iOS 16.0, *) final class AppRestrictVC: BaseViewController { private var rootView: AppRestrictView! private var tokens: [ApplicationToken] = [] private var linkingToken: ApplicationToken? override func loadView() { rootView = AppRestrictView(frame: UIScreen.main.bounds) view = rootView } override func viewDidLoad() { super.viewDidLoad() rootView.tableView.dataSource = self rootView.tableView.delegate = self rootView.tableView.register(AppRestrictCell.self, forCellReuseIdentifier: AppRestrictCell.reuseId) bind() reload() } private func bind() { rootView.navView.onBackTapped = { [weak self] in self?.navigationController?.popViewController(animated: true) } rootView.addBtn.rx.tap.subscribe(onNext: { [weak self] in self?.addApps() }).disposed(by: disposeBag) } private func reload() { tokens = AppRestrictManager.shared.applicationTokens rootView.setListVisible(!tokens.isEmpty, rowCount: tokens.count) rootView.tableView.reloadData() } private func addApps() { Task { @MainActor in do { if !AppRestrictManager.shared.isAuthorized { try await AppRestrictManager.shared.requestAuthorization() } guard AppRestrictManager.shared.isAuthorized else { DLToast.show(text: "需要屏幕使用时间权限") return } let initialSelection = AppRestrictManager.shared.selection let presenter = FamilyActivityPickerPresenter(selection: initialSelection) presenter.onComplete = { [weak self] selection in let addedApplications = selection.applicationTokens .subtracting(initialSelection.applicationTokens) AppRestrictManager.shared.mergeSelection(selection) self?.reload() if addedApplications.isEmpty, !selection.categoryTokens.isEmpty { self?.showCategorySelectionNotice() } else if !addedApplications.isEmpty { self?.showApplicationTokens(addedApplications) } } present(presenter, animated: false) } catch { DLToast.show(text: "授权失败,请在设置中开启屏幕使用时间权限") } } } private func showCategorySelectionNotice() { let alert = UIAlertController( title: "请选择具体应用", message: "系统不会返回分类“全部”中的具体应用。请展开分类,取消“全部”,再逐个勾选需要配对的应用。", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "取消", style: .cancel)) alert.addAction(UIAlertAction(title: "重新选择", style: .default) { [weak self] _ in self?.addApps() }) present(alert, animated: true) } private func showApplicationTokens(_ tokens: Set) { let values = tokens.compactMap { token in AppRestrictTokenCodec.encode(token)?.base64EncodedString() }.sorted() guard !values.isEmpty else { DLToast.show(text: "ApplicationToken 编码失败") return } let message = values.enumerated().map { index, value in values.count == 1 ? value : "应用 \(index + 1):\n\(value)" }.joined(separator: "\n\n") let alert = UIAlertController( title: "ApplicationToken", message: message, preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "复制", style: .default) { _ in UIPasteboard.general.string = values.joined(separator: "\n") DLToast.show(text: "Token 已复制") }) alert.addAction(UIAlertAction(title: "关闭", style: .cancel)) present(alert, animated: true) } private func openLink(for token: ApplicationToken) { linkingToken = token let vc = SelectActivityVC() vc.onSelect = { [weak self] item in guard let self, let token = self.linkingToken else { return } AppRestrictManager.shared.link( catalogId: item.id, token: token, displayName: item.name, iconURL: item.iconURL ) self.linkingToken = nil self.reload() } let nav = UINavigationController(rootViewController: vc) present(nav, animated: true) } private func deleteApp(_ token: ApplicationToken) { AppRestrictManager.shared.removeApplication(token) reload() } } @available(iOS 16.0, *) extension AppRestrictVC: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { tokens.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: AppRestrictCell.reuseId, for: indexPath) as! AppRestrictCell let token = tokens[indexPath.row] let catalog = AppRestrictManager.shared.catalogItem(for: token) cell.configure(token: token, catalog: catalog) cell.onPair = { [weak self] in self?.openLink(for: token) } cell.onDelete = { [weak self] in self?.deleteApp(token) } return cell } } /// Fallback when system is below iOS 16. final class AppRestrictUnsupportedVC: BaseViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white let nav = BaseNavigationView(title: "应用配对库") view.addSubview(nav) nav.layoutChain.top().edgesHorzontal().height(kNaviHeight) nav.onBackTapped = { [weak self] in self?.navigationController?.popViewController(animated: true) } let lab = UILabel() lab.text = "该功能需要 iOS 16 及以上系统" lab.textAlignment = .center lab.textColor = UIColor(hexStr: "#767676") view.addSubview(lab) lab.layoutChain.center() } } enum AppRestrictFactory { static func make() -> UIViewController { if #available(iOS 16.0, *) { return AppRestrictVC() } return AppRestrictUnsupportedVC() } }