mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
85 lines
3.1 KiB
Swift
85 lines
3.1 KiB
Swift
//
|
||
// Copyright 2026 Element Creations Ltd.
|
||
//
|
||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||
// Please see LICENSE files in the repository root for full details.
|
||
//
|
||
|
||
import Compound
|
||
import SwiftUI
|
||
|
||
struct GalleryGridView: View {
|
||
let items: [GalleryItem]
|
||
let mediaProvider: MediaProviderProtocol?
|
||
let contentScannerService: ContentScannerServiceProtocol?
|
||
let onItemTap: (Int) -> Void
|
||
|
||
static let spacing: CGFloat = 4
|
||
static let groupWidth: CGFloat = 264
|
||
static let maxVisible = 5
|
||
|
||
private var visibleCount: Int {
|
||
min(items.count, Self.maxVisible)
|
||
}
|
||
|
||
/// Number shown on the overflow tile when items.count > maxVisible.
|
||
private var overflowCount: Int {
|
||
// Represents the last visible item + the rest.
|
||
items.count > Self.maxVisible ? items.count - Self.maxVisible + 1 : 0
|
||
}
|
||
|
||
private struct Row {
|
||
let indices: [Int]
|
||
let height: CGFloat
|
||
}
|
||
|
||
/// Hero-style layout: 1 = full-width, 2 = two full-width stacked rows, 3 = full-width hero
|
||
/// over two squares, 4 = 2×2 squares, 5+ = top row two squares, bottom row three squares.
|
||
private var rows: [Row] {
|
||
switch visibleCount {
|
||
case 1: return [Row(indices: [0], height: 130)]
|
||
case 2: return [Row(indices: [0], height: 130),
|
||
Row(indices: [1], height: 130)]
|
||
case 3: return [Row(indices: [0], height: 130),
|
||
Row(indices: [1, 2], height: 130)]
|
||
case 4: return [Row(indices: [0, 1], height: 130),
|
||
Row(indices: [2, 3], height: 130)]
|
||
case 5: return [Row(indices: [0, 1], height: 130),
|
||
Row(indices: [2, 3, 4], height: 85)]
|
||
default: return []
|
||
}
|
||
}
|
||
|
||
var body: some View {
|
||
VStack(spacing: Self.spacing) {
|
||
ForEach(Array(rows.enumerated()), id: \.offset) { _, row in
|
||
let totalSpacing = CGFloat(row.indices.count - 1) * Self.spacing
|
||
let tileWidth = (Self.groupWidth - totalSpacing) / CGFloat(row.indices.count)
|
||
HStack(spacing: Self.spacing) {
|
||
ForEach(row.indices, id: \.self) { index in
|
||
tile(at: index, size: CGSize(width: tileWidth, height: row.height))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||
}
|
||
|
||
@ViewBuilder
|
||
private func tile(at index: Int, size: CGSize) -> some View {
|
||
if items.indices.contains(index) {
|
||
let isLastVisible = index == visibleCount - 1
|
||
let overflow = (overflowCount > 0 && isLastVisible) ? overflowCount : 0
|
||
|
||
GalleryItemTileView(item: items[index],
|
||
position: index + 1,
|
||
mediaProvider: mediaProvider,
|
||
contentScannerService: contentScannerService,
|
||
overflowCount: overflow) {
|
||
onItemTap(index)
|
||
}
|
||
.frame(width: size.width, height: size.height)
|
||
}
|
||
}
|
||
}
|