Implementing basic filtering parameters on the new filter screen

This commit is contained in:
Selim Mustafaev 2024-10-13 18:15:51 +03:00
parent 5f634ba895
commit 807fa94e31
9 changed files with 188 additions and 53 deletions

View File

@ -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 */,

View File

@ -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" }

View File

@ -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() {

View File

@ -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] {
[]
}
}

View File

@ -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()
}
}
}

View File

@ -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
}
}

View 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))
}
}

View File

@ -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 {

View File

@ -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]
}