188 lines
6.7 KiB
Swift
188 lines
6.7 KiB
Swift
//
|
||
// 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<ApplicationToken>) {
|
||
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()
|
||
}
|
||
}
|