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