Caps-iOS/Caps/Views/GridView.swift
Christoph Hagen b767301aa0 Fix crash
2022-12-11 19:25:58 +01:00

171 lines
4.8 KiB
Swift

import SwiftUI
import SFSafeSymbols
struct GridView: View {
let database: Database
@AppStorage("currentGridName")
private(set) var currentGridName = "default"
private var defaultImageGrid: ImageGrid {
.init(columns: 40, capPlacements: database.caps.keys.sorted())
}
var imageSize: CGFloat {
CapsApp.thumbnailImageSize
}
private let verticalInsetFactor: CGFloat = cos(.pi / 6)
private let minScale: CGFloat = 1.0
private let maxScale: CGFloat = 0.5
private let cancelButtonSize: CGFloat = 75
private let cancelIconSize: CGFloat = 25
@Binding
var isPresented: Bool
var image: ImageGrid
@State var scale: CGFloat = 1.0
@State var lastScaleValue: CGFloat = 1.0
init(isPresented: Binding<Bool>, database: Database) {
self._isPresented = isPresented
self.database = database
self.image = .init(columns: 1, capPlacements: [])
if let image = database.load(grid: currentGridName) {
self.image = image
} else {
self.image = defaultImageGrid
currentGridName = "default"
}
}
var columnCount: Int {
image.columns
}
var capCount: Int {
image.capCount
}
var imageHeight: CGFloat {
(CGFloat(capCount) / CGFloat(columnCount)).rounded(.up) * verticalInsetFactor * imageSize + (1-verticalInsetFactor) * imageSize
}
var imageWidth: CGFloat {
imageSize * (CGFloat(columnCount) + 0.5)
}
var magnificationGesture: some Gesture {
MagnificationGesture()
.onChanged { val in
let delta = val / self.lastScaleValue
self.lastScaleValue = val
self.scale = max(min(self.scale * delta, minScale), maxScale)
}
.onEnded { val in
// without this the next gesture will be broken
self.lastScaleValue = 1.0
}
}
var gridView: some View {
let gridItems = Array(repeating: GridItem(.fixed(imageSize), spacing: 0), count: columnCount)
return LazyVGrid(columns: gridItems, alignment: .leading, spacing: 0) {
ForEach(image.items) { item in
CachedCapImage(
item.id,
check: { cachedImage(item.cap) },
fetch: { await fetchImage(item.cap) },
content: { $0.resizable() },
placeholder: { ProgressView() })
.frame(width: imageSize,
height: imageSize)
.clipShape(Circle())
.offset(x: isEvenRow(item.id) ? 0 : imageSize / 2)
.frame(width: imageSize,
height: imageSize * verticalInsetFactor)
}
}
.frame(width: imageWidth)
}
var body: some View {
ZStack {
ScrollView([.vertical, .horizontal]) {
gridView
.scaleEffect(scale)
.frame(
width: imageWidth * scale,
height: imageHeight * scale
)
}
.gesture(magnificationGesture)
.onDisappear {
database.save(image, named: currentGridName)
}
VStack {
Spacer()
HStack {
Spacer()
IconButton(action: saveScreenshot,
icon: .squareAndArrowDown,
iconSize: cancelIconSize,
buttonSize: cancelButtonSize)
.padding()
IconButton(action: dismiss,
icon: .xmark,
iconSize: cancelIconSize,
buttonSize: cancelButtonSize)
.padding()
}
}
}
}
func isEvenRow(_ idx: Int) -> Bool {
(idx / columnCount) % 2 == 0
}
private func cachedImage(_ cap: Int) -> UIImage? {
let image = CapImage(
cap: cap,
version: database
.mainImage(for: cap))
return database.images.cachedImage(image)
}
private func fetchImage(_ cap: Int) async -> UIImage? {
let image = CapImage(
cap: cap,
version: database
.mainImage(for: cap))
return await database.images.thumbnail(for: image)
}
private func dismiss() {
isPresented = false
}
private func saveScreenshot() {
let image = gridView.snapshot()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
struct GridView_Previews: PreviewProvider {
static var previews: some View {
GridView(isPresented: .constant(true), database: .largeMock)
}
}