584 lines
22 KiB
Swift
584 lines
22 KiB
Swift
//
|
||
// HomeViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 首页工作台,展示景区头部、工作状态、位置上报卡片和常用应用网格。
|
||
final class HomeViewController: UIViewController {
|
||
|
||
private let viewModel = HomeViewModel()
|
||
private let commonMenuStore = HomeCommonMenuStore()
|
||
private var commonURIs: [String] = []
|
||
private var isOnline = false
|
||
private var reminderMinutes = 0
|
||
private var secondsUntilReport = 0
|
||
private var countdownTimer: Timer?
|
||
|
||
private let minimalTopRoleIds: Set<Int> = [46, 47, 52, 53, 54]
|
||
|
||
private lazy var scenicButton: UIButton = {
|
||
var config = UIButton.Configuration.plain()
|
||
config.baseForegroundColor = UIColor(hex: 0x333333)
|
||
config.image = UIImage(systemName: "chevron.down")?.withConfiguration(
|
||
UIImage.SymbolConfiguration(pointSize: AppMetrics.FontSize.subheadline, weight: .bold)
|
||
)
|
||
config.imagePlacement = .trailing
|
||
config.imagePadding = AppMetrics.Spacing.xSmall
|
||
config.contentInsets = .zero
|
||
let button = UIButton(configuration: config)
|
||
button.contentHorizontalAlignment = .leading
|
||
button.addTarget(self, action: #selector(scenicTapped), for: .touchUpInside)
|
||
return button
|
||
}()
|
||
|
||
private lazy var tableView: UITableView = {
|
||
let table = UITableView(frame: .zero, style: .grouped)
|
||
table.backgroundColor = AppDesignUIKit.pageBackground
|
||
table.separatorStyle = .none
|
||
table.showsVerticalScrollIndicator = false
|
||
table.dataSource = self
|
||
table.delegate = self
|
||
table.register(HomeMenuGridCell.self, forCellReuseIdentifier: HomeMenuGridCell.reuseID)
|
||
table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
|
||
return table
|
||
}()
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
view.backgroundColor = AppDesignUIKit.pageBackground
|
||
setupTopBar()
|
||
setupTableView()
|
||
bindViewModel()
|
||
rebuildMenus()
|
||
observeContextChanges()
|
||
}
|
||
|
||
override func viewWillAppear(_ animated: Bool) {
|
||
super.viewWillAppear(animated)
|
||
navigationController?.setNavigationBarHidden(true, animated: animated)
|
||
startCountdownTimerIfNeeded()
|
||
}
|
||
|
||
override func viewWillDisappear(_ animated: Bool) {
|
||
super.viewWillDisappear(animated)
|
||
countdownTimer?.invalidate()
|
||
countdownTimer = nil
|
||
}
|
||
|
||
private func setupTopBar() {
|
||
let topBar = UIView()
|
||
topBar.backgroundColor = .white
|
||
view.addSubview(topBar)
|
||
topBar.addSubview(scenicButton)
|
||
|
||
topBar.snp.makeConstraints { make in
|
||
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
|
||
make.height.equalTo(78)
|
||
}
|
||
scenicButton.snp.makeConstraints { make in
|
||
make.leading.trailing.equalToSuperview().inset(AppMetrics.Spacing.pageHorizontal)
|
||
make.centerY.equalToSuperview()
|
||
}
|
||
updateScenicTitle()
|
||
}
|
||
|
||
private func setupTableView() {
|
||
view.addSubview(tableView)
|
||
tableView.snp.makeConstraints { make in
|
||
make.top.equalTo(view.safeAreaLayoutGuide).offset(78)
|
||
make.leading.trailing.bottom.equalToSuperview()
|
||
}
|
||
}
|
||
|
||
private func bindViewModel() {
|
||
viewModel.onChange = { [weak self] in
|
||
self?.tableView.reloadData()
|
||
}
|
||
}
|
||
|
||
private func observeContextChanges() {
|
||
let services = appServices
|
||
services.permissionContext.onChange = { [weak self] in
|
||
self?.rebuildMenus()
|
||
}
|
||
services.accountContext.onChange = { [weak self] in
|
||
self?.updateScenicTitle()
|
||
self?.tableView.reloadData()
|
||
}
|
||
}
|
||
|
||
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 func updateScenicTitle() {
|
||
let name = appServices.accountContext.currentScenic?.name ?? "请选择景区"
|
||
scenicButton.configuration?.attributedTitle = AttributedString(
|
||
name,
|
||
attributes: AttributeContainer([
|
||
.font: UIFont.systemFont(ofSize: AppMetrics.FontSize.title3, weight: .semibold)
|
||
])
|
||
)
|
||
}
|
||
|
||
private var currentRoleId: Int? {
|
||
appServices.permissionContext.currentRole?.id
|
||
}
|
||
|
||
private var shouldShowWorkStatus: Bool {
|
||
guard let currentRoleId else { return true }
|
||
return !minimalTopRoleIds.contains(currentRoleId)
|
||
}
|
||
|
||
private var isStoreManager: Bool {
|
||
currentRoleId == 46
|
||
}
|
||
|
||
private var displayMenuItems: [HomeMenuItem] {
|
||
let selected = commonURIs.compactMap { menuItem(for: $0) }
|
||
let fallback = selected.isEmpty ? Array(viewModel.menuItems.prefix(3)) : selected
|
||
return fallback + [HomeMenuItem(title: "更多功能", uri: "more_functions", iconSrc: nil)]
|
||
}
|
||
|
||
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 var countdownDisplay: String {
|
||
let hours = secondsUntilReport / 3_600
|
||
let minutes = (secondsUntilReport % 3_600) / 60
|
||
let seconds = secondsUntilReport % 60
|
||
return "\(hours):\(String(format: "%02d", minutes)):\(String(format: "%02d", seconds))"
|
||
}
|
||
|
||
private var reminderText: String {
|
||
reminderMinutes == 0 ? "不提醒" : "提前\(reminderMinutes)分钟"
|
||
}
|
||
|
||
private func startCountdownTimerIfNeeded() {
|
||
countdownTimer?.invalidate()
|
||
guard isOnline else { return }
|
||
countdownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
|
||
guard let self, self.isOnline, self.secondsUntilReport > 0 else { return }
|
||
self.secondsUntilReport -= 1
|
||
self.tableView.reloadSections(IndexSet(integer: 0), with: .none)
|
||
}
|
||
}
|
||
|
||
@objc private func scenicTapped() {
|
||
HomeMenuRouting.push(.scenicSelection, from: self)
|
||
}
|
||
|
||
@objc private func onlineTapped() {
|
||
let message = isOnline
|
||
? "是否确认切换为离线状态?离线后将暂停位置上报。"
|
||
: "是否确认切换为在线状态?在线后将开始位置上报和计时。"
|
||
let alert = UIAlertController(title: "切换在线状态", message: message, preferredStyle: .alert)
|
||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||
alert.addAction(UIAlertAction(title: "确定", style: .default) { [weak self] _ in
|
||
guard let self else { return }
|
||
self.isOnline.toggle()
|
||
if self.isOnline {
|
||
self.secondsUntilReport = 7_200
|
||
self.startCountdownTimerIfNeeded()
|
||
} else {
|
||
self.countdownTimer?.invalidate()
|
||
}
|
||
self.tableView.reloadSections(IndexSet(integer: 0), with: .none)
|
||
})
|
||
present(alert, animated: true)
|
||
}
|
||
|
||
@objc private func reminderTapped() {
|
||
let sheet = UIAlertController(title: "提前提醒时间", message: nil, preferredStyle: .actionSheet)
|
||
for minute in [0, 5, 10, 15, 30] {
|
||
let title = minute == 0 ? "不提醒" : "\(minute)分钟"
|
||
sheet.addAction(UIAlertAction(title: title, style: .default) { [weak self] _ in
|
||
self?.reminderMinutes = minute
|
||
self?.tableView.reloadSections(IndexSet(integer: 0), with: .none)
|
||
})
|
||
}
|
||
sheet.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||
present(sheet, animated: true)
|
||
}
|
||
|
||
@objc private func locationReportTapped() {
|
||
HomeMenuRouting.openRoute(HomeMenuRouter.resolve(uri: "location_report", title: "位置上报"), from: self)
|
||
}
|
||
|
||
@objc private func paymentTapped() {
|
||
HomeMenuRouting.openRoute(HomeMenuRouter.resolve(uri: "payment_collection", title: "立即收款"), from: self)
|
||
}
|
||
|
||
@objc private func taskCreateTapped() {
|
||
HomeMenuRouting.openRoute(HomeMenuRouter.resolve(uri: "task_create", title: "提交任务"), from: self)
|
||
}
|
||
}
|
||
|
||
// MARK: - UITableView
|
||
|
||
extension HomeViewController: UITableViewDataSource, UITableViewDelegate {
|
||
func numberOfSections(in tableView: UITableView) -> Int {
|
||
var count = 1
|
||
if shouldShowWorkStatus { count += 2 }
|
||
if isStoreManager, appServices.accountContext.currentStore != nil { count += 1 }
|
||
return count
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||
1
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||
if section == tableView.numberOfSections - 1 {
|
||
return "常用应用"
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||
if indexPath.section == tableView.numberOfSections - 1 {
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: HomeMenuGridCell.reuseID, for: indexPath) as! HomeMenuGridCell
|
||
cell.configure(items: displayMenuItems) { [weak self] item in
|
||
self.flatMap { HomeMenuRouting.openMenu(item, from: $0) }
|
||
}
|
||
return cell
|
||
}
|
||
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
|
||
cell.selectionStyle = .none
|
||
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
|
||
|
||
if shouldShowWorkStatus {
|
||
if indexPath.section == 0 {
|
||
cell.contentView.addSubview(makeStatusCard())
|
||
} else if indexPath.section == 1 {
|
||
cell.contentView.addSubview(makeLocationCard())
|
||
} else if indexPath.section == 2, isStoreManager, let store = appServices.accountContext.currentStore {
|
||
cell.contentView.addSubview(makeStoreCard(store))
|
||
} else {
|
||
cell.contentView.addSubview(makeQuickActionsRow())
|
||
}
|
||
} else if isStoreManager, let store = appServices.accountContext.currentStore, indexPath.section == 0 {
|
||
cell.contentView.addSubview(makeStoreCard(store))
|
||
}
|
||
|
||
cell.contentView.subviews.first?.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview().inset(UIEdgeInsets(
|
||
top: AppMetrics.Spacing.xxSmall,
|
||
left: AppMetrics.Spacing.pageHorizontal,
|
||
bottom: AppMetrics.Spacing.xxSmall,
|
||
right: AppMetrics.Spacing.pageHorizontal
|
||
))
|
||
}
|
||
cell.backgroundColor = .clear
|
||
return cell
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||
if indexPath.section == tableView.numberOfSections - 1 { return 240 }
|
||
if shouldShowWorkStatus {
|
||
switch indexPath.section {
|
||
case 0: return 100
|
||
case 1: return 148
|
||
case 2 where isStoreManager && appServices.accountContext.currentStore != nil: return 88
|
||
default: return 118
|
||
}
|
||
}
|
||
if isStoreManager, appServices.accountContext.currentStore != nil, indexPath.section == 0 { return 88 }
|
||
return UITableView.automaticDimension
|
||
}
|
||
|
||
private func makeStatusCard() -> UIView {
|
||
let card = makeCardView()
|
||
|
||
let onlineButton = UIButton(type: .system)
|
||
onlineButton.setTitle(isOnline ? "在线" : "离线", for: .normal)
|
||
onlineButton.titleLabel?.font = .systemFont(ofSize: AppMetrics.FontSize.caption, weight: .medium)
|
||
onlineButton.setTitleColor(isOnline ? UIColor(hex: 0xF0FDF4) : UIColor(hex: 0x7B8EAA), for: .normal)
|
||
onlineButton.backgroundColor = isOnline ? UIColor(hex: 0x22C55E) : UIColor(hex: 0xF4F4F4)
|
||
onlineButton.layer.cornerRadius = 4
|
||
onlineButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12)
|
||
onlineButton.addTarget(self, action: #selector(onlineTapped), for: .touchUpInside)
|
||
|
||
let clockLabel = UILabel()
|
||
clockLabel.text = " \(countdownDisplay)"
|
||
clockLabel.font = .systemFont(ofSize: AppMetrics.FontSize.body, weight: .medium)
|
||
clockLabel.textColor = AppDesignUIKit.primary
|
||
|
||
let reminderButton = UIButton(type: .system)
|
||
reminderButton.setTitle(" \(reminderText)", for: .normal)
|
||
reminderButton.titleLabel?.font = .systemFont(ofSize: AppMetrics.FontSize.body, weight: .medium)
|
||
reminderButton.setTitleColor(AppDesignUIKit.primary, for: .normal)
|
||
reminderButton.addTarget(self, action: #selector(reminderTapped), for: .touchUpInside)
|
||
|
||
let stack = UIStackView(arrangedSubviews: [onlineButton, clockLabel, reminderButton])
|
||
stack.axis = .horizontal
|
||
stack.distribution = .equalSpacing
|
||
stack.alignment = .center
|
||
card.addSubview(stack)
|
||
stack.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview().inset(15)
|
||
}
|
||
return card
|
||
}
|
||
|
||
private func makeLocationCard() -> UIView {
|
||
let card = makeCardView()
|
||
|
||
let titleLabel = UILabel()
|
||
titleLabel.text = "立即上报"
|
||
titleLabel.font = .systemFont(ofSize: 22, weight: .bold)
|
||
titleLabel.textColor = UIColor(hex: 0x333333)
|
||
|
||
let subtitleLabel = UILabel()
|
||
subtitleLabel.text = "您已进入打卡范围"
|
||
subtitleLabel.font = .systemFont(ofSize: 15)
|
||
subtitleLabel.textColor = UIColor(hex: 0x999999)
|
||
|
||
let textStack = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
|
||
textStack.axis = .vertical
|
||
textStack.spacing = AppMetrics.Spacing.xxSmall
|
||
|
||
let actionButton = UIButton(type: .system)
|
||
actionButton.setImage(UIImage(systemName: "hand.tap.fill"), for: .normal)
|
||
actionButton.tintColor = .white
|
||
actionButton.backgroundColor = AppDesignUIKit.primary
|
||
actionButton.layer.cornerRadius = 46
|
||
actionButton.addTarget(self, action: #selector(locationReportTapped), for: .touchUpInside)
|
||
|
||
card.addSubview(textStack)
|
||
card.addSubview(actionButton)
|
||
textStack.snp.makeConstraints { make in
|
||
make.leading.top.bottom.equalToSuperview().inset(15)
|
||
make.trailing.lessThanOrEqualTo(actionButton.snp.leading).offset(-12)
|
||
}
|
||
actionButton.snp.makeConstraints { make in
|
||
make.trailing.equalToSuperview().inset(15)
|
||
make.centerY.equalToSuperview()
|
||
make.width.height.equalTo(92)
|
||
}
|
||
return card
|
||
}
|
||
|
||
private func makeQuickActionsRow() -> UIView {
|
||
let stack = UIStackView()
|
||
stack.axis = .horizontal
|
||
stack.spacing = AppMetrics.Spacing.small
|
||
stack.distribution = .fillEqually
|
||
|
||
stack.addArrangedSubview(quickActionButton(icon: "qrcode", title: "立即收款", action: #selector(paymentTapped)))
|
||
stack.addArrangedSubview(quickActionButton(icon: "checklist.checked", title: "提交任务", action: #selector(taskCreateTapped)))
|
||
stack.addArrangedSubview(quickActionButton(
|
||
icon: isOnline ? "wifi" : "wifi.slash",
|
||
title: isOnline ? "在线" : "离线",
|
||
action: #selector(onlineTapped),
|
||
active: isOnline
|
||
))
|
||
return stack
|
||
}
|
||
|
||
private func quickActionButton(icon: String, title: String, action: Selector, active: Bool = false) -> UIView {
|
||
let card = makeCardView()
|
||
card.backgroundColor = active ? UIColor(hex: 0xE3F2FD) : .white
|
||
|
||
let iconView = UIImageView(image: UIImage(systemName: icon))
|
||
iconView.tintColor = AppDesignUIKit.primary
|
||
iconView.contentMode = .scaleAspectFit
|
||
|
||
let label = UILabel()
|
||
label.text = title
|
||
label.font = .systemFont(ofSize: AppMetrics.FontSize.body)
|
||
label.textAlignment = .center
|
||
|
||
let stack = UIStackView(arrangedSubviews: [iconView, label])
|
||
stack.axis = .vertical
|
||
stack.spacing = AppMetrics.Spacing.xSmall
|
||
stack.alignment = .center
|
||
card.addSubview(stack)
|
||
stack.snp.makeConstraints { make in
|
||
make.center.equalToSuperview()
|
||
}
|
||
iconView.snp.makeConstraints { make in
|
||
make.height.equalTo(34)
|
||
}
|
||
|
||
let button = UIButton(type: .custom)
|
||
button.addTarget(self, action: action, for: .touchUpInside)
|
||
card.addSubview(button)
|
||
button.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
card.snp.makeConstraints { make in
|
||
make.height.equalTo(102)
|
||
}
|
||
return card
|
||
}
|
||
|
||
private func makeStoreCard(_ store: BusinessScope) -> UIView {
|
||
let card = makeCardView()
|
||
|
||
let nameLabel = UILabel()
|
||
nameLabel.text = store.name
|
||
nameLabel.font = .systemFont(ofSize: AppMetrics.FontSize.title2, weight: .bold)
|
||
|
||
let scenicLabel = UILabel()
|
||
scenicLabel.text = appServices.accountContext.currentScenic?.name
|
||
scenicLabel.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline)
|
||
scenicLabel.textColor = UIColor(hex: 0x7B8EAA)
|
||
scenicLabel.numberOfLines = 2
|
||
|
||
let statusLabel = UILabel()
|
||
statusLabel.text = "营业中"
|
||
statusLabel.font = .systemFont(ofSize: AppMetrics.FontSize.caption)
|
||
statusLabel.textColor = UIColor(hex: 0x22C55E)
|
||
statusLabel.backgroundColor = UIColor(hex: 0xF0FDF4)
|
||
statusLabel.layer.cornerRadius = 4
|
||
statusLabel.clipsToBounds = true
|
||
statusLabel.textAlignment = .center
|
||
|
||
let textStack = UIStackView(arrangedSubviews: [nameLabel, scenicLabel])
|
||
textStack.axis = .vertical
|
||
textStack.spacing = AppMetrics.Spacing.xSmall
|
||
|
||
card.addSubview(textStack)
|
||
card.addSubview(statusLabel)
|
||
textStack.snp.makeConstraints { make in
|
||
make.leading.top.bottom.equalToSuperview().inset(AppMetrics.Spacing.medium)
|
||
make.trailing.lessThanOrEqualTo(statusLabel.snp.leading).offset(-8)
|
||
}
|
||
statusLabel.snp.makeConstraints { make in
|
||
make.trailing.top.equalToSuperview().inset(AppMetrics.Spacing.medium)
|
||
make.width.greaterThanOrEqualTo(52)
|
||
make.height.equalTo(22)
|
||
}
|
||
return card
|
||
}
|
||
}
|
||
|
||
// MARK: - Menu Grid Cell
|
||
|
||
private final class HomeMenuGridCell: UITableViewCell {
|
||
static let reuseID = "HomeMenuGridCell"
|
||
|
||
private var onSelect: ((HomeMenuItem) -> Void)?
|
||
private var items: [HomeMenuItem] = []
|
||
|
||
private lazy var collectionView: UICollectionView = {
|
||
let layout = UICollectionViewFlowLayout()
|
||
layout.minimumInteritemSpacing = 15
|
||
layout.minimumLineSpacing = 15
|
||
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||
view.backgroundColor = .clear
|
||
view.isScrollEnabled = false
|
||
view.dataSource = self
|
||
view.delegate = self
|
||
view.register(HomeMenuItemCell.self, forCellWithReuseIdentifier: HomeMenuItemCell.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.pageHorizontal)
|
||
make.height.equalTo(220)
|
||
}
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
func configure(items: [HomeMenuItem], onSelect: @escaping (HomeMenuItem) -> Void) {
|
||
self.items = items
|
||
self.onSelect = onSelect
|
||
collectionView.reloadData()
|
||
}
|
||
}
|
||
|
||
extension HomeMenuGridCell: 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: HomeMenuItemCell.reuseID, for: indexPath) as! HomeMenuItemCell
|
||
cell.configure(item: items[indexPath.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 - 30) / 3
|
||
return CGSize(width: width, height: 102)
|
||
}
|
||
}
|
||
|
||
private final class HomeMenuItemCell: UICollectionViewCell {
|
||
static let reuseID = "HomeMenuItemCell"
|
||
|
||
private let iconView = UIImageView()
|
||
private let titleLabel = UILabel()
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
contentView.backgroundColor = .white
|
||
contentView.layer.cornerRadius = 8
|
||
|
||
titleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.body)
|
||
titleLabel.textColor = UIColor(hex: 0x4B5563)
|
||
titleLabel.textAlignment = .center
|
||
titleLabel.numberOfLines = 1
|
||
titleLabel.adjustsFontSizeToFitWidth = true
|
||
|
||
let stack = UIStackView(arrangedSubviews: [iconView, titleLabel])
|
||
stack.axis = .vertical
|
||
stack.spacing = AppMetrics.Spacing.small
|
||
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(30)
|
||
}
|
||
iconView.contentMode = .scaleAspectFit
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
func configure(item: HomeMenuItem) {
|
||
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
|
||
}
|
||
}
|