Adding vehicle cell view. Displaying list of vehicles in sections.
This commit is contained in:
parent
67977f1ebf
commit
bade0d5418
@ -146,6 +146,7 @@
|
||||
7AABBE3B2CF9F85600346588 /* Binding+Map.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABBE3A2CF9F85600346588 /* Binding+Map.swift */; };
|
||||
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; };
|
||||
7AB0EF812C5CC0FE00291EE6 /* SwiftLocationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */; };
|
||||
7AB4E42C2D397D8E0006D052 /* VehicleCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4E42B2D397D8E0006D052 /* VehicleCellView.swift */; };
|
||||
7AB5871D2C42C1CF00FA7B66 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7AB5871C2C42C1CF00FA7B66 /* RealmSwift */; };
|
||||
7AB587322C42D38E00FA7B66 /* StorageServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB587312C42D38E00FA7B66 /* StorageServiceProtocol.swift */; };
|
||||
7AB587342C42D3FA00FA7B66 /* StorageService+Notes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB587332C42D3FA00FA7B66 /* StorageService+Notes.swift */; };
|
||||
@ -417,6 +418,7 @@
|
||||
7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxSectionedDataSource.swift; sourceTree = "<group>"; };
|
||||
7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = "<group>"; };
|
||||
7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLocationProtocol.swift; sourceTree = "<group>"; };
|
||||
7AB4E42B2D397D8E0006D052 /* VehicleCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCellView.swift; sourceTree = "<group>"; };
|
||||
7AB562B9249C9E9B00473D53 /* VehicleRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleRegion.swift; sourceTree = "<group>"; };
|
||||
7AB587222C42D27F00FA7B66 /* AutoCatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AutoCatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7AB587312C42D38E00FA7B66 /* StorageServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -1082,6 +1084,7 @@
|
||||
7A4927D42CCE438600851C01 /* OptionalBinding.swift */,
|
||||
7AABBE3A2CF9F85600346588 /* Binding+Map.swift */,
|
||||
7A912F362D381B7400002938 /* LicensePlateView.swift */,
|
||||
7AB4E42B2D397D8E0006D052 /* VehicleCellView.swift */,
|
||||
);
|
||||
path = SwiftUI;
|
||||
sourceTree = "<group>";
|
||||
@ -1345,6 +1348,7 @@
|
||||
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */,
|
||||
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */,
|
||||
7A06E0AE2C7065C7005731AC /* SettingsViewModel.swift in Sources */,
|
||||
7AB4E42C2D397D8E0006D052 /* VehicleCellView.swift in Sources */,
|
||||
7A961C6E2C4C3C9E00CE2211 /* LinkRowView.swift in Sources */,
|
||||
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */,
|
||||
7A3E30F32C18840600567704 /* ActivityItemSource.swift in Sources */,
|
||||
|
||||
@ -14,12 +14,18 @@ struct HistoryScreen: View {
|
||||
@State var viewModel: HistoryViewModel
|
||||
|
||||
var body: some View {
|
||||
List(viewModel.vehicles) { vehicle in
|
||||
LicensePlateView(number: PlateNumber(vehicle.getNumber()))
|
||||
.frame(width: 200)
|
||||
List {
|
||||
ForEach(viewModel.vehicleSections) { section in
|
||||
Section(header: Text(section.header)) {
|
||||
ForEach(section.elements) { vehicle in
|
||||
VehicleCellView(vehicle: vehicle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), viewModel.vehicles.count))
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), viewModel.vehicleSections.reduce(0, { $0 + $1.elements.count })))
|
||||
.searchable(text: $viewModel.searchText, prompt: "Search plate numbers")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,16 @@ final class HistoryViewModel {
|
||||
let apiService: ApiServiceProtocol
|
||||
let storageService: StorageServiceProtocol
|
||||
|
||||
var vehicles: [VehicleDto] = []
|
||||
var vehicleSections: [DateSection<VehicleDto>] = []
|
||||
var vehicleSectionsFiltered: [DateSection<VehicleDto>] = []
|
||||
|
||||
var searchText: String = "" {
|
||||
didSet {
|
||||
if searchText != oldValue {
|
||||
applySearchFilter(text: searchText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(apiService: ApiServiceProtocol, storageService: StorageServiceProtocol) {
|
||||
|
||||
@ -28,6 +37,11 @@ final class HistoryViewModel {
|
||||
|
||||
func loadVehicles() async {
|
||||
|
||||
vehicles = await storageService.loadVehicles()
|
||||
let vehicles = await storageService.loadVehicles()
|
||||
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
|
||||
}
|
||||
|
||||
func applySearchFilter(text: String) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,17 +20,19 @@ struct LicensePlateView: View {
|
||||
private static let innerCornerMultiplier = outerCornerMultiplier - borderMultiplier
|
||||
|
||||
let number: PlateNumber
|
||||
let foreground: Color
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: geometry.size.width*LicensePlateView.outerCornerMultiplier)
|
||||
.fill(Color("PlateForeground"))
|
||||
.fill(foreground)
|
||||
HStack(spacing: 0) {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: geometry.size.width*LicensePlateView.innerCornerMultiplier)
|
||||
.fill(Color("PlateBackground"))
|
||||
Text(number.mainPart())
|
||||
.foregroundStyle(foreground)
|
||||
.font(.custom("RoadNumbers", size: geometry.size.height))
|
||||
.kerning(3)
|
||||
.offset(y: geometry.size.width*0.015)
|
||||
@ -43,9 +45,11 @@ struct LicensePlateView: View {
|
||||
.fill(Color("PlateBackground"))
|
||||
VStack(spacing: 0) {
|
||||
Text(number.region())
|
||||
.foregroundStyle(foreground)
|
||||
.font(.custom("RoadNumbers", size: geometry.size.height*0.7))
|
||||
HStack(spacing: 2) {
|
||||
Text("RUS")
|
||||
.foregroundStyle(foreground)
|
||||
.font(.system(size: geometry.size.height*0.3))
|
||||
VStack(spacing: 0) {
|
||||
Rectangle().fill(.white)
|
||||
@ -69,5 +73,5 @@ struct LicensePlateView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LicensePlateView(number: PlateNumber("А123АА761"))
|
||||
LicensePlateView(number: PlateNumber("А123АА761"), foreground: Color("PlateForeground"))
|
||||
}
|
||||
|
||||
74
AutoCat/SwiftUI/VehicleCellView.swift
Normal file
74
AutoCat/SwiftUI/VehicleCellView.swift
Normal file
@ -0,0 +1,74 @@
|
||||
//
|
||||
// VehicleCellView.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 16.01.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
struct VehicleCellView: View {
|
||||
|
||||
let vehicle: VehicleDto
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack(spacing: 4) {
|
||||
if let name = vehicle.brand?.name?.original {
|
||||
Text(name)
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
if vehicle.synchronized || vehicle.unrecognized {
|
||||
Image(systemName: "exclamationmark.arrow.triangle")
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
|
||||
if !vehicle.notes.isEmpty {
|
||||
Image(systemName: "text.bubble")
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(.blue)
|
||||
Text(String(vehicle.notes.count))
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
|
||||
HStack(alignment: .bottom, spacing: 8) {
|
||||
LicensePlateView(number: PlateNumber(vehicle.getNumber()), foreground: getForegroundColor())
|
||||
.frame(height: 40)
|
||||
|
||||
Spacer(minLength: 8)
|
||||
|
||||
VStack(alignment: .trailing, spacing: 8) {
|
||||
if vehicle.updatedDate != vehicle.addedDate {
|
||||
Text(Formatters.short.string(from: Date(timeIntervalSince1970: vehicle.updatedDate)))
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Text(Formatters.short.string(from: Date(timeIntervalSince1970: vehicle.addedDate)))
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getForegroundColor() -> Color {
|
||||
if vehicle.unrecognized {
|
||||
Color(UIColor.systemRed)
|
||||
} else if vehicle.outdated {
|
||||
Color(UIColor.systemGray3)
|
||||
} else {
|
||||
Color("PlateForeground")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
VehicleCell()
|
||||
}
|
||||
@ -23,4 +23,11 @@ struct Formatters {
|
||||
formatter.timeStyle = .short
|
||||
return formatter
|
||||
}()
|
||||
|
||||
static let short: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .short
|
||||
return formatter
|
||||
}()
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import Foundation
|
||||
import SwiftDate
|
||||
|
||||
public struct DateSection<T: Identifiable> {
|
||||
public struct DateSection<T: Identifiable>: Identifiable {
|
||||
|
||||
public var id = UUID()
|
||||
var timestamp: Double = 0
|
||||
var dateString: String
|
||||
public var elements: [T]
|
||||
@ -20,6 +21,7 @@ public struct DateSection<T: Identifiable> {
|
||||
public init(original: DateSection, items: [T]) {
|
||||
self = original
|
||||
self.elements = items
|
||||
self.id = original.id
|
||||
}
|
||||
|
||||
public init(timestamp: Double, items: [T], monthStart: DateInRegion, weekStart: DateInRegion) {
|
||||
|
||||
@ -59,6 +59,8 @@ public actor StorageService: StorageServiceProtocol {
|
||||
|
||||
public func loadVehicles() async -> [VehicleDto] {
|
||||
|
||||
realm.objects(Vehicle.self).map(\.shallowDto)
|
||||
realm.objects(Vehicle.self)
|
||||
.sorted(byKeyPath: "updatedDate", ascending: false)
|
||||
.map(\.shallowDto)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user