Add view of all images for a cap
This commit is contained in:
parent
fb90a4847e
commit
9cf1c236e0
@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
88C1511C29A11ADF0080EF4F /* CapImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C1511B29A11ADF0080EF4F /* CapImagesView.swift */; };
|
||||||
88DBE72E285495B100D1573B /* FancyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DBE72D285495B100D1573B /* FancyTextField.swift */; };
|
88DBE72E285495B100D1573B /* FancyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DBE72D285495B100D1573B /* FancyTextField.swift */; };
|
||||||
E20D104A285612AF0019BD91 /* ImageGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D1049285612AF0019BD91 /* ImageGrid.swift */; };
|
E20D104A285612AF0019BD91 /* ImageGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D1049285612AF0019BD91 /* ImageGrid.swift */; };
|
||||||
E20D104C28563DB10019BD91 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D104B28563DB10019BD91 /* ImageCache.swift */; };
|
E20D104C28563DB10019BD91 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D104B28563DB10019BD91 /* ImageCache.swift */; };
|
||||||
@ -50,6 +51,7 @@
|
|||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
88C1511B29A11ADF0080EF4F /* CapImagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapImagesView.swift; sourceTree = "<group>"; };
|
||||||
88DBE72D285495B100D1573B /* FancyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTextField.swift; sourceTree = "<group>"; };
|
88DBE72D285495B100D1573B /* FancyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTextField.swift; sourceTree = "<group>"; };
|
||||||
E20D1049285612AF0019BD91 /* ImageGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrid.swift; sourceTree = "<group>"; };
|
E20D1049285612AF0019BD91 /* ImageGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrid.swift; sourceTree = "<group>"; };
|
||||||
E20D104B28563DB10019BD91 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
E20D104B28563DB10019BD91 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||||
@ -187,6 +189,7 @@
|
|||||||
E2EA00E0283F658E00F7B269 /* SettingsView.swift */,
|
E2EA00E0283F658E00F7B269 /* SettingsView.swift */,
|
||||||
E2EA00CB283EB43E00F7B269 /* SortCaseRowView.swift */,
|
E2EA00CB283EB43E00F7B269 /* SortCaseRowView.swift */,
|
||||||
E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */,
|
E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */,
|
||||||
|
88C1511B29A11ADF0080EF4F /* CapImagesView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -285,6 +288,7 @@
|
|||||||
files = (
|
files = (
|
||||||
E20D10582858CEBD0019BD91 /* IconButton.swift in Sources */,
|
E20D10582858CEBD0019BD91 /* IconButton.swift in Sources */,
|
||||||
E25AAC7E283D855D006E9E7F /* ContentView.swift in Sources */,
|
E25AAC7E283D855D006E9E7F /* ContentView.swift in Sources */,
|
||||||
|
88C1511C29A11ADF0080EF4F /* CapImagesView.swift in Sources */,
|
||||||
E2EA00F328438E6B00F7B269 /* CapNameEntryView.swift in Sources */,
|
E2EA00F328438E6B00F7B269 /* CapNameEntryView.swift in Sources */,
|
||||||
E25AAC8B283D868D006E9E7F /* Classifier.swift in Sources */,
|
E25AAC8B283D868D006E9E7F /* Classifier.swift in Sources */,
|
||||||
E20D10562858CDFA0019BD91 /* View+Extensions.swift in Sources */,
|
E20D10562858CDFA0019BD91 /* View+Extensions.swift in Sources */,
|
||||||
@ -457,7 +461,7 @@
|
|||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -489,7 +493,7 @@
|
|||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.4;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -532,7 +536,7 @@
|
|||||||
repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols";
|
repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 3.0.0;
|
minimumVersion = 4.0.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
E2EA00C8283EACB200F7B269 /* XCRemoteSwiftPackageReference "bottom-sheet" */ = {
|
E2EA00C8283EACB200F7B269 /* XCRemoteSwiftPackageReference "bottom-sheet" */ = {
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
|
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "c8c33d947d8a1c883aa19fd24e14fd738b06e369",
|
"revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c",
|
||||||
"version" : "3.3.2"
|
"version" : "4.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
Binary file not shown.
@ -55,6 +55,12 @@ struct ContentView: View {
|
|||||||
@State
|
@State
|
||||||
private var selectedCapId: Int?
|
private var selectedCapId: Int?
|
||||||
|
|
||||||
|
@State
|
||||||
|
var showImageOverviewForCap = false
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var selectedCapToShowImages: Cap?
|
||||||
|
|
||||||
var filteredCaps: [Cap] {
|
var filteredCaps: [Cap] {
|
||||||
let text = searchString
|
let text = searchString
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
@ -125,7 +131,7 @@ struct ContentView: View {
|
|||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
didTap(cap: cap)
|
didTap(cap: cap)
|
||||||
}
|
}
|
||||||
.swipeActions() {
|
.swipeActions(edge: .trailing) {
|
||||||
Button {
|
Button {
|
||||||
showRenameWindow(for: cap)
|
showRenameWindow(for: cap)
|
||||||
} label: {
|
} label: {
|
||||||
@ -133,6 +139,14 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.tint(.purple)
|
.tint(.purple)
|
||||||
}
|
}
|
||||||
|
.swipeActions(edge: .leading) {
|
||||||
|
Button {
|
||||||
|
showAllImages(for: cap)
|
||||||
|
} label: {
|
||||||
|
Label("Images", systemSymbol: .photoStack)
|
||||||
|
}
|
||||||
|
.tint(.purple)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.refreshable {
|
.refreshable {
|
||||||
refresh()
|
refresh()
|
||||||
@ -260,7 +274,11 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.sheet(isPresented: $showGridView) {
|
.sheet(isPresented: $showGridView) {
|
||||||
GridView(isPresented: $showGridView, database: database)
|
GridView(isPresented: $showGridView, database: database)
|
||||||
}.alert(isPresented: $showNewClassifierAlert) {
|
}
|
||||||
|
.bottomSheet(isPresented: $showImageOverviewForCap, height: 400) {
|
||||||
|
CapImagesView(cap: $selectedCapToShowImages, database: database, isPresented: $showImageOverviewForCap)
|
||||||
|
}
|
||||||
|
.alert(isPresented: $showNewClassifierAlert) {
|
||||||
Alert(title: Text("New classifier available"),
|
Alert(title: Text("New classifier available"),
|
||||||
message: Text("Classifier \(database.serverClassifierVersion) is available. You have version \(database.classifierVersion). Do you want to download it now?"),
|
message: Text("Classifier \(database.serverClassifierVersion) is available. You have version \(database.classifierVersion). Do you want to download it now?"),
|
||||||
primaryButton: .default(Text("Download"), action: downloadClassifier),
|
primaryButton: .default(Text("Download"), action: downloadClassifier),
|
||||||
@ -377,6 +395,11 @@ struct ContentView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func showAllImages(for cap: Cap) {
|
||||||
|
selectedCapToShowImages = cap
|
||||||
|
showImageOverviewForCap = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
|
@ -24,13 +24,21 @@ struct Cap {
|
|||||||
|
|
||||||
/// The subpath to the main image on the server
|
/// The subpath to the main image on the server
|
||||||
var mainImagePath: String {
|
var mainImagePath: String {
|
||||||
String(format: "images/%04d/%04d-%02d.jpg", id, id, mainImage)
|
imagePath(version: mainImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imagePath(version: Int) -> String {
|
||||||
|
String(format: "images/%04d/%04d-%02d.jpg", id, id, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
var image: CapImage {
|
var image: CapImage {
|
||||||
.init(cap: id, version: mainImage)
|
.init(cap: id, version: mainImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func image(version: Int) -> CapImage {
|
||||||
|
.init(cap: id, version: version)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new cap.
|
Create a new cap.
|
||||||
- Parameter id: The unique id of the cap
|
- Parameter id: The unique id of the cap
|
||||||
|
@ -6,3 +6,8 @@ struct CapImage: Codable, Equatable, Hashable {
|
|||||||
|
|
||||||
let version: Int
|
let version: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CapImage: Identifiable {
|
||||||
|
|
||||||
|
var id: Int { version }
|
||||||
|
}
|
||||||
|
@ -241,7 +241,7 @@ final class Database: ObservableObject {
|
|||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func downloadCaps() async -> Bool {
|
func downloadCaps() async -> Bool {
|
||||||
print("Downloading cap data")
|
print("Downloading cap data from \(serverDbUrl)")
|
||||||
let data: Data
|
let data: Data
|
||||||
let response: URLResponse
|
let response: URLResponse
|
||||||
do {
|
do {
|
||||||
@ -250,7 +250,11 @@ final class Database: ObservableObject {
|
|||||||
print("Failed to download classifier version: \(error)")
|
print("Failed to download classifier version: \(error)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
guard httpResponse.statusCode == 200 else {
|
||||||
|
print("Failed to download caps: \(httpResponse.statusCode)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,6 +265,7 @@ final class Database: ObservableObject {
|
|||||||
print("Failed to decode server database: \(error)")
|
print("Failed to decode server database: \(error)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
print("Downloaded \(capData) caps")
|
||||||
var inserts = 0
|
var inserts = 0
|
||||||
var updates = 0
|
var updates = 0
|
||||||
for cap in capData {
|
for cap in capData {
|
||||||
@ -359,6 +364,10 @@ final class Database: ObservableObject {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cap(for id: Int) -> Cap? {
|
||||||
|
caps[id]
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Adding new data
|
// MARK: Adding new data
|
||||||
|
|
||||||
func save(newCap name: String) -> Cap {
|
func save(newCap name: String) -> Cap {
|
||||||
|
75
Caps/Views/CapImagesView.swift
Normal file
75
Caps/Views/CapImagesView.swift
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CapImagesView: View {
|
||||||
|
|
||||||
|
private let imageSize: CGFloat = 70
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
var cap: Cap?
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
var isPresented: Bool
|
||||||
|
|
||||||
|
init(cap: Binding<Cap?>, database: Database, isPresented: Binding<Bool>) {
|
||||||
|
self.database = database
|
||||||
|
self._cap = cap
|
||||||
|
self._isPresented = isPresented
|
||||||
|
}
|
||||||
|
|
||||||
|
let database: Database
|
||||||
|
|
||||||
|
var images: [CapImage] {
|
||||||
|
guard let cap else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return (0..<cap.imageCount).map {
|
||||||
|
cap.image(version: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
guard let cap else {
|
||||||
|
return "Images"
|
||||||
|
}
|
||||||
|
return "Cap \(cap.id)"
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geo in
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Text(title).font(.title2).bold()
|
||||||
|
Spacer()
|
||||||
|
Button(action: { isPresented = false }) {
|
||||||
|
Image(systemSymbol: .xmarkCircleFill)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.font(.system(size: 26))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let gridItem = GridItem(.flexible(), spacing: 10, alignment: .leading)
|
||||||
|
ScrollView(.vertical) {
|
||||||
|
LazyVGrid(columns: [gridItem, gridItem, gridItem, gridItem]) {
|
||||||
|
ForEach(images) { item in
|
||||||
|
CachedCapImage(
|
||||||
|
item,
|
||||||
|
check: { database.images.cachedImage(item) },
|
||||||
|
fetch: { await database.images.image(item) },
|
||||||
|
content: { $0.resizable() },
|
||||||
|
placeholder: { ProgressView() })
|
||||||
|
.frame(width: imageSize,
|
||||||
|
height: imageSize)
|
||||||
|
.clipShape(Circle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CapImagesView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
CapImagesView(cap: .constant(.init(id: 123, name: "Some")), database: .mock, isPresented: .constant(true))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user