Files
suixinkan_uikit/suixinkan/UI/Home/HomeCollectionModels.swift
汉秋 715393e78c Refactor home to single collection view and add all-functions customization.
Replace scroll/stack layout with one UICollectionView, fix grid column math and Diffable item IDs so location report and quick actions render, and add the customizable all-functions page with persistence and tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 09:57:41 +08:00

166 lines
5.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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: CGFloat = 15
private static let menuAspectRatio: CGFloat = 1.3
private static let sectionSpacing = AppSpacing.sm
/// Compositional Layout section
static func makeLayout(sections: [HomeCollectionSection]) -> UICollectionViewCompositionalLayout {
UICollectionViewCompositionalLayout { sectionIndex, environment in
guard sectionIndex < sections.count else { return nil }
switch sections[sectionIndex] {
case .workStatus:
return fullWidthSection(height: AppSpacing.formRowHeight)
case .locationReport:
return fullWidthSection(height: 100)
case .quickActions:
return fullWidthSection(height: 72)
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 availableWidth = environment.container.effectiveContentSize.width - horizontalInset * 2
let totalSpacing = menuSpacing * CGFloat(menuColumns - 1)
let itemWidth = max(0, (availableWidth - totalSpacing) / CGFloat(menuColumns))
let itemHeight = itemWidth / menuAspectRatio
let itemSize = NSCollectionLayoutSize(
widthDimension: .absolute(itemWidth),
heightDimension: .absolute(itemHeight)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .absolute(itemHeight)
)
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
}
}