From 544f81be5efc61ca0a87fdaea56739f1c6504640 Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Sun, 20 Apr 2025 14:46:35 +0300 Subject: [PATCH] Adding split view for ipad/mac --- AutoCat.xcodeproj/project.pbxproj | 24 ++- .../Extensions/SearchControllerExt.swift | 57 ----- AutoCat/AppDelegate.swift | 40 ---- AutoCat/SceneDelegate.swift | 86 -------- .../Screens/FiltersScreen/FiltersScreen.swift | 12 +- .../Screens/HistoryScreen/HistoryScreen.swift | 110 ++++------ .../HistoryScreen/HistorySplitScreen.swift | 29 +++ .../HistoryScreen/HistoryViewModel.swift | 16 +- .../Screens/MainTabScreen/MainTabScreen.swift | 8 +- AutoCat/Screens/MapScreen/MapScreen.swift | 15 +- .../Screens/ReportScreen/ReportScreen.swift | 204 +++++++++--------- .../Screens/SearchScreen/SearchScreen.swift | 79 +++---- .../SearchScreen/SearchSplitScreen.swift | 37 ++++ .../SearchScreen/SearchViewModel.swift | 14 ++ AutoCat/SwiftUI/HideTabBarModifier.swift | 32 +++ 15 files changed, 347 insertions(+), 416 deletions(-) delete mode 100644 AutoCat/ACUIKit/Extensions/SearchControllerExt.swift create mode 100644 AutoCat/Screens/HistoryScreen/HistorySplitScreen.swift create mode 100644 AutoCat/Screens/SearchScreen/SearchSplitScreen.swift create mode 100644 AutoCat/SwiftUI/HideTabBarModifier.swift diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index 1344095..5ac90ae 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -37,7 +37,6 @@ 7A2C96122C3B155B00AE46B5 /* NoteAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2C96112C3B155B00AE46B5 /* NoteAlertModifier.swift */; }; 7A2E11292CCE395300E5CA17 /* OptionalDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2E11282CCE395300E5CA17 /* OptionalDatePicker.swift */; }; 7A2E6FA72C42B3AD00C40DA7 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; }; - 7A3399AB299063370087DF98 /* SearchControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3399AA299063370087DF98 /* SearchControllerExt.swift */; }; 7A386A402DABDC190051676A /* MapScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A386A3F2DABDC190051676A /* MapScreen.swift */; }; 7A386A442DABDC360051676A /* MapViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A386A432DABDC360051676A /* MapViewModel.swift */; }; 7A386A482DABE0D00051676A /* MapMarkerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A386A472DABE0D00051676A /* MapMarkerModel.swift */; }; @@ -162,11 +161,14 @@ 7ABDA80F2D8723F90083C715 /* StorageService+AudioRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABDA80E2D8723F90083C715 /* StorageService+AudioRecords.swift */; }; 7AC3554A2969652F00889457 /* SwiftEntryKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7AC355492969652F00889457 /* SwiftEntryKit */; }; 7AC3555029696D5A00889457 /* NewNumberController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3554F29696D5A00889457 /* NewNumberController.swift */; }; - 7AC3555229696E3F00889457 /* UIView+layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3555129696E3F00889457 /* UIView+layout.swift */; }; 7AC35554296973E100889457 /* ACButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC35553296973E100889457 /* ACButton.swift */; }; 7AC355592969746600889457 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC355582969746600889457 /* UIControl.swift */; }; 7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3555A296995B200889457 /* UIEdgeInsets.swift */; }; 7AC44B822DB390B900ADC026 /* MainTabScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B812DB390B900ADC026 /* MainTabScreen.swift */; }; + 7AC44B842DB3EF7A00ADC026 /* HistorySplitScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B832DB3EF7A00ADC026 /* HistorySplitScreen.swift */; }; + 7AC44B862DB40A5C00ADC026 /* HideTabBarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B852DB40A5C00ADC026 /* HideTabBarModifier.swift */; }; + 7AC44B882DB438F200ADC026 /* UIView+layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B872DB438F200ADC026 /* UIView+layout.swift */; }; + 7AC44B8A2DB4395300ADC026 /* SearchSplitScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B892DB4395300ADC026 /* SearchSplitScreen.swift */; }; 7AC76D7B270083AE0084DB27 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC76D7A270083AE0084DB27 /* TextView.swift */; }; 7AC8B2762D6A01C700190706 /* UISearchTextField+Dumb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC8B2752D6A01C700190706 /* UISearchTextField+Dumb.swift */; }; 7ACBB91E2CB9B155005A5168 /* Mockable in Frameworks */ = {isa = PBXBuildFile; productRef = 7ACBB91D2CB9B155005A5168 /* Mockable */; }; @@ -305,7 +307,6 @@ 7A2E11282CCE395300E5CA17 /* OptionalDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalDatePicker.swift; sourceTree = ""; }; 7A2E6FA32C42B3AD00C40DA7 /* AutoCatCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AutoCatCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7A333813249A532400D878F1 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = ""; }; - 7A3399AA299063370087DF98 /* SearchControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchControllerExt.swift; sourceTree = ""; }; 7A386A3F2DABDC190051676A /* MapScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapScreen.swift; sourceTree = ""; }; 7A386A432DABDC360051676A /* MapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewModel.swift; sourceTree = ""; }; 7A386A472DABE0D00051676A /* MapMarkerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapMarkerModel.swift; sourceTree = ""; }; @@ -433,11 +434,14 @@ 7ABDA80C2D8721B10083C715 /* Substrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Substrings.swift; sourceTree = ""; }; 7ABDA80E2D8723F90083C715 /* StorageService+AudioRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageService+AudioRecords.swift"; sourceTree = ""; }; 7AC3554F29696D5A00889457 /* NewNumberController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNumberController.swift; sourceTree = ""; }; - 7AC3555129696E3F00889457 /* UIView+layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+layout.swift"; sourceTree = ""; }; 7AC35553296973E100889457 /* ACButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACButton.swift; sourceTree = ""; }; 7AC355582969746600889457 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = ""; }; 7AC3555A296995B200889457 /* UIEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEdgeInsets.swift; sourceTree = ""; }; 7AC44B812DB390B900ADC026 /* MainTabScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabScreen.swift; sourceTree = ""; }; + 7AC44B832DB3EF7A00ADC026 /* HistorySplitScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistorySplitScreen.swift; sourceTree = ""; }; + 7AC44B852DB40A5C00ADC026 /* HideTabBarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideTabBarModifier.swift; sourceTree = ""; }; + 7AC44B872DB438F200ADC026 /* UIView+layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+layout.swift"; sourceTree = ""; }; + 7AC44B892DB4395300ADC026 /* SearchSplitScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSplitScreen.swift; sourceTree = ""; }; 7AC76D7A270083AE0084DB27 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; 7AC8B2752D6A01C700190706 /* UISearchTextField+Dumb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchTextField+Dumb.swift"; sourceTree = ""; }; 7ADF6C92250B954900F237B2 /* Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigation.swift; sourceTree = ""; }; @@ -676,6 +680,7 @@ 7A131FD12D37B74100DC7755 /* HistoryScreen */ = { isa = PBXGroup; children = ( + 7AC44B832DB3EF7A00ADC026 /* HistorySplitScreen.swift */, 7A131FD22D37B75500DC7755 /* HistoryScreen.swift */, 7A131FD42D37B76A00DC7755 /* HistoryViewModel.swift */, 7A4955812D58CCF900912E66 /* HistoryFilter.swift */, @@ -789,6 +794,7 @@ 7A5911EC2D63225500EC51BA /* SearchScreen */ = { isa = PBXGroup; children = ( + 7AC44B892DB4395300ADC026 /* SearchSplitScreen.swift */, 7A5911ED2D63226F00EC51BA /* SearchScreen.swift */, 7A5911EF2D63266B00EC51BA /* SearchViewModel.swift */, ); @@ -1069,13 +1075,12 @@ 7AC355572969744100889457 /* Extensions */ = { isa = PBXGroup; children = ( - 7A3399AA299063370087DF98 /* SearchControllerExt.swift */, - 7AC3555129696E3F00889457 /* UIView+layout.swift */, 7AC355582969746600889457 /* UIControl.swift */, 7AC3555A296995B200889457 /* UIEdgeInsets.swift */, 7A91894E29A2BD8700519C74 /* GestureRecognizers.swift */, 7A17CE492A2E820300626A6E /* UIStackView.swift */, 7A17CE4B2A2E850200626A6E /* UISegmentedControl.swift */, + 7AC44B872DB438F200ADC026 /* UIView+layout.swift */, ); path = Extensions; sourceTree = ""; @@ -1169,6 +1174,7 @@ 7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */, 7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */, 7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */, + 7AC44B852DB40A5C00ADC026 /* HideTabBarModifier.swift */, ); path = SwiftUI; sourceTree = ""; @@ -1408,11 +1414,11 @@ 7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */, 7A11470123FDE7E500B424AF /* AppDelegate.swift in Sources */, 7A1E78FF2CE91A740004B740 /* Vehicle.swift in Sources */, - 7A3399AB299063370087DF98 /* SearchControllerExt.swift in Sources */, 7A14416E2C297F7C00E79018 /* Coordinator.swift in Sources */, 7A6DD90824329144009DE740 /* CenterTextLayer.swift in Sources */, 7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */, 7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */, + 7AC44B8A2DB4395300ADC026 /* SearchSplitScreen.swift in Sources */, 7AAAFADA2C4D1AFE0050410D /* Zoomable.swift in Sources */, 7ADFC9572DAD0288001A43E3 /* GoogleAuthScreen.swift in Sources */, 7AC8B2762D6A01C700190706 /* UISearchTextField+Dumb.swift in Sources */, @@ -1430,12 +1436,14 @@ 7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */, 7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */, 7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */, + 7AC44B882DB438F200ADC026 /* UIView+layout.swift in Sources */, 7A6C65222D999325001240C2 /* AudioRecordViewModel.swift in Sources */, 7A06E0AE2C7065C7005731AC /* SettingsViewModel.swift in Sources */, 7AB4E42C2D397D8E0006D052 /* VehicleCellView.swift in Sources */, 7A961C6E2C4C3C9E00CE2211 /* LinkRowView.swift in Sources */, 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */, 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */, + 7AC44B842DB3EF7A00ADC026 /* HistorySplitScreen.swift in Sources */, 7AC44B822DB390B900ADC026 /* MainTabScreen.swift in Sources */, 7A7158122C444A6400852088 /* AdsViewModel.swift in Sources */, 7AF231952DA1C29300AE5EB3 /* AuthViewModel.swift in Sources */, @@ -1471,7 +1479,6 @@ 7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */, 7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */, 7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */, - 7AC3555229696E3F00889457 /* UIView+layout.swift in Sources */, 7A9519822D80B6E500E69883 /* RecordsViewModel.swift in Sources */, 7AC355592969746600889457 /* UIControl.swift in Sources */, 7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */, @@ -1495,6 +1502,7 @@ 7A5911F02D63266B00EC51BA /* SearchViewModel.swift in Sources */, 7A589E0F2D6B6E8E00EF3FBE /* NumberEditView.swift in Sources */, 7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */, + 7AC44B862DB40A5C00ADC026 /* HideTabBarModifier.swift in Sources */, 7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */, 7A9519802D80B6C100E69883 /* RecordsScreen.swift in Sources */, 7A131FD52D37B76A00DC7755 /* HistoryViewModel.swift in Sources */, diff --git a/AutoCat/ACUIKit/Extensions/SearchControllerExt.swift b/AutoCat/ACUIKit/Extensions/SearchControllerExt.swift deleted file mode 100644 index 215446d..0000000 --- a/AutoCat/ACUIKit/Extensions/SearchControllerExt.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// SearchControllerExt.swift -// AutoCat -// -// Created by Selim Mustafaev on 06.02.2023. -// Copyright © 2023 Selim Mustafaev. All rights reserved. -// - -import UIKit - -extension UISearchController { - - static var `default`: UISearchController { - let searchController = UISearchController(searchResultsController: nil) - searchController.obscuresBackgroundDuringPresentation = false - searchController.hidesNavigationBarDuringPresentation = false - searchController.searchBar.keyboardType = .webSearch - - if #available(iOS 16, *) { - searchController.scopeBarActivation = .onTextEntry - } else { - searchController.automaticallyShowsScopeBar = true - } - - return searchController - } - - func placeholder(_ placeholder: String) -> UISearchController { - searchBar.placeholder = placeholder - return self - } - - func resultsUpdater(_ updater: UISearchResultsUpdating) -> UISearchController { - searchResultsUpdater = updater - return self - } - - func makeDumb() -> UISearchController { - searchBar.autocorrectionType = .no - searchBar.spellCheckingType = .no - searchBar.autocapitalizationType = .none - searchBar.smartDashesType = .no - searchBar.smartQuotesType = .no - searchBar.smartInsertDeleteType = .no - return self - } - - func scopeButtons(_ buttons: [String]) -> UISearchController { - searchBar.scopeButtonTitles = buttons - return self - } - - func searchBarDelegate(_ delegate: UISearchBarDelegate) -> UISearchController { - searchBar.delegate = delegate - return self - } -} diff --git a/AutoCat/AppDelegate.swift b/AutoCat/AppDelegate.swift index 9a3fa99..84cc9a4 100644 --- a/AutoCat/AppDelegate.swift +++ b/AutoCat/AppDelegate.swift @@ -10,23 +10,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { HUD.dimsBackground = true HUD.allowsInteraction = false - setupAppearance() - return true } - - func setupAppearance() { - - let tabBarAppearance = UITabBarAppearance() - tabBarAppearance.configureWithDefaultBackground() - UITabBar.appearance().standardAppearance = tabBarAppearance - UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance - - let navigationBarAppearance = UINavigationBarAppearance() - navigationBarAppearance.configureWithDefaultBackground() - UINavigationBar.appearance().standardAppearance = navigationBarAppearance - UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance - } // MARK: UISceneSession Lifecycle @@ -42,30 +27,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - - // MARK: - Global menu - - #if targetEnvironment(macCatalyst) - - override func buildMenu(with builder: UIMenuBuilder) { - super.buildMenu(with: builder) - - let command = UIKeyCommand(title: NSLocalizedString("Add new number", comment: ""), - action: #selector(openAddNumberPanel), - input: "n", - modifierFlags: .command) - let menu = UIMenu(options: .displayInline, children: [command]) - builder.insertChild(menu, atEndOfMenu: .file) - } - - @objc func openAddNumberPanel() { - guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate else { - return - } - - sceneDelegate.showAddNumberPanel() - } - - #endif } diff --git a/AutoCat/SceneDelegate.swift b/AutoCat/SceneDelegate.swift index 9bb25ab..c5f161f 100644 --- a/AutoCat/SceneDelegate.swift +++ b/AutoCat/SceneDelegate.swift @@ -97,21 +97,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - #if targetEnvironment(macCatalyst) - - let toolbar = NSToolbar(identifier: "main") - toolbar.delegate = self - toolbar.displayMode = .iconOnly - - if let titlebar = windowScene.titlebar { - titlebar.toolbar = toolbar - if #available(macCatalyst 14.0, *) { - titlebar.toolbarStyle = .unifiedCompact - } - } - - #endif - self.window?.makeKeyAndVisible() } @@ -180,74 +165,3 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } } - -#if targetEnvironment(macCatalyst) - -extension NSToolbarItem.Identifier { - - static let addNumber = NSToolbarItem.Identifier("pro.aliencat.add.number") -} - -extension SceneDelegate: NSToolbarDelegate { - - func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - let identifiers: [NSToolbarItem.Identifier] = [ - .addNumber - ] - return identifiers - } - - func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - return toolbarDefaultItemIdentifiers(toolbar) - } - - func toolbar(_ toolbar: NSToolbar, - itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, - willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { - - var toolbarItem: NSToolbarItem? - - switch itemIdentifier { - case .addNumber: - let item = NSToolbarItem(itemIdentifier: itemIdentifier) - item.image = UIImage(systemName: "plus") - item.label = "Add" - item.target = self - item.action = #selector(showAddNumberPanel) - toolbarItem = item - - default: - toolbarItem = nil - } - - return toolbarItem - } - - @objc func showAddNumberPanel() { - let controller = NewNumberController() - controller.preferredContentSize = CGSize(width: 400, height: 350) - controller.onCheck = { number in - controller.dismiss(animated: true) { [weak self] in - self?.checkNewNumber(number) - } - } - - self.window?.rootViewController?.present(controller, animated: true) - } - - func checkNewNumber(_ number: String) { - guard let split = self.window?.rootViewController as? MainSplitController, - let tabvc = split.viewControllers.first as? MainTabController - else { - return - } - - tabvc.selectedIndex = 0 - - Task { - await tabvc.historyViewModel?.checkNewNumber(number) - } - } -} - -#endif diff --git a/AutoCat/Screens/FiltersScreen/FiltersScreen.swift b/AutoCat/Screens/FiltersScreen/FiltersScreen.swift index 1618d95..49f6855 100644 --- a/AutoCat/Screens/FiltersScreen/FiltersScreen.swift +++ b/AutoCat/Screens/FiltersScreen/FiltersScreen.swift @@ -20,6 +20,7 @@ enum DetailScreen: Hashable { struct FiltersScreen: View { @Environment(\.dismiss) var dismiss + @Environment(\.horizontalSizeClass) var horizontalSizeClass @State var viewModel: FiltersViewModel @@ -113,6 +114,7 @@ struct FiltersScreen: View { } } .navigationTitle("Filters") + .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Done") { @@ -120,10 +122,18 @@ struct FiltersScreen: View { dismiss() } } + + if horizontalSizeClass == .regular { + ToolbarItem(placement: .topBarLeading) { + Button("Close") { + dismiss() + } + } + } } .task { await viewModel.loadData() } - .toolbar(.hidden, for: .tabBar) + .hideTabBar() } } diff --git a/AutoCat/Screens/HistoryScreen/HistoryScreen.swift b/AutoCat/Screens/HistoryScreen/HistoryScreen.swift index 1801467..c387a20 100644 --- a/AutoCat/Screens/HistoryScreen/HistoryScreen.swift +++ b/AutoCat/Screens/HistoryScreen/HistoryScreen.swift @@ -15,91 +15,65 @@ struct HistoryScreen: View { @State var filterSheetPresented = false @State var exportSheetPresented = false - init() { - - let resolver = ServiceContainer.shared - self.viewModel = HistoryViewModel( - apiService: resolver.resolve(ApiServiceProtocol.self), - storageService: resolver.resolve(StorageServiceProtocol.self), - vehicleService: resolver.resolve(VehicleServiceProtocol.self) - ) - } - init(viewModel: HistoryViewModel) { self.viewModel = viewModel } var body: some View { - NavigationStack { - List { - ForEach(viewModel.vehicleSections) { section in - Section(header: Text(section.header)) { - ForEach(section.elements) { vehicle in - NavigationLink(value: vehicle) { - vehicleCell(vehicle) - } + List(selection: $viewModel.selectedVehicleId) { + ForEach(viewModel.vehicleSections) { section in + Section(header: Text(section.header)) { + ForEach(section.elements) { vehicle in + NavigationLink(value: vehicle.id) { + vehicleCell(vehicle) } } } } - .onAppear { - Task { await viewModel.onAppear() } - } - .hud($viewModel.hud) - .listStyle(.plain) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), - viewModel.vehiclesCount)) - .searchable(text: $viewModel.searchText, prompt: "Search plate numbers") - .searchPresentationToolbarBehavior(.avoidHidingContent) - .autocorrectionDisabled() - .textInputAutocapitalization(.never) - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button("", systemImage: "square.and.arrow.up") { - exportSheetPresented = true - } - } - ToolbarItem(placement: .primaryAction) { - Button("", systemImage: "line.horizontal.3.decrease") { - filterSheetPresented = true - } + } + .onAppear { + Task { await viewModel.onAppear() } + } + .hud($viewModel.hud) + .listStyle(.plain) + .navigationBarTitleDisplayMode(.inline) + .navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), + viewModel.vehiclesCount)) + .searchable(text: $viewModel.searchText, prompt: "Search plate numbers") + .searchPresentationToolbarBehavior(.avoidHidingContent) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button("", systemImage: "square.and.arrow.up") { + exportSheetPresented = true } } - .confirmationDialog("Filter check history", isPresented: $filterSheetPresented, titleVisibility: .visible) { - ForEach(HistoryFilter.allCases) { filter in - Button(filter.title) { - viewModel.filter = filter - viewModel.applyFilters() - } + ToolbarItem(placement: .primaryAction) { + Button("", systemImage: "line.horizontal.3.decrease") { + filterSheetPresented = true } } - .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") - } + } + .confirmationDialog("Filter check history", isPresented: $filterSheetPresented, titleVisibility: .visible) { + ForEach(HistoryFilter.allCases) { filter in + Button(filter.title) { + viewModel.filter = filter + viewModel.applyFilters() } } - .navigationDestination(for: VehicleDto.self) { vehicle in - ReportScreen( - vehicle: vehicle, - isPersistent: true, - onUpdate: viewModel.onVehicleChanged - ) + } + .confirmationDialog("Export history as", isPresented: $exportSheetPresented, titleVisibility: .visible) { + + ShareLink(item: viewModel.vehiclesArchive, preview: SharePreview(VehiclesArchive.fileName)) { + Text("CSV table") } - .navigationDestination(item: $viewModel.vehicleToOpen) { vehicle in - ReportScreen( - vehicle: vehicle, - isPersistent: true, - onUpdate: viewModel.onVehicleChanged - ) + + if let dbUrl = viewModel.dbFileURL { + ShareLink(item: dbUrl) { + Text("Database file") + } } } } diff --git a/AutoCat/Screens/HistoryScreen/HistorySplitScreen.swift b/AutoCat/Screens/HistoryScreen/HistorySplitScreen.swift new file mode 100644 index 0000000..f9ede1a --- /dev/null +++ b/AutoCat/Screens/HistoryScreen/HistorySplitScreen.swift @@ -0,0 +1,29 @@ +// +// HistorySplitScreen.swift +// AutoCat +// +// Created by Selim Mustafaev on 19.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI +import AutoCatCore + +struct HistorySplitScreen: View { + + var viewModel: HistoryViewModel + + var body: some View { + NavigationSplitView { + HistoryScreen(viewModel: viewModel) + } detail: { + if let vehicle = viewModel.selectedVehicle { + ReportScreen(vehicle: vehicle, isPersistent: true, onUpdate: viewModel.onVehicleChanged) + .id(vehicle.id) + } else { + Text("No vehicle selected") + } + } + .navigationSplitViewStyle(.balanced) + } +} diff --git a/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift b/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift index 5cff5c5..b15b198 100644 --- a/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift +++ b/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift @@ -30,7 +30,19 @@ final class HistoryViewModel: ACHudContainer { var vehiclesFiltered: [VehicleDto] = [] var vehicleSections: [DateSection] = [] - var vehicleToOpen: VehicleDto? + var selectedVehicleId: VehicleDto.ID? + + var selectedVehicle: VehicleDto? { + guard let id = selectedVehicleId else { + return nil + } + + if let vehicle = vehicles.first(where: { $0.id == id }) { + return vehicle + } else { + return nil + } + } var searchText: String = "" { didSet { @@ -130,7 +142,7 @@ final class HistoryViewModel: ACHudContainer { hud = nil if !vehicle.unrecognized { // TODO: Fix programmatic navigation - //vehicleToOpen = vehicle + //selectedVehicleId = vehicle.id } } else { showErrors(errors) diff --git a/AutoCat/Screens/MainTabScreen/MainTabScreen.swift b/AutoCat/Screens/MainTabScreen/MainTabScreen.swift index e79942d..e800153 100644 --- a/AutoCat/Screens/MainTabScreen/MainTabScreen.swift +++ b/AutoCat/Screens/MainTabScreen/MainTabScreen.swift @@ -38,7 +38,7 @@ struct MainTabScreen: View { var body: some View { TabView(selection: $selection) { - HistoryScreen(viewModel: historyViewModel) + HistorySplitScreen(viewModel: historyViewModel) .tabItem { Label("History", systemImage: "clock.arrow.circlepath") } @@ -58,7 +58,7 @@ struct MainTabScreen: View { .tag(2) #endif - SearchScreen() + SearchSplitScreen() .tabItem { Label("Search", systemImage: "magnifyingglass") } @@ -115,7 +115,3 @@ struct MainTabScreen: View { Task { await historyViewModel.checkRecord(number: number, event: event) } } } - -#Preview { - MainTabScreen() -} diff --git a/AutoCat/Screens/MapScreen/MapScreen.swift b/AutoCat/Screens/MapScreen/MapScreen.swift index eea40df..4f553a0 100644 --- a/AutoCat/Screens/MapScreen/MapScreen.swift +++ b/AutoCat/Screens/MapScreen/MapScreen.swift @@ -13,6 +13,9 @@ import AutoCatCore struct MapScreen: View { + @Environment(\.dismiss) var dismiss + @Environment(\.horizontalSizeClass) var horizontalSizeClass + @State var viewModel: MapViewModel init(mapInput: MapInput) { @@ -37,6 +40,7 @@ struct MapScreen: View { } .hud($viewModel.hud) .navigationTitle(viewModel.title) + .navigationBarTitleDisplayMode(.inline) .readSize { newValue in viewModel.mapSize = newValue } @@ -48,6 +52,15 @@ struct MapScreen: View { await viewModel.reloadMarkers() } } - .toolbar(.hidden, for: .tabBar) + .toolbar { + if horizontalSizeClass == .regular { + ToolbarItem(placement: .topBarLeading) { + Button("Close") { + dismiss() + } + } + } + } + .hideTabBar() } } diff --git a/AutoCat/Screens/ReportScreen/ReportScreen.swift b/AutoCat/Screens/ReportScreen/ReportScreen.swift index 729f6f6..5fc9eb9 100644 --- a/AutoCat/Screens/ReportScreen/ReportScreen.swift +++ b/AutoCat/Screens/ReportScreen/ReportScreen.swift @@ -37,116 +37,118 @@ struct ReportScreen: View { } var body: some View { - Form { - Section { - LabeledContent { - Text(viewModel.vehicle.brand?.name?.original ?? "") - } label: { - AsyncImage(url: URL(string: viewModel.vehicle.brand?.logo ?? "")) { phase in - phase.image? - .resizable() - .aspectRatio(contentMode: .fit) - .frame(height: 32) + NavigationStack { + Form { + Section { + LabeledContent { + Text(viewModel.vehicle.brand?.name?.original ?? "") + } label: { + AsyncImage(url: URL(string: viewModel.vehicle.brand?.logo ?? "")) { phase in + phase.image? + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 32) + } + } + } + + Section("General") { + LabeledContent("Year", value: String(viewModel.vehicle.year)) + LabeledContent("Color", value: viewModel.vehicle.color ?? "") + LabeledContent("Category", value: viewModel.vehicle.category ?? "") + LabeledContent("Steering wheel position", value: viewModel.steerignWheelPosition) + LabeledContent("Japanese", value: viewModel.isJapanese) + } + + Section("Identifiers") { + LabeledContent("Plate number", value: viewModel.plateNumber) + LabeledContent("VIN", value: viewModel.vehicle.vin1 ?? "") + LabeledContent("STS", value: viewModel.vehicle.sts ?? "") + LabeledContent("PTS", value: viewModel.vehicle.pts ?? "") + } + + Section("Engine") { + LabeledContent("Number", value: viewModel.vehicle.engine?.number ?? "") + LabeledContent("Fuel type", value: viewModel.vehicle.engine?.fuelType ?? "") + LabeledContent("Volume (cm³)", value: String(viewModel.vehicle.engine?.volume ?? 0)) + LabeledContent("Power (HP)", value: String(viewModel.vehicle.engine?.powerHp ?? 0)) + LabeledContent("Power (kw)", value: String(viewModel.vehicle.engine?.powerKw ?? 0)) + } + + Section("History") { + NavigationLink(value: Screen.events) { + LabeledContent("Events", value: String(viewModel.vehicle.events.count)) + } + + NavigationLink(value: Screen.osago) { + LabeledContent("OSAGO", value: String(viewModel.vehicle.osagoContracts.count)) + } + .disabled(viewModel.vehicle.osagoContracts.isEmpty) + + NavigationLink(value: Screen.owners) { + LabeledContent("Owners", value: String(viewModel.vehicle.ownershipPeriods.count)) + } + .disabled(viewModel.vehicle.ownershipPeriods.isEmpty) + + NavigationLink(value: Screen.photos) { + LabeledContent("Photos", value: String(viewModel.vehicle.photos.count)) + } + .disabled(viewModel.vehicle.photos.isEmpty) + + NavigationLink(value: Screen.ads) { + LabeledContent("Ads", value: String(viewModel.vehicle.ads.count)) + } + .disabled(viewModel.vehicle.ads.isEmpty) + + + NavigationLink(value: Screen.notes) { + LabeledContent("Notes", value: String(viewModel.vehicle.notes.count)) + } + } + + if viewModel.showDebugInfo { + Section("Debug info") { + makeDebugInfoCell(title: "Avtocod", model: viewModel.vehicle.debugInfo?.autocod) + makeDebugInfoCell(title: "Vin01 (VIN)", model: viewModel.vehicle.debugInfo?.vin01vin) + makeDebugInfoCell(title: "Vin01 (base)", model: viewModel.vehicle.debugInfo?.vin01base) + makeDebugInfoCell(title: "Vin01 (history)", model: viewModel.vehicle.debugInfo?.vin01history) + makeDebugInfoCell(title: "Nomerogram", model: viewModel.vehicle.debugInfo?.nomerogram) + } + } + + Section { + Button("Check GB") { + Task { await viewModel.checkGB() } } } } - - Section("General") { - LabeledContent("Year", value: String(viewModel.vehicle.year)) - LabeledContent("Color", value: viewModel.vehicle.color ?? "") - LabeledContent("Category", value: viewModel.vehicle.category ?? "") - LabeledContent("Steering wheel position", value: viewModel.steerignWheelPosition) - LabeledContent("Japanese", value: viewModel.isJapanese) + .onAppear { + Task { await viewModel.onAppear() } } - - Section("Identifiers") { - LabeledContent("Plate number", value: viewModel.plateNumber) - LabeledContent("VIN", value: viewModel.vehicle.vin1 ?? "") - LabeledContent("STS", value: viewModel.vehicle.sts ?? "") - LabeledContent("PTS", value: viewModel.vehicle.pts ?? "") - } - - Section("Engine") { - LabeledContent("Number", value: viewModel.vehicle.engine?.number ?? "") - LabeledContent("Fuel type", value: viewModel.vehicle.engine?.fuelType ?? "") - LabeledContent("Volume (cm³)", value: String(viewModel.vehicle.engine?.volume ?? 0)) - LabeledContent("Power (HP)", value: String(viewModel.vehicle.engine?.powerHp ?? 0)) - LabeledContent("Power (kw)", value: String(viewModel.vehicle.engine?.powerKw ?? 0)) - } - - Section("History") { - NavigationLink(value: Screen.events) { - LabeledContent("Events", value: String(viewModel.vehicle.events.count)) - } - - NavigationLink(value: Screen.osago) { - LabeledContent("OSAGO", value: String(viewModel.vehicle.osagoContracts.count)) - } - .disabled(viewModel.vehicle.osagoContracts.isEmpty) - - NavigationLink(value: Screen.owners) { - LabeledContent("Owners", value: String(viewModel.vehicle.ownershipPeriods.count)) - } - .disabled(viewModel.vehicle.ownershipPeriods.isEmpty) - - NavigationLink(value: Screen.photos) { - LabeledContent("Photos", value: String(viewModel.vehicle.photos.count)) - } - .disabled(viewModel.vehicle.photos.isEmpty) - - NavigationLink(value: Screen.ads) { - LabeledContent("Ads", value: String(viewModel.vehicle.ads.count)) - } - .disabled(viewModel.vehicle.ads.isEmpty) - - - NavigationLink(value: Screen.notes) { - LabeledContent("Notes", value: String(viewModel.vehicle.notes.count)) + .hud($viewModel.hud) + .toolbar { + if let link = viewModel.shareLink { + ShareLink(item: link) } } - - if viewModel.showDebugInfo { - Section("Debug info") { - makeDebugInfoCell(title: "Avtocod", model: viewModel.vehicle.debugInfo?.autocod) - makeDebugInfoCell(title: "Vin01 (VIN)", model: viewModel.vehicle.debugInfo?.vin01vin) - makeDebugInfoCell(title: "Vin01 (base)", model: viewModel.vehicle.debugInfo?.vin01base) - makeDebugInfoCell(title: "Vin01 (history)", model: viewModel.vehicle.debugInfo?.vin01history) - makeDebugInfoCell(title: "Nomerogram", model: viewModel.vehicle.debugInfo?.nomerogram) - } - } - - Section { - Button("Check GB") { - Task { await viewModel.checkGB() } + .navigationDestination(for: Screen.self) { screen in + switch screen { + case .events: + EventsScreen(vehicle: viewModel.vehicle, onUpdate: viewModel.onVehicleChanged) + case .osago: + OsagoScreen(contracts: viewModel.vehicle.osagoContracts) + case .owners: + OwnersScreen(ownerships: viewModel.vehicle.ownershipPeriods) + case .notes: + NotesScreen(vehicle: viewModel.vehicle, onUpdate: viewModel.onVehicleChanged) + case .ads: + AdsScreen(ads: viewModel.vehicle.ads) + case .photos: + GalleryScreen(photos: viewModel.vehicle.photos) } } + .hideTabBar() } - .onAppear { - Task { await viewModel.onAppear() } - } - .hud($viewModel.hud) - .toolbar { - if let link = viewModel.shareLink { - ShareLink(item: link) - } - } - .navigationDestination(for: Screen.self) { screen in - switch screen { - case .events: - EventsScreen(vehicle: viewModel.vehicle, onUpdate: viewModel.onVehicleChanged) - case .osago: - OsagoScreen(contracts: viewModel.vehicle.osagoContracts) - case .owners: - OwnersScreen(ownerships: viewModel.vehicle.ownershipPeriods) - case .notes: - NotesScreen(vehicle: viewModel.vehicle, onUpdate: viewModel.onVehicleChanged) - case .ads: - AdsScreen(ads: viewModel.vehicle.ads) - case .photos: - GalleryScreen(photos: viewModel.vehicle.photos) - } - } - .toolbar(.hidden, for: .tabBar) } @ViewBuilder diff --git a/AutoCat/Screens/SearchScreen/SearchScreen.swift b/AutoCat/Screens/SearchScreen/SearchScreen.swift index dafcef5..1493904 100644 --- a/AutoCat/Screens/SearchScreen/SearchScreen.swift +++ b/AutoCat/Screens/SearchScreen/SearchScreen.swift @@ -13,63 +13,50 @@ struct SearchScreen: View { enum Screen: Hashable { - case report(VehicleDto) case filter(Filter) case map(Filter) } @State var viewModel: SearchViewModel - init() { + init(viewModel: SearchViewModel) { - let resolver = ServiceContainer.shared - self.viewModel = SearchViewModel( - apiService: resolver.resolve(ApiServiceProtocol.self), - vehicleService: resolver.resolve(VehicleServiceProtocol.self) - ) + self.viewModel = viewModel } var body: some View { - NavigationStack { - List { - vehicles - if viewModel.hasMoreData && !viewModel.vehicleSections.isEmpty { - progressCell - } + List(selection: $viewModel.selectedVehicleId) { + vehicles + if viewModel.hasMoreData && !viewModel.vehicleSections.isEmpty { + progressCell } - .listStyle(.plain) - .hud($viewModel.hud) - .searchable(text: $viewModel.searchText, prompt: "Search plate numbers") - .searchPresentationToolbarBehavior(.avoidHidingContent) - .disableAutocorrection(true) - .autocapitalization(.none) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), - viewModel.vehiclesCount)) - .onAppear { - Task { await viewModel.onAppear() } + } + .listStyle(.plain) + .hud($viewModel.hud) + .searchable(text: $viewModel.searchText, prompt: "Search plate numbers") + .searchPresentationToolbarBehavior(.avoidHidingContent) + .disableAutocorrection(true) + .autocapitalization(.none) + .navigationBarTitleDisplayMode(.inline) + .navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), + viewModel.vehiclesCount)) + .onAppear { + Task { await viewModel.onAppear() } + } + .refreshable { + Task { await viewModel.reloadData() } + } + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + toolbarMenu } - .refreshable { - Task { await viewModel.reloadData() } - } - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - toolbarMenu - } - } - .navigationDestination(for: Screen.self) { screen in - switch screen { - case .report(let vehicle): - ReportScreen( - vehicle: vehicle, - isPersistent: false, - onUpdate: viewModel.onVehicleChanged - ) - case .filter(let filter): - FiltersScreen(filter: filter, onUpdate: viewModel.onFilterChanged) - case .map(let filter): - MapScreen(mapInput: .filter(filter)) - } + } + .navigationDestination(for: Screen.self) { screen in + switch screen { + case .filter(let filter): + FiltersScreen(filter: filter, onUpdate: viewModel.onFilterChanged) + case .map(let filter): + MapScreen(mapInput: .filter(filter)) } } } @@ -78,7 +65,7 @@ struct SearchScreen: View { ForEach(viewModel.vehicleSections) { section in Section(header: Text(section.header)) { ForEach(section.elements) { vehicle in - NavigationLink(value: Screen.report(vehicle)) { + NavigationLink(value: vehicle.id) { vehicleCell(vehicle) } } diff --git a/AutoCat/Screens/SearchScreen/SearchSplitScreen.swift b/AutoCat/Screens/SearchScreen/SearchSplitScreen.swift new file mode 100644 index 0000000..5ef572f --- /dev/null +++ b/AutoCat/Screens/SearchScreen/SearchSplitScreen.swift @@ -0,0 +1,37 @@ +// +// SearchSplitScreen.swift +// AutoCat +// +// Created by Selim Mustafaev on 19.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI +import AutoCatCore + +struct SearchSplitScreen: View { + + @State var viewModel: SearchViewModel + + init() { + let resolver = ServiceContainer.shared + self.viewModel = SearchViewModel( + apiService: resolver.resolve(ApiServiceProtocol.self), + vehicleService: resolver.resolve(VehicleServiceProtocol.self) + ) + } + + var body: some View { + NavigationSplitView { + SearchScreen(viewModel: viewModel) + } detail: { + if let vehicle = viewModel.selectedVehicle { + ReportScreen(vehicle: vehicle, isPersistent: false, onUpdate: viewModel.onVehicleChanged) + .id(vehicle.id) + } else { + Text("No vehicle selected") + } + } + .navigationSplitViewStyle(.balanced) + } +} diff --git a/AutoCat/Screens/SearchScreen/SearchViewModel.swift b/AutoCat/Screens/SearchScreen/SearchViewModel.swift index 2b3a336..4575506 100644 --- a/AutoCat/Screens/SearchScreen/SearchViewModel.swift +++ b/AutoCat/Screens/SearchScreen/SearchViewModel.swift @@ -23,6 +23,20 @@ final class SearchViewModel: ACHudContainer { var vehicles: [VehicleDto] = [] var vehicleSections: [DateSection] = [] + var selectedVehicleId: VehicleDto.ID? + + var selectedVehicle: VehicleDto? { + guard let id = selectedVehicleId else { + return nil + } + + if let vehicle = vehicles.first(where: { $0.id == id }) { + return vehicle + } else { + return nil + } + } + var filter = Filter() var pageToken: String? var hasMoreData: Bool = true diff --git a/AutoCat/SwiftUI/HideTabBarModifier.swift b/AutoCat/SwiftUI/HideTabBarModifier.swift new file mode 100644 index 0000000..9440d77 --- /dev/null +++ b/AutoCat/SwiftUI/HideTabBarModifier.swift @@ -0,0 +1,32 @@ +// +// HideTabBarModifier.swift +// AutoCat +// +// Created by Selim Mustafaev on 19.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +struct HideTabBarModifier: ViewModifier { + + @Environment(\.horizontalSizeClass) var horizontalSizeClass + + func body(content: Content) -> some View { + + if horizontalSizeClass == .compact { + content + .toolbar(.hidden, for: .tabBar) + } else { + content + } + } +} + +extension View { + + public func hideTabBar() -> some View { + + modifier(HideTabBarModifier()) + } +}