Start version 2
This commit is contained in:
31
Caps/Views/CapNameEntryView.swift
Normal file
31
Caps/Views/CapNameEntryView.swift
Normal file
@@ -0,0 +1,31 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct CapNameEntryView: View {
|
||||
|
||||
@Binding
|
||||
var name: String
|
||||
|
||||
var body: some View {
|
||||
TextField("Name", text: $name, prompt: Text("Enter name..."))
|
||||
.padding(7)
|
||||
.padding(.horizontal, 25)
|
||||
.background(Color(.systemGray5))
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
HStack {
|
||||
Image(systemSymbol: .squareAndPencil)
|
||||
.foregroundColor(.gray)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 8)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct CapNameEntryView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CapNameEntryView(name: .constant(""))
|
||||
.previewLayout(.fixed(width: 375, height: 50))
|
||||
}
|
||||
}
|
77
Caps/Views/CapRowView.swift
Normal file
77
Caps/Views/CapRowView.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
import SwiftUI
|
||||
import CachedAsyncImage
|
||||
|
||||
struct CapRowView: View {
|
||||
|
||||
private let imageSize: CGFloat = 70
|
||||
|
||||
private let sufficientImageCount = 10
|
||||
|
||||
let cap: Cap
|
||||
|
||||
let match: Float?
|
||||
|
||||
@EnvironmentObject
|
||||
var database: Database
|
||||
|
||||
var imageUrl: URL {
|
||||
database.serverUrl.appendingPathComponent(cap.mainImagePath)
|
||||
}
|
||||
|
||||
var imageCountText: String {
|
||||
guard cap.imageCount != 1 else {
|
||||
return "\(cap.id) (1 image)"
|
||||
}
|
||||
return "\(cap.id) (\(cap.imageCount) images)"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Text(imageCountText)
|
||||
.font(.footnote)
|
||||
if !cap.classifiable(by: database.classifierVersion) {
|
||||
Text("📵")
|
||||
}
|
||||
if cap.imageCount < sufficientImageCount {
|
||||
Text("⚠️")
|
||||
}
|
||||
if database.hasPendingUpdates(for: cap.id) {
|
||||
Text("⇅")
|
||||
}
|
||||
if database.hasPendingOperations(for: cap.id) {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
.padding(.top, 0)
|
||||
.font(.footnote)
|
||||
Text(cap.name)
|
||||
.font(.headline)
|
||||
.padding(.bottom, 3)
|
||||
if let match = match {
|
||||
Text("\(Int((match * 100).rounded())) % match")
|
||||
.font(.footnote)
|
||||
}
|
||||
}//.padding(.vertical)
|
||||
Spacer()
|
||||
CachedAsyncImage(url: imageUrl, urlCache: database.imageCache) { image in
|
||||
image.resizable()
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
.frame(width: imageSize, height: imageSize)
|
||||
.cornerRadius(imageSize / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CapRowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CapRowView(cap: Cap(id: 123, name: "My new cap"),
|
||||
match: 0.13)
|
||||
.previewLayout(.fixed(width: 375, height: 80))
|
||||
.environmentObject(Database.mock)
|
||||
}
|
||||
}
|
13
Caps/Views/GridView.swift
Normal file
13
Caps/Views/GridView.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
import SwiftUI
|
||||
|
||||
struct GridView: View {
|
||||
var body: some View {
|
||||
Text("Grid view")
|
||||
}
|
||||
}
|
||||
|
||||
struct GridView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GridView()
|
||||
}
|
||||
}
|
40
Caps/Views/SearchField.swift
Normal file
40
Caps/Views/SearchField.swift
Normal file
@@ -0,0 +1,40 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct SearchField: View {
|
||||
|
||||
@Binding
|
||||
var searchString: String
|
||||
|
||||
var body: some View {
|
||||
TextField("Search", text: $searchString, prompt: Text("Search..."))
|
||||
.padding(7)
|
||||
.padding(.horizontal, 25)
|
||||
.background(Color(.systemGray5))
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
HStack {
|
||||
Image(systemSymbol: .magnifyingglass)
|
||||
.foregroundColor(.gray)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 8)
|
||||
if searchString != "" {
|
||||
Button(action: {
|
||||
self.searchString = ""
|
||||
}) {
|
||||
Image(systemSymbol: .multiplyCircleFill)
|
||||
.foregroundColor(.gray)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchField_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SearchField(searchString: .constant(""))
|
||||
.previewLayout(.fixed(width: 375, height: 50))
|
||||
}
|
||||
}
|
30
Caps/Views/SettingsStatisticRow.swift
Normal file
30
Caps/Views/SettingsStatisticRow.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// SettingsStatisticRow.swift
|
||||
// Caps
|
||||
//
|
||||
// Created by CH on 26.05.22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsStatisticRow: View {
|
||||
|
||||
let label: String
|
||||
|
||||
let value: String
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
Spacer()
|
||||
Text(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsStatisticRow_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsStatisticRow(label: "Label", value: "Value")
|
||||
.previewLayout(.fixed(width: 375, height: 40))
|
||||
}
|
||||
}
|
74
Caps/Views/SettingsView.swift
Normal file
74
Caps/Views/SettingsView.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
var database: Database
|
||||
|
||||
@Binding
|
||||
var isPresented: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
HStack {
|
||||
Text("Settings")
|
||||
.font(.title2)
|
||||
.bold()
|
||||
Spacer()
|
||||
Button(action: hide) {
|
||||
Image(systemSymbol: .xmarkCircleFill)
|
||||
.foregroundColor(.gray)
|
||||
.font(.system(size: 26))
|
||||
}
|
||||
}
|
||||
Text("Statistics")
|
||||
.font(.footnote)
|
||||
.textCase(.uppercase)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top)
|
||||
Group {
|
||||
SettingsStatisticRow(label: "Caps", value: "\(database.numberOfCaps)")
|
||||
SettingsStatisticRow(label: "Total images", value: "\(database.numberOfImages)")
|
||||
SettingsStatisticRow(label: "Images per cap", value: String(format: "%.1f", database.averageImageCount))
|
||||
}.padding(.horizontal)
|
||||
Text("Classifier")
|
||||
.font(.footnote)
|
||||
.textCase(.uppercase)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top)
|
||||
Group {
|
||||
SettingsStatisticRow(label: "Version", value: "\(database.classifierVersion)")
|
||||
SettingsStatisticRow(label: "Recognized caps", value: "\(database.classifierClassCount)")
|
||||
}.padding(.horizontal)
|
||||
Text("Storage")
|
||||
.font(.footnote)
|
||||
.textCase(.uppercase)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top)
|
||||
Group {
|
||||
SettingsStatisticRow(label: "Image cache", value: byteString(database.imageCacheSize))
|
||||
SettingsStatisticRow(label: "Database", value: byteString(database.databaseSize))
|
||||
SettingsStatisticRow(label: "Classifier", value: byteString(database.classifierSize))
|
||||
}.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
private func hide() {
|
||||
isPresented = false
|
||||
}
|
||||
|
||||
private func byteString(_ count: Int) -> String {
|
||||
ByteCountFormatter.string(fromByteCount: Int64(count), countStyle: .file)
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsView(isPresented: .constant(true))
|
||||
.environmentObject(Database.mock)
|
||||
.previewLayout(.fixed(width: 375, height: 330))
|
||||
}
|
||||
}
|
33
Caps/Views/SortCaseRowView.swift
Normal file
33
Caps/Views/SortCaseRowView.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SortCaseRowView: View {
|
||||
|
||||
@Binding
|
||||
var selectedType: SortCriteria
|
||||
|
||||
let type: SortCriteria
|
||||
|
||||
var body: some View {
|
||||
Button(action: { selectedType = type}) {
|
||||
HStack {
|
||||
Text(type.text)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
if selectedType == type {
|
||||
Image(systemSymbol: .checkmark)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color(UIColor.systemGroupedBackground))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SortCaseRowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SortCaseRowView(selectedType: .constant(.id), type: .id)
|
||||
.previewLayout(.fixed(width: 375, height: 50))
|
||||
}
|
||||
}
|
95
Caps/Views/SortSelectionView.swift
Normal file
95
Caps/Views/SortSelectionView.swift
Normal file
@@ -0,0 +1,95 @@
|
||||
import SwiftUI
|
||||
|
||||
private extension Binding where Value == SortCriteria {
|
||||
|
||||
func value() -> Binding<Int> {
|
||||
return Binding<Int>(get:{ self.wrappedValue.rawValue },
|
||||
set: { self.wrappedValue = .init(rawValue: $0)!})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SortSelectionView: View {
|
||||
|
||||
let hasMatches: Bool
|
||||
|
||||
@Binding
|
||||
var isPresented: Bool
|
||||
|
||||
@Binding
|
||||
var sortType: SortCriteria
|
||||
|
||||
@Binding
|
||||
var sortAscending: Bool
|
||||
|
||||
@Binding
|
||||
var showGridView: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
HStack {
|
||||
Text("List Settings").font(.title2).bold()
|
||||
Spacer()
|
||||
Button(action: { isPresented = false }) {
|
||||
Image(systemSymbol: .xmarkCircleFill)
|
||||
.foregroundColor(.gray)
|
||||
.font(.system(size: 26))
|
||||
}
|
||||
}
|
||||
.padding(.bottom)
|
||||
Text("Sort by")
|
||||
.font(.footnote)
|
||||
.textCase(.uppercase)
|
||||
.foregroundColor(.secondary)
|
||||
Picker("Sort type", selection: $sortType.value()) {
|
||||
Text(SortCriteria.id.text).tag(SortCriteria.id.rawValue)
|
||||
Text(SortCriteria.name.text).tag(SortCriteria.name.rawValue)
|
||||
Text(SortCriteria.count.text).tag(SortCriteria.count.rawValue)
|
||||
if hasMatches {
|
||||
Text(SortCriteria.match.text).tag(SortCriteria.match.rawValue)
|
||||
}
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
.padding(.bottom)
|
||||
Text("Sort order")
|
||||
.font(.footnote)
|
||||
.textCase(.uppercase)
|
||||
.foregroundColor(.secondary)
|
||||
Picker("Sort order", selection: $sortAscending) {
|
||||
Text("Ascending").tag(true)
|
||||
Text("Descending").tag(false)
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
.padding(.bottom)
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: showGrid) {
|
||||
HStack {
|
||||
Image(systemSymbol: .circleHexagongrid)
|
||||
Text("Show grid")
|
||||
}
|
||||
}.padding()
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
private func showGrid() {
|
||||
showGridView = true
|
||||
isPresented = false
|
||||
}
|
||||
}
|
||||
|
||||
struct SortSelectionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SortSelectionView(
|
||||
hasMatches: true,
|
||||
isPresented: .constant(true),
|
||||
sortType: .constant(.id),
|
||||
sortAscending: .constant(false),
|
||||
showGridView: .constant(false))
|
||||
.previewLayout(.fixed(width: 375, height: 250))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user