171 lines
4.8 KiB
Swift
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)
|
|
}
|
|
}
|