172 lines
6.2 KiB
Swift
172 lines
6.2 KiB
Swift
//
|
||
// HomeCollectionModels.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import Foundation
|
||
import UIKit
|
||
|
||
/// 首页 CollectionView 分区标识。
|
||
enum HomeCollectionSection: Hashable {
|
||
case workStatus
|
||
case locationReport
|
||
case quickActions
|
||
case store
|
||
case commonApps
|
||
}
|
||
|
||
/// 首页 CollectionView 条目标识;各区块使用独立 case,避免 Diffable 去重。
|
||
enum HomeCollectionItem: Hashable {
|
||
case workStatus
|
||
case locationReport
|
||
case quickActions
|
||
case store
|
||
case menu(HomeMenuItem)
|
||
}
|
||
|
||
/// 首页 CollectionView 布局与 snapshot 构建工具。
|
||
enum HomeCollectionLayoutBuilder {
|
||
|
||
private static let menuColumns = 3
|
||
private static let menuSpacing = AppSpacing.sm
|
||
private static let sectionSpacing = AppSpacing.sm
|
||
|
||
/// 构建 Compositional Layout,按当前分区类型返回 section 布局。
|
||
static func makeLayout(sections: [HomeCollectionSection]) -> UICollectionViewCompositionalLayout {
|
||
UICollectionViewCompositionalLayout { sectionIndex, environment in
|
||
let section = sectionIndex < sections.count ? sections[sectionIndex] : .commonApps
|
||
switch section {
|
||
case .workStatus:
|
||
return fullWidthSection(height: 84)
|
||
case .locationReport:
|
||
return fullWidthSection(height: 132)
|
||
case .quickActions:
|
||
return quickActionsSection(environment: environment)
|
||
case .store:
|
||
return fullWidthSection(height: 96)
|
||
case .commonApps:
|
||
return menuGridSection(environment: environment)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 根据 ViewModel 状态构建 Diffable Snapshot。
|
||
static func makeSnapshot(
|
||
showWorkBlock: Bool,
|
||
showStore: Bool,
|
||
storeItem: StoreItem?,
|
||
commonMenus: [HomeMenuItem]
|
||
) -> NSDiffableDataSourceSnapshot<HomeCollectionSection, HomeCollectionItem> {
|
||
var snapshot = NSDiffableDataSourceSnapshot<HomeCollectionSection, HomeCollectionItem>()
|
||
|
||
if showWorkBlock {
|
||
snapshot.appendSections([.workStatus, .locationReport, .quickActions])
|
||
snapshot.appendItems([.workStatus], toSection: .workStatus)
|
||
snapshot.appendItems([.locationReport], toSection: .locationReport)
|
||
snapshot.appendItems([.quickActions], toSection: .quickActions)
|
||
}
|
||
|
||
if showStore, storeItem != nil {
|
||
snapshot.appendSections([.store])
|
||
snapshot.appendItems([.store], toSection: .store)
|
||
}
|
||
|
||
let menus = commonMenus + [HomeMenuItem.moreFunctions]
|
||
snapshot.appendSections([.commonApps])
|
||
snapshot.appendItems(menus.map { HomeCollectionItem.menu($0) }, toSection: .commonApps)
|
||
|
||
return snapshot
|
||
}
|
||
|
||
/// 当前 snapshot 对应的分区顺序,供 layout 使用。
|
||
static func sectionOrder(
|
||
showWorkBlock: Bool,
|
||
showStore: Bool,
|
||
storeItem: StoreItem?
|
||
) -> [HomeCollectionSection] {
|
||
var sections: [HomeCollectionSection] = []
|
||
if showWorkBlock {
|
||
sections.append(contentsOf: [.workStatus, .locationReport, .quickActions])
|
||
}
|
||
if showStore, storeItem != nil {
|
||
sections.append(.store)
|
||
}
|
||
sections.append(.commonApps)
|
||
return sections
|
||
}
|
||
|
||
private static let horizontalInset = AppSpacing.screenHorizontalInset
|
||
|
||
private static func fullWidthSection(height: CGFloat) -> NSCollectionLayoutSection {
|
||
let itemSize = NSCollectionLayoutSize(
|
||
widthDimension: .fractionalWidth(1),
|
||
heightDimension: .absolute(height)
|
||
)
|
||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||
let groupSize = NSCollectionLayoutSize(
|
||
widthDimension: .fractionalWidth(1),
|
||
heightDimension: .absolute(height)
|
||
)
|
||
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
|
||
let section = NSCollectionLayoutSection(group: group)
|
||
section.contentInsets = NSDirectionalEdgeInsets(
|
||
top: 0,
|
||
leading: horizontalInset,
|
||
bottom: sectionSpacing,
|
||
trailing: horizontalInset
|
||
)
|
||
return section
|
||
}
|
||
|
||
private static func menuGridSection(environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection {
|
||
let itemWidth = menuItemWidth(environment: environment)
|
||
|
||
let itemSize = NSCollectionLayoutSize(
|
||
widthDimension: .absolute(itemWidth),
|
||
heightDimension: .absolute(itemWidth)
|
||
)
|
||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
||
let groupSize = NSCollectionLayoutSize(
|
||
widthDimension: .fractionalWidth(1),
|
||
heightDimension: .absolute(itemWidth)
|
||
)
|
||
let group = NSCollectionLayoutGroup.horizontal(
|
||
layoutSize: groupSize,
|
||
repeatingSubitem: item,
|
||
count: menuColumns
|
||
)
|
||
group.interItemSpacing = .fixed(menuSpacing)
|
||
|
||
let section = NSCollectionLayoutSection(group: group)
|
||
section.interGroupSpacing = menuSpacing
|
||
section.contentInsets = NSDirectionalEdgeInsets(
|
||
top: 0,
|
||
leading: horizontalInset,
|
||
bottom: sectionSpacing,
|
||
trailing: horizontalInset
|
||
)
|
||
|
||
let headerSize = NSCollectionLayoutSize(
|
||
widthDimension: .fractionalWidth(1),
|
||
heightDimension: .absolute(36)
|
||
)
|
||
let header = NSCollectionLayoutBoundarySupplementaryItem(
|
||
layoutSize: headerSize,
|
||
elementKind: UICollectionView.elementKindSectionHeader,
|
||
alignment: .top
|
||
)
|
||
section.boundarySupplementaryItems = [header]
|
||
return section
|
||
}
|
||
|
||
private static func quickActionsSection(environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection {
|
||
fullWidthSection(height: menuItemWidth(environment: environment))
|
||
}
|
||
|
||
private static func menuItemWidth(environment: NSCollectionLayoutEnvironment) -> CGFloat {
|
||
let availableWidth = environment.container.effectiveContentSize.width - horizontalInset * 2
|
||
let totalSpacing = menuSpacing * CGFloat(menuColumns - 1)
|
||
return max(0, (availableWidth - totalSpacing) / CGFloat(menuColumns))
|
||
}
|
||
}
|