261 lines
9.6 KiB
Swift
261 lines
9.6 KiB
Swift
//
|
||
// HomeMoreFunctionsViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 首页全部功能页,支持常用应用增删和更多功能网格展示。
|
||
final class HomeMoreFunctionsViewController: UIViewController {
|
||
|
||
private let viewModel = HomeViewModel()
|
||
private let commonMenuStore = HomeCommonMenuStore()
|
||
private var commonURIs: [String] = []
|
||
|
||
private lazy var tableView: UITableView = {
|
||
let table = UITableView(frame: .zero, style: .grouped)
|
||
table.backgroundColor = AppDesignUIKit.pageBackground
|
||
table.separatorStyle = .none
|
||
table.dataSource = self
|
||
table.delegate = self
|
||
table.register(HomeMoreMenuGridCell.self, forCellReuseIdentifier: HomeMoreMenuGridCell.reuseID)
|
||
return table
|
||
}()
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
title = "全部功能"
|
||
view.backgroundColor = AppDesignUIKit.pageBackground
|
||
navigationItem.largeTitleDisplayMode = .never
|
||
|
||
view.addSubview(tableView)
|
||
tableView.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
|
||
viewModel.onChange = { [weak self] in
|
||
self?.tableView.reloadData()
|
||
}
|
||
rebuildMenus()
|
||
|
||
appServices.permissionContext.onChange = { [weak self] in
|
||
self?.rebuildMenus()
|
||
}
|
||
}
|
||
|
||
private func rebuildMenus() {
|
||
let services = appServices
|
||
viewModel.buildMenus(
|
||
from: services.permissionContext.rolePermissions,
|
||
currentRoleId: services.permissionContext.currentRole?.id
|
||
)
|
||
commonURIs = commonMenuStore.load(menuItems: viewModel.menuItems)
|
||
tableView.reloadData()
|
||
}
|
||
|
||
private var commonItems: [HomeMenuItem] {
|
||
commonURIs.compactMap { menuItem(for: $0) }
|
||
}
|
||
|
||
private var moreItems: [HomeMenuItem] {
|
||
viewModel.menuItems.filter { !isCommonURI($0.uri) }
|
||
}
|
||
|
||
private func menuItem(for uri: String) -> HomeMenuItem? {
|
||
let availableURIs = Set(viewModel.menuItems.map(\.uri))
|
||
let resolvedUri = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
|
||
guard let existing = viewModel.menuItems.first(where: { $0.uri == resolvedUri }) else { return nil }
|
||
return HomeMenuItem(
|
||
title: HomeMenuRouter.displayTitle(for: resolvedUri, fallback: existing.title),
|
||
uri: resolvedUri,
|
||
iconSrc: existing.iconSrc
|
||
)
|
||
}
|
||
|
||
private func isCommonURI(_ uri: String) -> Bool {
|
||
let aliasKey = HomeMenuRouter.menuAliasKey(for: uri)
|
||
return commonURIs.contains { HomeMenuRouter.menuAliasKey(for: $0) == aliasKey }
|
||
}
|
||
|
||
private func toggleCommon(_ item: HomeMenuItem, isCommon: Bool) {
|
||
if isCommon {
|
||
commonURIs = commonMenuStore.remove(item.uri, current: commonURIs)
|
||
} else {
|
||
commonURIs = commonMenuStore.add(item.uri, current: commonURIs, menuItems: viewModel.menuItems)
|
||
}
|
||
tableView.reloadData()
|
||
}
|
||
|
||
private func openMenu(_ item: HomeMenuItem) {
|
||
let route = HomeMenuRouter.resolve(uri: item.uri, title: item.title)
|
||
if case .destination(let homeRoute) = route, homeRoute == .moreFunctions { return }
|
||
HomeMenuRouting.openRoute(route, from: self)
|
||
}
|
||
}
|
||
|
||
extension HomeMoreFunctionsViewController: UITableViewDataSource, UITableViewDelegate {
|
||
func numberOfSections(in tableView: UITableView) -> Int { 2 }
|
||
|
||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 1 }
|
||
|
||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||
section == 0 ? "常用应用" : "更多功能"
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: HomeMoreMenuGridCell.reuseID, for: indexPath) as! HomeMoreMenuGridCell
|
||
let isCommon = indexPath.section == 0
|
||
let items = isCommon ? commonItems : moreItems
|
||
cell.configure(items: items, isCommon: isCommon) { [weak self] item in
|
||
self?.openMenu(item)
|
||
} onToggle: { [weak self] item in
|
||
self?.toggleCommon(item, isCommon: isCommon)
|
||
}
|
||
return cell
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||
let count = indexPath.section == 0 ? commonItems.count : moreItems.count
|
||
let rows = max(1, Int(ceil(Double(count) / 3.0)))
|
||
return CGFloat(rows) * 124 + 8
|
||
}
|
||
}
|
||
|
||
private final class HomeMoreMenuGridCell: UITableViewCell {
|
||
static let reuseID = "HomeMoreMenuGridCell"
|
||
|
||
private var items: [HomeMenuItem] = []
|
||
private var isCommon = false
|
||
private var onSelect: ((HomeMenuItem) -> Void)?
|
||
private var onToggle: ((HomeMenuItem) -> Void)?
|
||
|
||
private lazy var collectionView: UICollectionView = {
|
||
let layout = UICollectionViewFlowLayout()
|
||
layout.minimumInteritemSpacing = 14
|
||
layout.minimumLineSpacing = AppMetrics.Spacing.mediumLarge
|
||
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||
view.backgroundColor = .clear
|
||
view.isScrollEnabled = false
|
||
view.dataSource = self
|
||
view.delegate = self
|
||
view.register(HomeMoreMenuItemCell.self, forCellWithReuseIdentifier: HomeMoreMenuItemCell.reuseID)
|
||
return view
|
||
}()
|
||
|
||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||
backgroundColor = .clear
|
||
selectionStyle = .none
|
||
contentView.addSubview(collectionView)
|
||
collectionView.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview().inset(AppMetrics.Spacing.mediumLarge)
|
||
}
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
func configure(
|
||
items: [HomeMenuItem],
|
||
isCommon: Bool,
|
||
onSelect: @escaping (HomeMenuItem) -> Void,
|
||
onToggle: @escaping (HomeMenuItem) -> Void
|
||
) {
|
||
self.items = items
|
||
self.isCommon = isCommon
|
||
self.onSelect = onSelect
|
||
self.onToggle = onToggle
|
||
collectionView.reloadData()
|
||
}
|
||
}
|
||
|
||
extension HomeMoreMenuGridCell: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
|
||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||
items.count
|
||
}
|
||
|
||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeMoreMenuItemCell.reuseID, for: indexPath) as! HomeMoreMenuItemCell
|
||
let item = items[indexPath.item]
|
||
cell.configure(item: item, isCommon: isCommon) { [weak self] in
|
||
self?.onToggle?(item)
|
||
}
|
||
return cell
|
||
}
|
||
|
||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||
onSelect?(items[indexPath.item])
|
||
}
|
||
|
||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||
let width = (collectionView.bounds.width - 28) / 3
|
||
return CGSize(width: width, height: 112)
|
||
}
|
||
}
|
||
|
||
private final class HomeMoreMenuItemCell: UICollectionViewCell {
|
||
static let reuseID = "HomeMoreMenuItemCell"
|
||
|
||
private let iconView = UIImageView()
|
||
private let titleLabel = UILabel()
|
||
private let toggleButton = UIButton(type: .system)
|
||
private var onToggle: (() -> Void)?
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
contentView.backgroundColor = .white
|
||
contentView.layer.cornerRadius = 8
|
||
|
||
titleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.callout)
|
||
titleLabel.textColor = UIColor(hex: 0x252525)
|
||
titleLabel.textAlignment = .center
|
||
titleLabel.numberOfLines = 1
|
||
titleLabel.adjustsFontSizeToFitWidth = true
|
||
|
||
toggleButton.addTarget(self, action: #selector(toggleTapped), for: .touchUpInside)
|
||
contentView.addSubview(toggleButton)
|
||
|
||
let stack = UIStackView(arrangedSubviews: [iconView, titleLabel])
|
||
stack.axis = .vertical
|
||
stack.spacing = AppMetrics.Spacing.mediumLarge + 1
|
||
stack.alignment = .center
|
||
contentView.addSubview(stack)
|
||
|
||
stack.snp.makeConstraints { make in
|
||
make.center.equalToSuperview()
|
||
make.leading.trailing.equalToSuperview().inset(4)
|
||
}
|
||
iconView.snp.makeConstraints { make in
|
||
make.width.height.equalTo(34)
|
||
}
|
||
toggleButton.snp.makeConstraints { make in
|
||
make.top.trailing.equalToSuperview().inset(2)
|
||
make.width.height.equalTo(28)
|
||
}
|
||
iconView.contentMode = .scaleAspectFit
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
func configure(item: HomeMenuItem, isCommon: Bool, onToggle: @escaping () -> Void) {
|
||
self.onToggle = onToggle
|
||
titleLabel.text = item.title
|
||
let symbol = UIImage(systemName: HomeIconCatalog.iconName(for: item.uri))
|
||
iconView.loadRemoteImage(urlString: item.iconSrc, contentMode: .scaleAspectFit, placeholder: symbol)
|
||
iconView.tintColor = AppDesign.primary
|
||
let iconName = isCommon ? "minus.circle.fill" : "plus.circle.fill"
|
||
toggleButton.setImage(UIImage(systemName: iconName), for: .normal)
|
||
toggleButton.tintColor = isCommon ? UIColor(hex: 0xFF1111) : AppDesignUIKit.primary
|
||
}
|
||
|
||
@objc private func toggleTapped() {
|
||
onToggle?()
|
||
}
|
||
}
|