diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index 8be1df7..595cbb9 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -62,7 +62,7 @@ 7A4322952CB2CD0F00085CF6 /* FiltersCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322942CB2CD0F00085CF6 /* FiltersCoordinator.swift */; }; 7A45FB382C27073700618694 /* StorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A45FB372C27073700618694 /* StorageService.swift */; }; 7A4927D52CCE438600851C01 /* OptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4927D42CCE438600851C01 /* OptionalBinding.swift */; }; - 7A530B7A24001D3300CBFE6E /* CheckController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A530B7924001D3300CBFE6E /* CheckController.swift */; }; + 7A4955822D58CCF900912E66 /* HistoryFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4955812D58CCF900912E66 /* HistoryFilter.swift */; }; 7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */; }; 7A54BFD32D43B95E00176D6D /* DbUpdatePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */; }; 7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C352C18AC7F00D47C18 /* ApiError.swift */; }; @@ -332,8 +332,8 @@ 7A43F9F7246C8A6200BA5B49 /* JWT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWT.swift; sourceTree = ""; }; 7A45FB372C27073700618694 /* StorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageService.swift; sourceTree = ""; }; 7A4927D42CCE438600851C01 /* OptionalBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalBinding.swift; sourceTree = ""; }; + 7A4955812D58CCF900912E66 /* HistoryFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryFilter.swift; sourceTree = ""; }; 7A52AB292580112E002CD910 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - 7A530B7924001D3300CBFE6E /* CheckController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckController.swift; sourceTree = ""; }; 7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCell.swift; sourceTree = ""; }; 7A530B7F2401803A00CBFE6E /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; 7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DbUpdatePolicy.swift; sourceTree = ""; }; @@ -632,7 +632,6 @@ children = ( 7A813DC7250B5C6E00CC93B9 /* Location */, 7A11471923FE839000B424AF /* AuthController.swift */, - 7A530B7924001D3300CBFE6E /* CheckController.swift */, 7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */, 7A11471523FDEB2A00B424AF /* MainSplitController.swift */, 7A27ADF2249F8B650035F39E /* RecordsController.swift */, @@ -704,6 +703,7 @@ 7A131FD22D37B75500DC7755 /* HistoryScreen.swift */, 7A131FD42D37B76A00DC7755 /* HistoryViewModel.swift */, 7A131FD62D37B77E00DC7755 /* HistoryCoordinator.swift */, + 7A4955812D58CCF900912E66 /* HistoryFilter.swift */, ); path = HistoryScreen; sourceTree = ""; @@ -1411,7 +1411,6 @@ 7A64AE762469DFB600ABE48E /* ContentTransformers.swift in Sources */, 7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */, 7A11471A23FE839000B424AF /* AuthController.swift in Sources */, - 7A530B7A24001D3300CBFE6E /* CheckController.swift in Sources */, 7A64AE742469DFB600ABE48E /* MediaContentView.swift in Sources */, 7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */, 7AB9FE2A2D08CF35005DE374 /* EventsScreenMode.swift in Sources */, @@ -1443,6 +1442,7 @@ 7A4322952CB2CD0F00085CF6 /* FiltersCoordinator.swift in Sources */, 7A131FD72D37B77E00DC7755 /* HistoryCoordinator.swift in Sources */, 7A7158002C43EA6900852088 /* OwnersScreen.swift in Sources */, + 7A4955822D58CCF900912E66 /* HistoryFilter.swift in Sources */, 7A1441702C2998B200E79018 /* Formatters.swift in Sources */, 7A4322912CB2CC8A00085CF6 /* FiltersScreen.swift in Sources */, 7ABD1B472D044A3200B43213 /* GalleryScreen.swift in Sources */, diff --git a/AutoCat/Base.lproj/Main.storyboard b/AutoCat/Base.lproj/Main.storyboard index d236c0b..433e729 100644 --- a/AutoCat/Base.lproj/Main.storyboard +++ b/AutoCat/Base.lproj/Main.storyboard @@ -244,143 +244,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -429,7 +293,7 @@ - + @@ -441,9 +305,7 @@ - - @@ -555,22 +417,6 @@ - - - - - - - - - - - - - - - - @@ -609,25 +455,6 @@ - - - - - - - - - - - - - - - - - - - @@ -664,7 +491,7 @@ - + @@ -686,17 +513,14 @@ - - - diff --git a/AutoCat/Controllers/CheckController.swift b/AutoCat/Controllers/CheckController.swift deleted file mode 100644 index 79bb621..0000000 --- a/AutoCat/Controllers/CheckController.swift +++ /dev/null @@ -1,508 +0,0 @@ -import UIKit -import RealmSwift -import SwiftDate -import PKHUD -import CoreLocation -import AutoCatCore -import SwiftLocation - -enum EventAction: Equatable { - case doNotSend - case receiveAndSend -} - -enum HistoryFilter: CaseIterable, Identifiable { - case all - case unrecognized - case outdated - case notSynced - - var title: String { - switch self { - case .all: return NSLocalizedString("All", comment: ""); - case .unrecognized: return NSLocalizedString("Unrecognized", comment: ""); - case .outdated: return NSLocalizedString("Outdated", comment: ""); - case .notSynced: return NSLocalizedString("Not updated", comment: ""); - } - } - - var id: Int { - switch self { - case .all: return 0; - case .unrecognized: return 1; - case .outdated: return 2; - case .notSynced: return 3; - } - } -} - -extension String.StringInterpolation { - mutating func appendInterpolation(_ value: EventAction) { - switch value { - case .doNotSend: appendLiteral("do not send"); break - case .receiveAndSend: appendLiteral("receive and send"); break - } - } -} - -class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpdating { - - @IBOutlet weak var history: UITableView! - - private var historyDataSource: RealmSectionedDataSource! - private var historyFilter: HistoryFilter = .all - - private lazy var searchController: UISearchController = .default - .placeholder(NSLocalizedString("Search plate numbers", comment: "")) - .resultsUpdater(self) - .makeDumb() - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - navigationItem.searchController = searchController - - guard let realm = try? Realm() else { return } - - self.hideKeyboardWhenTappedAround() - - DispatchQueue.main.async { - self.historyDataSource = RealmSectionedDataSource(table: self.history, data: realm.objects(Vehicle.self).sorted(byKeyPath: "updatedDate", ascending: false)) { count in - self.navigationItem.title = String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), count) - } - } - - self.history.delegate = self - - NotificationCenter.default.addObserver(forName: .NSCalendarDayChanged, - object: nil, - queue: .main, - using: calendarDayDidChange) - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - if let index = self.history.indexPathForSelectedRow { - self.history.deselectRow(at: index, animated: true) - } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - Task { await self.handleQuickActions() } - } - - // MARK: - - - func handleQuickActions() async { - guard let ad = UIApplication.shared.delegate as? AppDelegate else { return } - - switch ad.quickAction { - case .check: - ad.quickAction = .none - if let tabBar = tabBarController as? MainTabController { - tabBar.showCheckPuller() - } - break - case .checkNumber(let number, let event): - ad.quickAction = .none - var action: EventAction = .receiveAndSend - var events: [VehicleEventDto] = [] - if let event = event { - events = [event] - action = .doNotSend - } - do { - HUD.show(.progress) - let (vehicle, errors) = try await self.check(number: number, action: action, notes: [], events: events) - if !vehicle.unrecognized { - self.updateDetailController(with: vehicle) - } - HUD.hide() - self.showErrors(errors) - } catch { - HUD.hide() - self.show(error: error) - } - break - case .addVoiceRecord: - self.tabBarController?.selectedIndex = 1 - break - case .openReport(let number): - ad.quickAction = .none - if let sd = self.view.window?.windowScene?.delegate as? SceneDelegate { - Task { await sd.openReport(with: number) } - } - break - default: - break - } - } - - nonisolated private func calendarDayDidChange(_ notification : Notification) { - MainActor.assumeIsolated { - historyDataSource.reload() - } - } - - // MARK: - Actions - - @IBAction func onExport(_ sender: UIBarButtonItem) { - let sheet = UIAlertController(title: NSLocalizedString("Export history as", comment: ""), message: nil, preferredStyle: .actionSheet) - let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) } - - let csv = UIAlertAction(title: NSLocalizedString("CSV table", comment: "export as CSV table"), style: .default) { action in - do { - let csvString = try self.historyDataSource.makeCsv() - let tmpUrl = FileManager.default.tmpUrl(name: "history", ext: "csv") - try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8) - - #if targetEnvironment(macCatalyst) - self.save(file: tmpUrl) - #else - self.shareFile(tmpUrl) - #endif - } catch { - self.show(error: error) - } - } - - let db = UIAlertAction(title: NSLocalizedString("Database file", comment: "export as database file"), style: .default) { action in - do { - let realm = try Realm() - let tmpUrl = FileManager.default.tmpUrl(name: "history", ext: "realm") - if let dbUrl = realm.configuration.fileURL { - try FileManager.default.copyItem(at: dbUrl, to: tmpUrl) - self.shareFile(tmpUrl) - } - } catch { - self.show(error: error) - } - } - - sheet.addAction(csv) - sheet.addAction(db) - sheet.addAction(cancel) - sheet.popoverPresentationController?.barButtonItem = sender - self.present(sheet, animated: true) - } - - @IBAction func onFilter(_ sender: UIBarButtonItem) { - let sheet = UIAlertController(title: NSLocalizedString("Filter check history", comment: ""), message: nil, preferredStyle: .actionSheet) - let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) } - let all = UIAlertAction(title: NSLocalizedString("All", comment: ""), style: .default) { action in - self.historyDataSource.setFilterPredicate(nil) - self.historyFilter = .all - } - let unrecognized = UIAlertAction(title: NSLocalizedString("Unrecognized", comment: ""), style: .default) { _ in - self.historyDataSource.setFilterPredicate { $0.unrecognized } - self.historyFilter = .unrecognized - } - let outdated = UIAlertAction(title: NSLocalizedString("Outdated", comment: ""), style: .default) { _ in - self.historyDataSource.setFilterPredicate { $0.outdated } - self.historyFilter = .outdated - } - let notUpdated = UIAlertAction(title: NSLocalizedString("Not updated", comment: ""), style: .default) { _ in - self.historyDataSource.setFilterPredicate { $0.needSync } - self.historyFilter = .notSynced - } - - switch self.historyFilter { - case .all: all.setValue(true, forKey: "checked") - case .unrecognized: unrecognized.setValue(true, forKey: "checked") - case .outdated: outdated.setValue(true, forKey: "checked") - case .notSynced: notUpdated.setValue(true, forKey: "checked") - } - - sheet.addAction(all) - sheet.addAction(unrecognized) - sheet.addAction(outdated) - sheet.addAction(notUpdated) - sheet.addAction(cancel) - sheet.popoverPresentationController?.barButtonItem = sender - self.present(sheet, animated: true) - } - - func shareFile(_ url: URL) { - let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil) - self.present(activityController, animated: true) - } - - func save(file url: URL) { - if #available(iOS 14, *) { - let controller = UIDocumentPickerViewController(forExporting: [url]) - self.present(controller, animated: true) - } else { - let controller = UIDocumentPickerViewController(url: url, in: .exportToService) - present(controller, animated: true) - } - } - - // MARK: - Checking new number - - func checkTapped(number: String) { - let numberNormalized = number.filter { !$0.isWhitespace }.uppercased() - - var events: [VehicleEventDto] = [] - do { - let realm = try Realm() - if let dbVehicle = realm.object(ofType: Vehicle.self, forPrimaryKey: numberNormalized) { - events.append(contentsOf: dbVehicle.events.map(\.dto)) - } - } catch { - print(error) - } - - Task { - do { - HUD.show(.progress) - let (vehicle, errors) = try await self.check(number: numberNormalized, - action: .receiveAndSend, - notes: [], - events: events) - if !vehicle.unrecognized && errors.isEmpty { - self.updateDetailController(with: vehicle) - } - HUD.hide() - self.showErrors(errors) - } catch { - HUD.hide() - self.show(error: error) - } - } - } - - func updateDetailController(with vehicle: VehicleDto) { - if let splitViewController = self.view.window?.rootViewController as? UISplitViewController - { - Task { - let coordinator = ReportCoordinator(controller: splitViewController, vehicle: vehicle, isPersistent: true) - _ = try? await coordinator.start() - } - } - } - - // MARK: - UITableViewDelegate - - func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let vehicle = self.historyDataSource.item(at: indexPath) - - let updateAction = UIContextualAction(style: .normal, title: NSLocalizedString("Update", comment: "")) { action, view, completion in - self.update(vehicle: vehicle) - completion(true) - } - updateAction.image = UIImage(systemName: "arrow.2.circlepath") - updateAction.backgroundColor = .systemBlue - - let removeAction = UIContextualAction(style: .destructive, title: NSLocalizedString("Remove", comment: "")) { action, view, completion in - self.remove(vehicle: vehicle) - } - removeAction.image = UIImage(systemName: "trash") - - let configuration = UISwipeActionsConfiguration(actions: [updateAction, removeAction]) - configuration.performsFirstActionWithFullSwipe = false - return configuration - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let vehicle = self.historyDataSource.item(at: indexPath) - self.updateDetailController(with: vehicle) - } - - func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let vehicle = self.historyDataSource.item(at: indexPath) - return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in - let update = UIAction(title: NSLocalizedString("Update", comment: ""), image: UIImage(systemName: "arrow.2.circlepath")) { action in - self.update(vehicle: vehicle) - } - let remove = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) { action in - self.remove(vehicle: vehicle) - } - - return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: [update, remove]) - } - } - - // MARK: - UISearchResultsUpdating - - func updateSearchResults(for searchController: UISearchController) { - guard let text = searchController.searchBar.text?.uppercased(), - !text.isEmpty - else { - historyDataSource.setSearchPredicate(nil) - return - } - - let regex = try? NSRegularExpression(pattern: text) - - historyDataSource.setSearchPredicate { vehicle in - let number = vehicle.getNumber() - if let regex { - let range = NSRange(location: 0, length: number.utf16.count) - return regex.numberOfMatches(in: number, range: range) > 0 - } else { - return number.contains(text) - } - } - } - - // MARK: - Contextual actions - - func update(vehicle: VehicleDto) { - Task { - do { - HUD.show(.progress) - let (vehicle, errors) = try await self.check(number: vehicle.getNumber(), - action: .doNotSend, - notes: Array(vehicle.notes), - events: Array(vehicle.events), - force: true) - if !vehicle.unrecognized { - self.updateDetailController(with: vehicle) - } - HUD.hide() - self.showErrors(errors) - } catch { - HUD.hide() - self.show(error: error) - } - } - } - - func remove(vehicle: VehicleDto) { - guard let realm = try? Realm() else { return } - guard let realmVehicle = realm.object(ofType: Vehicle.self, forPrimaryKey: vehicle.getNumber()) else { return } - - do { - try realm.write { - realm.delete(realmVehicle) - } - } catch { - print(error) - } - } - - // MARK: - Checking number - - func save(vehicle: VehicleDto) throws { - let realm = try Realm() - try realm.write { - realm.add(Vehicle(dto: vehicle), update: .all) - } - } - - func getEvent(for action: EventAction) async throws -> VehicleEventDto { - if let event = await RxLocationManager.getLastEvent(), (Date().timeIntervalSince1970 - event.date) < 100 { - return event - } else { - return try await RxLocationManager.requestCurrentLocation() - } - } - - func prepareEvent(for action: EventAction) async -> (event: VehicleEventDto?, error: Error?) { - guard action != .doNotSend else { - return (event: nil, error: nil) - } - - do { - var event = try await getEvent(for: action) - event.address = try? await RxLocationManager.getAddressForLocation(latitude: event.latitude, - longitude: event.longitude) - return (event: event, error: nil) - } catch { - return (event: nil, error: error) - } - } - - func checkVehicle(number: String, - notes: [VehicleNoteDto], - events: [VehicleEventDto], - force: Bool = false) async -> (vehicle: VehicleDto, error: Error?) { - - do { - let vehicle = try await ApiService.shared.checkVehicle(by: number, notes: notes, events: events, force: force) - try self.save(vehicle: vehicle) - return (vehicle: vehicle, error: nil) - } catch { - let realm = try? await Realm() - if let existingVehicle = realm?.object(ofType: Vehicle.self, forPrimaryKey: number) { - return (vehicle: existingVehicle.dto, error: error) - } else { - let vehicle = Vehicle(number) - try? realm?.write { realm?.add(vehicle, update: .all) } - return (vehicle: vehicle.dto, error: error) - } - } - } - - func check(number: String, - action: EventAction, - notes: [VehicleNoteDto], - events: [VehicleEventDto], - force: Bool = false) async throws -> (vehicle: VehicleDto, errors: [Error]) { - - async let eventTask = prepareEvent(for: action) - async let vehicleTask = checkVehicle(number: number, notes: notes, events: events, force: force) - let (eventResult, vehicleResult) = await (eventTask, vehicleTask) - - var errors = [eventResult.error, vehicleResult.error].map { error -> Error? in - if let clerror = error as? CLError { - if clerror.code != .denied { - return CocoaError.error(NSLocalizedString("Location error", comment: ""), reason: clerror.code.description) - } else { - return nil - } - } else { - return error - } - } - .compactMap { $0 } - - RxLocationManager.resetLastEvent() - - let realm = try await Realm() - let dbVehicle = realm.object(ofType: Vehicle.self, forPrimaryKey: vehicleResult.vehicle.getNumber()) - if let event = eventResult.event, let vehicle = dbVehicle { - try realm.write { - vehicle.events.append(VehicleEvent(dto: event)) - vehicle.updatedDate = Date().timeIntervalSince1970 - vehicle.synchronized = false - } - } - - if vehicleResult.error != nil { - return (vehicle: vehicleResult.vehicle, errors: errors) - } else { - if let event = eventResult.event { - do { - let vehicle = try await ApiService.shared.add(event: event, to: vehicleResult.vehicle.getNumber()) - try self.save(vehicle: vehicle) - return (vehicle: vehicle, errors: errors) - } catch { - errors.append(error) - return (vehicle: vehicleResult.vehicle, errors: errors) - } - } else { - return (vehicle: vehicleResult.vehicle, errors: errors) - } - } - } - - func showErrors(_ errors: [Error]) { - Task { - for error in errors { - await asyncShowError(error) - } - } - } - - func asyncShowError(_ error: Error) async { - await withCheckedContinuation { continuation in - self.show(error: error, animated: true) { - continuation.resume() - } - } - } -} diff --git a/AutoCat/Info.plist b/AutoCat/Info.plist index 54e4199..849bcbd 100644 --- a/AutoCat/Info.plist +++ b/AutoCat/Info.plist @@ -99,25 +99,6 @@ - UIApplicationShortcutItems - - - UIApplicationShortcutItemIconType - UIApplicationShortcutIconTypeAudio - UIApplicationShortcutItemTitle - Add voice record - UIApplicationShortcutItemType - AddVoiceRecordAction - - - UIApplicationShortcutItemIconType - UIApplicationShortcutIconTypeCompose - UIApplicationShortcutItemTitle - Check plate number - UIApplicationShortcutItemType - CheckNumberAction - - UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/AutoCat/SceneDelegate.swift b/AutoCat/SceneDelegate.swift index 684f426..abc58ae 100644 --- a/AutoCat/SceneDelegate.swift +++ b/AutoCat/SceneDelegate.swift @@ -17,9 +17,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let ad = UIApplication.shared.delegate as? AppDelegate else { return } if let activity = connectionOptions.userActivities.first { - if activity.activityType == "pro.aliencat.autocat.addVoiceRecord" { - ad.quickAction = .addVoiceRecord - } if let url = activity.webpageURL { if let param = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.first, let token = param.value { @@ -144,34 +141,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { - guard let ad = UIApplication.shared.delegate as? AppDelegate else { return } - - if shortcutItem.type == "CheckNumberAction" { - ad.quickAction = .check - - if let split = self.window?.rootViewController as? MainSplitController, let tabvc = split.viewControllers.first as? UITabBarController { - if tabvc.selectedIndex == 0 { - if let nav = tabvc.selectedViewController as? UINavigationController, let child = nav.topViewController { - if let check = child as? CheckController { - Task { await check.handleQuickActions() } - } else { - nav.popToRootViewController(animated: false) - } - } - } else { - tabvc.selectedIndex = 0 - } - } - } else if shortcutItem.type == "AddVoiceRecordAction" { - self.handleAddVoiceRecordAction() - } + } func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - if userActivity.activityType == "pro.aliencat.autocat.addVoiceRecord" { - self.handleAddVoiceRecordAction() - } - + if let url = userActivity.webpageURL { if let param = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.first, let token = param.value { if let jwt = JWT(string: token) { @@ -181,25 +155,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - func handleAddVoiceRecordAction() { - guard let ad = UIApplication.shared.delegate as? AppDelegate else { return } - ad.quickAction = .addVoiceRecord - - if let split = self.window?.rootViewController as? MainSplitController, let tabvc = split.viewControllers.first as? UITabBarController { - if tabvc.selectedIndex == 1 { - if let nav = tabvc.selectedViewController as? UINavigationController, let child = nav.topViewController { - if let record = child as? RecordsController { - record.handleQuickActions() - } else { - nav.popToRootViewController(animated: false) - } - } - } else { - tabvc.selectedIndex = 1 - } - } - } - func openReport(with number: String) async { guard let rootController = self.window?.rootViewController else { return } diff --git a/AutoCat/Screens/HistoryScreen/HistoryFilter.swift b/AutoCat/Screens/HistoryScreen/HistoryFilter.swift new file mode 100644 index 0000000..1053740 --- /dev/null +++ b/AutoCat/Screens/HistoryScreen/HistoryFilter.swift @@ -0,0 +1,34 @@ +// +// HistoryFilter.swift +// AutoCat +// +// Created by Selim Mustafaev on 09.02.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import Foundation + +enum HistoryFilter: CaseIterable, Identifiable { + case all + case unrecognized + case outdated + case notSynced + + var title: String { + switch self { + case .all: return NSLocalizedString("All", comment: ""); + case .unrecognized: return NSLocalizedString("Unrecognized", comment: ""); + case .outdated: return NSLocalizedString("Outdated", comment: ""); + case .notSynced: return NSLocalizedString("Not updated", comment: ""); + } + } + + var id: Int { + switch self { + case .all: return 0; + case .unrecognized: return 1; + case .outdated: return 2; + case .notSynced: return 3; + } + } +}