Implementing basic filtering parameters on the new filter screen
This commit is contained in:
parent
5f634ba895
commit
807fa94e31
@ -194,6 +194,7 @@
|
||||
7AF6D22A2677C3AD0086EA64 /* Exportable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8424D26109F78002F6B31 /* Exportable.swift */; };
|
||||
7AF8606C2CB9B20C00954D2F /* Mockable in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF8606B2CB9B20C00954D2F /* Mockable */; };
|
||||
7AF8606E2CB9B86300954D2F /* Mockable in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF8606D2CB9B86300954D2F /* Mockable */; };
|
||||
7AF860702CBAA24500954D2F /* NavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF8606F2CBAA24500954D2F /* NavigationLink.swift */; };
|
||||
7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8BF2C3024E5003C491D /* ACHud.swift */; };
|
||||
7AFBE8C42C302561003C491D /* ACHudContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8C32C302561003C491D /* ACHudContainer.swift */; };
|
||||
7AFBE8CA2C3081C7003C491D /* ACProgressHud+Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8C92C3081C7003C491D /* ACProgressHud+Modifiers.swift */; };
|
||||
@ -437,6 +438,7 @@
|
||||
7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AutoCatCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7AF6D1F12677C03B0086EA64 /* AutoCatCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutoCatCore.h; sourceTree = "<group>"; };
|
||||
7AF6D1F22677C03B0086EA64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
7AF8606F2CBAA24500954D2F /* NavigationLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationLink.swift; sourceTree = "<group>"; };
|
||||
7AFBE8BF2C3024E5003C491D /* ACHud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACHud.swift; sourceTree = "<group>"; };
|
||||
7AFBE8C32C302561003C491D /* ACHudContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACHudContainer.swift; sourceTree = "<group>"; };
|
||||
7AFBE8C92C3081C7003C491D /* ACProgressHud+Modifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ACProgressHud+Modifiers.swift"; sourceTree = "<group>"; };
|
||||
@ -1035,6 +1037,7 @@
|
||||
7AAAFAD92C4D1AFE0050410D /* Zoomable.swift */,
|
||||
7A1022712C554A1300B84627 /* CustomHostingController.swift */,
|
||||
7A5D7E0B2C71EB25002C17E7 /* ToggleRowView.swift */,
|
||||
7AF8606F2CBAA24500954D2F /* NavigationLink.swift */,
|
||||
);
|
||||
path = SwiftUI;
|
||||
sourceTree = "<group>";
|
||||
@ -1293,6 +1296,7 @@
|
||||
7AC3555029696D5A00889457 /* NewNumberController.swift in Sources */,
|
||||
7AE26A3524F31B0700625033 /* EventsController.swift in Sources */,
|
||||
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */,
|
||||
7AF860702CBAA24500954D2F /* NavigationLink.swift in Sources */,
|
||||
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */,
|
||||
7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */,
|
||||
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */,
|
||||
|
||||
@ -35,7 +35,7 @@ class FiltersController: FormViewController {
|
||||
|
||||
let brandRow = PushRow<String>("Brand") { row in
|
||||
row.title = NSLocalizedString("Brand", comment: "")
|
||||
row.value = self.filter.brand ?? "Any"
|
||||
row.value = self.filter.brand.text
|
||||
row.selectorTitle = NSLocalizedString("Brands", comment: "")
|
||||
row.optionsProvider = .lazy({ form, completion in
|
||||
self.runAsync {
|
||||
@ -45,32 +45,32 @@ class FiltersController: FormViewController {
|
||||
})
|
||||
}
|
||||
.onPresent(removeSectionName(from:to:))
|
||||
.onChange { self.filter.brand = $0.value == "Any" ? nil : $0.value }
|
||||
.cellUpdate { $1.value = self.filter.brand ?? "Any" }
|
||||
.onChange { self.filter.brand = .init($0.value) }
|
||||
.cellUpdate { $1.value = self.filter.brand.text }
|
||||
|
||||
let modelRow = PushRow<String>("Model") { row in
|
||||
row.title = NSLocalizedString("Model", comment: "")
|
||||
row.value = self.filter.model ?? "Any"
|
||||
row.value = self.filter.model.text
|
||||
row.disabled = "$Brand == 'Any'"
|
||||
row.optionsProvider = .lazy({ form, completion in
|
||||
guard let brand = self.filter.brand else {
|
||||
guard self.filter.brand != .any else {
|
||||
completion(["Any"])
|
||||
return
|
||||
}
|
||||
|
||||
self.runAsync {
|
||||
let models = try await ApiService.shared.getModels(of: brand)
|
||||
let models = try await ApiService.shared.getModels(of: self.filter.brand.text)
|
||||
completion(["Any"] + models)
|
||||
}
|
||||
})
|
||||
}
|
||||
.onPresent(removeSectionName(from:to:))
|
||||
.onChange { self.filter.model = $0.value == "Any" ? nil : $0.value }
|
||||
.cellUpdate { $1.value = self.filter.model ?? "Any" }
|
||||
.onChange { self.filter.model = .init($0.value) }
|
||||
.cellUpdate { $1.value = self.filter.model.text }
|
||||
|
||||
let colorRow = PushRow<String>("Color") { row in
|
||||
row.title = NSLocalizedString("Color", comment: "")
|
||||
row.value = self.filter.color ?? "Any"
|
||||
row.value = self.filter.color.text
|
||||
row.optionsProvider = .lazy({ form, completion in
|
||||
self.runAsync {
|
||||
let colors = try await ApiService.shared.getColors()
|
||||
@ -79,12 +79,12 @@ class FiltersController: FormViewController {
|
||||
})
|
||||
}
|
||||
.onPresent(removeSectionName(from:to:))
|
||||
.onChange { self.filter.color = $0.value == "Any" ? nil : $0.value }
|
||||
.cellUpdate { $1.value = self.filter.color ?? "Any" }
|
||||
.onChange { self.filter.color = .init($0.value) }
|
||||
.cellUpdate { $1.value = self.filter.color.text }
|
||||
|
||||
let yearRow = PushRow<String>("Year") { row in
|
||||
row.title = NSLocalizedString("Year", comment: "Manufacturing year")
|
||||
row.value = self.filter.year ?? "Any"
|
||||
row.value = self.filter.year.text
|
||||
row.optionsProvider = .lazy({ form, completion in
|
||||
self.runAsync {
|
||||
let years = try await ApiService.shared.getYears()
|
||||
@ -92,8 +92,8 @@ class FiltersController: FormViewController {
|
||||
}
|
||||
})
|
||||
}
|
||||
.onChange { self.filter.year = $0.value == "Any" ? nil : $0.value }
|
||||
.cellUpdate { $1.value = self.filter.year ?? "Any" }
|
||||
.onChange { self.filter.year = .init($0.value) }
|
||||
.cellUpdate { $1.value = self.filter.year.text }
|
||||
|
||||
let mainSection = Section(NSLocalizedString("Main filters", comment: "")) { $0.tag = "MainFilters" }
|
||||
|
||||
|
||||
@ -184,26 +184,26 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
|
||||
}
|
||||
|
||||
func showFilter() async throws {
|
||||
let sb = UIStoryboard(name: "Main", bundle: nil)
|
||||
let controller = sb.instantiateViewController(identifier: "FiltersController") as FiltersController
|
||||
controller.filter = self.filter
|
||||
controller.onDone = {
|
||||
self.filter = controller.filter
|
||||
self.datasource.setSortParameter(self.filter.sortBy ?? .updatedDate)
|
||||
self.filter.needReset = true
|
||||
self.filter.scope = SearchScope(rawValue: self.searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
|
||||
self.updateSearchResults(with: self.filter)
|
||||
}
|
||||
self.navigationController?.pushViewController(controller, animated: true)
|
||||
|
||||
// if let navigationController = self.navigationController {
|
||||
// let coordinator = FiltersCoordinator(navController: navigationController, filter: filter)
|
||||
// filter = try await coordinator.start()
|
||||
// datasource.setSortParameter(self.filter.sortBy ?? .updatedDate)
|
||||
// filter.needReset = true
|
||||
// filter.scope = SearchScope(rawValue: self.searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
|
||||
// updateSearchResults(with: self.filter)
|
||||
// let sb = UIStoryboard(name: "Main", bundle: nil)
|
||||
// let controller = sb.instantiateViewController(identifier: "FiltersController") as FiltersController
|
||||
// controller.filter = self.filter
|
||||
// controller.onDone = {
|
||||
// self.filter = controller.filter
|
||||
// self.datasource.setSortParameter(self.filter.sortBy ?? .updatedDate)
|
||||
// self.filter.needReset = true
|
||||
// self.filter.scope = SearchScope(rawValue: self.searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
|
||||
// self.updateSearchResults(with: self.filter)
|
||||
// }
|
||||
// self.navigationController?.pushViewController(controller, animated: true)
|
||||
|
||||
if let navigationController = self.navigationController {
|
||||
let coordinator = FiltersCoordinator(navController: navigationController, filter: filter)
|
||||
filter = try await coordinator.start()
|
||||
datasource.setSortParameter(self.filter.sortBy ?? .updatedDate)
|
||||
filter.needReset = true
|
||||
filter.scope = SearchScope(rawValue: self.searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
|
||||
updateSearchResults(with: self.filter)
|
||||
}
|
||||
}
|
||||
|
||||
func showOnMap() {
|
||||
|
||||
@ -23,4 +23,24 @@ actor ApiServiceStub: ApiServiceProtocol {
|
||||
func remove(note id: String) async throws -> VehicleDto {
|
||||
vehicle
|
||||
}
|
||||
|
||||
func getBrands() async throws -> [String] {
|
||||
[]
|
||||
}
|
||||
|
||||
func getModels(of brand: String) async throws -> [String] {
|
||||
[]
|
||||
}
|
||||
|
||||
func getColors() async throws -> [String] {
|
||||
[]
|
||||
}
|
||||
|
||||
func getRegions() async throws -> [AutoCatCore.VehicleRegion] {
|
||||
[]
|
||||
}
|
||||
|
||||
func getYears() async throws -> [Int] {
|
||||
[]
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,20 +24,30 @@ struct FiltersScreen: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
Section("Main filters") {
|
||||
NavigationLink(value: DetailScreen.brand) {
|
||||
LabeledContent("Brand", value: viewModel.filter.brand ?? "Any")
|
||||
Picker("Brand", selection: $viewModel.filter.brand) {
|
||||
ForEach(viewModel.brands, id: \.self) { Text($0.text) }
|
||||
}
|
||||
LabeledContent("Model", value: viewModel.filter.model ?? "Any")
|
||||
LabeledContent("Color", value: viewModel.filter.color ?? "Any")
|
||||
LabeledContent("Year", value: viewModel.filter.year ?? "Any")
|
||||
|
||||
Picker("Model", selection: $viewModel.filter.model) {
|
||||
ForEach(viewModel.models, id: \.self) { Text($0.text) }
|
||||
}
|
||||
|
||||
Picker("Color", selection: $viewModel.filter.color) {
|
||||
ForEach(viewModel.colors, id: \.self) { Text($0.text) }
|
||||
}
|
||||
|
||||
Picker("Year", selection: $viewModel.filter.year) {
|
||||
ForEach(viewModel.years, id: \.self) { Text($0.text) }
|
||||
}
|
||||
}
|
||||
.pickerStyle(.navigationLink)
|
||||
}
|
||||
.navigationTitle("Filters")
|
||||
.navigationBarItems(trailing: Button("Done", action: {
|
||||
|
||||
}))
|
||||
.navigationDestination(for: DetailScreen.self) { _ in
|
||||
EmptyView()
|
||||
.task {
|
||||
await viewModel.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,9 +13,42 @@ import AutoCatCore
|
||||
@Observable
|
||||
class FiltersViewModel {
|
||||
|
||||
var filter: Filter
|
||||
@ObservationIgnored @Service var api: ApiServiceProtocol
|
||||
|
||||
var filter: Filter {
|
||||
didSet {
|
||||
if filter.brand != currentBrand {
|
||||
if filter.brand != .any {
|
||||
Task { await loadModels(brand: filter.brand.text) }
|
||||
}
|
||||
currentBrand = filter.brand
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var models: [StringOption] = [.any]
|
||||
var brands: [StringOption] = [.any]
|
||||
var colors: [StringOption] = [.any]
|
||||
var years: [StringOption] = [.any]
|
||||
|
||||
@ObservationIgnored var currentBrand: StringOption = .any
|
||||
|
||||
init(filter: Filter) {
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
func loadData() async {
|
||||
guard brands == [.any] || colors == [.any] || years == [.any] else {
|
||||
return
|
||||
}
|
||||
|
||||
brands = [.any] + ((try? await api.getBrands()) ?? []).map { .value($0) }
|
||||
colors = [.any] + ((try? await api.getColors()) ?? []).map { .value($0) }
|
||||
years = [.any] + ((try? await api.getYears())?.map(String.init) ?? []).map { .value($0) }
|
||||
}
|
||||
|
||||
func loadModels(brand: String) async {
|
||||
models = [.any] + ((try? await api.getModels(of: brand)) ?? []).map { .value($0) }
|
||||
filter.model = .any
|
||||
}
|
||||
}
|
||||
|
||||
35
AutoCat/SwiftUI/NavigationLink.swift
Normal file
35
AutoCat/SwiftUI/NavigationLink.swift
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// NavigationLink.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 12.10.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NavigationLinkModifier: ViewModifier {
|
||||
|
||||
var onTap: () -> Void
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
content
|
||||
Spacer()
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.footnote)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture(perform: onTap)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func navigationLink(onTap: @escaping () -> Void) -> some View {
|
||||
modifier(NavigationLinkModifier(onTap: onTap))
|
||||
}
|
||||
}
|
||||
@ -61,12 +61,39 @@ public enum SearchScope: Int, CaseIterable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum StringOption: Hashable, Sendable {
|
||||
|
||||
case any
|
||||
case value(String)
|
||||
|
||||
public var text: String {
|
||||
switch self {
|
||||
case .any: return "Any"
|
||||
case .value(let value): return value
|
||||
}
|
||||
}
|
||||
|
||||
public init(_ text: String?) {
|
||||
guard let text else {
|
||||
self = .any
|
||||
return
|
||||
}
|
||||
|
||||
switch text {
|
||||
case "Any":
|
||||
self = .any
|
||||
default :
|
||||
self = .value(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Filter: Sendable {
|
||||
public var searchString = ""
|
||||
public var brand: String?
|
||||
public var model: String?
|
||||
public var color: String?
|
||||
public var year: String?
|
||||
public var brand: StringOption = .any
|
||||
public var model: StringOption = .any
|
||||
public var color: StringOption = .any
|
||||
public var year: StringOption = .any
|
||||
public var regions: [Int]?
|
||||
public var addedBy: AddedBy?
|
||||
public var sortBy: SortParameter? = .updatedDate
|
||||
@ -84,10 +111,10 @@ public struct Filter: Sendable {
|
||||
}
|
||||
|
||||
public mutating func clear() {
|
||||
self.brand = nil
|
||||
self.model = nil
|
||||
self.color = nil
|
||||
self.year = nil
|
||||
self.brand = .any
|
||||
self.model = .any
|
||||
self.color = .any
|
||||
self.year = .any
|
||||
self.regions = nil
|
||||
self.addedBy = nil
|
||||
self.sortBy = .updatedDate
|
||||
@ -104,16 +131,16 @@ public struct Filter: Sendable {
|
||||
public func queryDictionary() -> [String: String] {
|
||||
var dict: [String: String] = ["query": self.searchString]
|
||||
|
||||
if let brand = self.brand {
|
||||
if case let .value(brand) = brand {
|
||||
dict["brand"] = brand
|
||||
}
|
||||
if let model = self.model {
|
||||
if case let .value(model) = model {
|
||||
dict["model"] = model
|
||||
}
|
||||
if let color = self.color {
|
||||
if case let .value(color) = color {
|
||||
dict["color"] = color
|
||||
}
|
||||
if let year = self.year {
|
||||
if case let .value(year) = year {
|
||||
dict["year"] = year
|
||||
}
|
||||
if let regions = self.regions {
|
||||
|
||||
@ -14,4 +14,10 @@ public protocol ApiServiceProtocol: Sendable {
|
||||
func add(notes: [VehicleNoteDto], to number: String) async throws -> VehicleDto
|
||||
func edit(note: VehicleNoteDto) async throws -> VehicleDto
|
||||
func remove(note id: String) async throws -> VehicleDto
|
||||
|
||||
func getBrands() async throws -> [String]
|
||||
func getModels(of brand: String) async throws -> [String]
|
||||
func getColors() async throws -> [String]
|
||||
func getRegions() async throws -> [VehicleRegion]
|
||||
func getYears() async throws -> [Int]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user