Adding sharing to SwiftUI version of history screen
This commit is contained in:
parent
13a0bbe895
commit
a7f4e6b3c5
@ -48,7 +48,6 @@
|
||||
7A1E78FA2CE9005C0004B740 /* ReportCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F92CE9005C0004B740 /* ReportCoordinator.swift */; };
|
||||
7A1E78FF2CE91A740004B740 /* Vehicle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78FE2CE91A740004B740 /* Vehicle.swift */; };
|
||||
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF2249F8B650035F39E /* RecordsController.swift */; };
|
||||
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */; };
|
||||
7A27ADF7249FEF690035F39E /* Recorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF6249FEF690035F39E /* Recorder.swift */; };
|
||||
7A2C96122C3B155B00AE46B5 /* NoteAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2C96112C3B155B00AE46B5 /* NoteAlertModifier.swift */; };
|
||||
7A2E11292CCE395300E5CA17 /* OptionalDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2E11282CCE395300E5CA17 /* OptionalDatePicker.swift */; };
|
||||
@ -147,6 +146,8 @@
|
||||
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 */; };
|
||||
7AB4E4332D3C21C00006D052 /* FileManagerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4E4322D3C21C00006D052 /* FileManagerExt.swift */; };
|
||||
7AB4E4382D3D0C5C0006D052 /* VehiclesArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4E4372D3D0C5C0006D052 /* VehiclesArchive.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 */; };
|
||||
@ -309,7 +310,6 @@
|
||||
7A1E78F92CE9005C0004B740 /* ReportCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A1E78FE2CE91A740004B740 /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = "<group>"; };
|
||||
7A27ADF2249F8B650035F39E /* RecordsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsController.swift; sourceTree = "<group>"; };
|
||||
7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExt.swift; sourceTree = "<group>"; };
|
||||
7A27ADF6249FEF690035F39E /* Recorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recorder.swift; sourceTree = "<group>"; };
|
||||
7A27ADF824A09CAD0035F39E /* CocoaError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaError.swift; sourceTree = "<group>"; };
|
||||
7A2C96112C3B155B00AE46B5 /* NoteAlertModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteAlertModifier.swift; sourceTree = "<group>"; };
|
||||
@ -419,6 +419,8 @@
|
||||
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>"; };
|
||||
7AB4E4322D3C21C00006D052 /* FileManagerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExt.swift; sourceTree = "<group>"; };
|
||||
7AB4E4372D3D0C5C0006D052 /* VehiclesArchive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehiclesArchive.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>"; };
|
||||
@ -659,6 +661,7 @@
|
||||
7A11474823FF2B2D00B424AF /* Response.swift */,
|
||||
7A11474623FF2AA500B424AF /* User.swift */,
|
||||
7AB562B9249C9E9B00473D53 /* VehicleRegion.swift */,
|
||||
7AB4E4372D3D0C5C0006D052 /* VehiclesArchive.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@ -738,7 +741,6 @@
|
||||
7A8AB76425A0DB8F00ECF2C1 /* BundleVersion.swift */,
|
||||
7AE24C5E251F1B4E00758E39 /* Buttons.swift */,
|
||||
7A3F07AA24360DC800E59687 /* Dated.swift */,
|
||||
7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */,
|
||||
7ADF6CA02512244400F237B2 /* MapExt.swift */,
|
||||
7ADF6C92250B954900F237B2 /* Navigation.swift */,
|
||||
7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */,
|
||||
@ -1061,6 +1063,7 @@
|
||||
7AF6D2292677C3950086EA64 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AB4E4322D3C21C00006D052 /* FileManagerExt.swift */,
|
||||
7A27ADF824A09CAD0035F39E /* CocoaError.swift */,
|
||||
7AE8424D26109F78002F6B31 /* Exportable.swift */,
|
||||
7A1CF81529A42117007962DA /* Realm.swift */,
|
||||
@ -1342,7 +1345,6 @@
|
||||
7AF860702CBAA24500954D2F /* NavigationLink.swift in Sources */,
|
||||
7AB9FE262D08C2D7005DE374 /* EventsCoordinator.swift in Sources */,
|
||||
7AB9FE282D08C2F4005DE374 /* EventsViewModel.swift in Sources */,
|
||||
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */,
|
||||
7A4927D52CCE438600851C01 /* OptionalBinding.swift in Sources */,
|
||||
7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */,
|
||||
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */,
|
||||
@ -1485,6 +1487,7 @@
|
||||
7A60D24F2C5A9DA800D13F7B /* LocationServiceProtocol.swift in Sources */,
|
||||
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
|
||||
7A64A2032C19DA1000284124 /* VehicleDto.swift in Sources */,
|
||||
7AB4E4332D3C21C00006D052 /* FileManagerExt.swift in Sources */,
|
||||
7AB587322C42D38E00FA7B66 /* StorageServiceProtocol.swift in Sources */,
|
||||
7A3E12D72C7B42B700EE710D /* UserDefaults+Settings.swift in Sources */,
|
||||
7AA514E02D0B75B3001CAC50 /* StorageService+Events.swift in Sources */,
|
||||
@ -1493,6 +1496,7 @@
|
||||
7A64A2202C19E93500284124 /* VehicleNoteDto.swift in Sources */,
|
||||
7AF6D21A2677C1680086EA64 /* User.swift in Sources */,
|
||||
7A60D2512C5A9E4200D13F7B /* GeocoderProtocol.swift in Sources */,
|
||||
7AB4E4382D3D0C5C0006D052 /* VehiclesArchive.swift in Sources */,
|
||||
7A64A21C2C19E87B00284124 /* OsagoDto.swift in Sources */,
|
||||
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
|
||||
7A1CF81629A42117007962DA /* Realm.swift in Sources */,
|
||||
|
||||
@ -13,6 +13,7 @@ struct HistoryScreen: View {
|
||||
|
||||
@State var viewModel: HistoryViewModel
|
||||
@State var filterSheetPresented = false
|
||||
@State var exportSheetPresented = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
@ -24,14 +25,20 @@ struct HistoryScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.hud($viewModel.hud)
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""),
|
||||
viewModel.vehicleSections.reduce(0, { $0 + $1.elements.count })))
|
||||
viewModel.vehiclesCount))
|
||||
.searchable(text: $viewModel.searchText, prompt: "Search plate numbers")
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(.asciiCapable)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("", systemImage: "square.and.arrow.up") {
|
||||
exportSheetPresented = true
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("", systemImage: "line.horizontal.3.decrease") {
|
||||
filterSheetPresented = true
|
||||
@ -46,6 +53,18 @@ struct HistoryScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Export history as", isPresented: $exportSheetPresented, titleVisibility: .visible) {
|
||||
|
||||
ShareLink(item: viewModel.vehiclesArchive, preview: SharePreview(VehiclesArchive.fileName)) {
|
||||
Text("CSV table")
|
||||
}
|
||||
|
||||
if let dbUrl = viewModel.dbFileURL {
|
||||
ShareLink(item: dbUrl) {
|
||||
Text("Database file")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,11 +11,13 @@ import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
final class HistoryViewModel {
|
||||
final class HistoryViewModel: ACHudContainer {
|
||||
|
||||
let apiService: ApiServiceProtocol
|
||||
let storageService: StorageServiceProtocol
|
||||
|
||||
var hud: ACHud?
|
||||
|
||||
var vehicles: [VehicleDto] = []
|
||||
var vehiclesFiltered: [VehicleDto] = []
|
||||
var vehicleSections: [DateSection<VehicleDto>] = []
|
||||
@ -30,12 +32,24 @@ final class HistoryViewModel {
|
||||
|
||||
var filter: HistoryFilter = .all
|
||||
|
||||
init(apiService: ApiServiceProtocol, storageService: StorageServiceProtocol) {
|
||||
var vehiclesArchive: VehiclesArchive {
|
||||
VehiclesArchive(vehiles: vehiclesFiltered)
|
||||
}
|
||||
|
||||
var vehiclesCount: Int {
|
||||
vehicleSections.reduce(0, { $0 + $1.elements.count })
|
||||
}
|
||||
|
||||
var dbFileURL: URL?
|
||||
|
||||
init(apiService: ApiServiceProtocol,
|
||||
storageService: StorageServiceProtocol) {
|
||||
|
||||
self.apiService = apiService
|
||||
self.storageService = storageService
|
||||
|
||||
Task { await loadVehicles() }
|
||||
Task { dbFileURL = await storageService.dbFileURL }
|
||||
}
|
||||
|
||||
func loadVehicles() async {
|
||||
|
||||
@ -23,8 +23,8 @@ struct VehicleCellView: View {
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
if vehicle.synchronized || vehicle.unrecognized {
|
||||
Image(systemName: "exclamationmark.arrow.triangle")
|
||||
if !vehicle.synchronized && !vehicle.unrecognized {
|
||||
Image(systemName: "exclamationmark.arrow.triangle.2.circlepath")
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
extension FileManager {
|
||||
public extension FileManager {
|
||||
func url(for file: String, in dir: String) throws -> URL {
|
||||
guard let docUrl = self.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
throw CocoaError(.fileReadNoSuchFile)
|
||||
66
AutoCatCore/Models/VehiclesArchive.swift
Normal file
66
AutoCatCore/Models/VehiclesArchive.swift
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// VehiclesArchive.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 19.01.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public enum VehiclesArchiveError: LocalizedError {
|
||||
|
||||
case filedCreateCsv
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .filedCreateCsv: "Failed to create csv data for vehicles"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class VehiclesArchive {
|
||||
|
||||
let vehicles: [VehicleDto]
|
||||
|
||||
public init(vehiles: [VehicleDto]) {
|
||||
|
||||
self.vehicles = vehiles
|
||||
}
|
||||
|
||||
func makeCsvString() throws -> String {
|
||||
|
||||
var result = ""
|
||||
let newLine: Character = "\r\n"
|
||||
result.append(VehicleDto.csvHeader)
|
||||
result.append(newLine)
|
||||
|
||||
for vehicle in vehicles {
|
||||
result.append(vehicle.csvLine)
|
||||
result.append(newLine)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension VehiclesArchive: Transferable {
|
||||
|
||||
public static var fileName: String {
|
||||
"autocat.csv"
|
||||
}
|
||||
|
||||
public static var transferRepresentation: some TransferRepresentation {
|
||||
|
||||
DataRepresentation(exportedContentType: .commaSeparatedText) { archive in
|
||||
|
||||
let csvString = try archive.makeCsvString()
|
||||
if let data = csvString.data(using: .utf8){
|
||||
return data
|
||||
} else {
|
||||
throw VehiclesArchiveError.filedCreateCsv
|
||||
}
|
||||
}
|
||||
.suggestedFileName(fileName)
|
||||
}
|
||||
}
|
||||
@ -37,6 +37,12 @@ public actor StorageService: StorageServiceProtocol {
|
||||
realm = try await Realm(configuration: config, actor: self)
|
||||
}
|
||||
|
||||
public var dbFileURL: URL? {
|
||||
get async {
|
||||
realm.configuration.fileURL
|
||||
}
|
||||
}
|
||||
|
||||
public func updateVehicleIfExists(dto: VehicleDto) async throws {
|
||||
|
||||
guard realm.object(ofType: Vehicle.self, forPrimaryKey: dto.getNumber()) != nil else {
|
||||
|
||||
@ -7,10 +7,14 @@
|
||||
//
|
||||
|
||||
import Mockable
|
||||
import Foundation
|
||||
|
||||
@Mockable
|
||||
public protocol StorageServiceProtocol: Sendable {
|
||||
|
||||
// Generic
|
||||
var dbFileURL: URL? { get async }
|
||||
|
||||
// Vehicles
|
||||
func loadVehicles() async -> [VehicleDto]
|
||||
func loadVehicle(number: String) async throws -> VehicleDto
|
||||
|
||||
Loading…
Reference in New Issue
Block a user