From a9bbbfa35be2fc4988b400be8d9417e13b3d5174 Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Sat, 5 Apr 2025 20:32:36 +0300 Subject: [PATCH] Remove some old code --- AutoCat.xcodeproj/project.pbxproj | 76 -- AutoCat/Cells/AudioRecordCell.swift | 81 -- AutoCat/Cells/ConfigurableCell.swift | 7 - AutoCat/Cells/EventCell.swift | 39 - AutoCat/Cells/VehicleCell.swift | 53 - AutoCat/Cells/VehicleNoteCell.swift | 24 - AutoCat/Controllers/RecordsController.swift | 368 ------ AutoCat/SceneDelegate.swift | 17 - AutoCat/SwiftUI/VehicleCellView.swift | 2 +- .../ATGMediaBrowser/ContentTransformers.swift | 237 ---- .../DismissAnimationController.swift | 289 ----- .../MediaBrowserViewController.swift | 1022 ----------------- .../ATGMediaBrowser/MediaContentView.swift | 315 ----- AutoCat/Utils/ActivityItemSource.swift | 40 - AutoCat/Utils/AudioPlayer.swift | 107 -- AutoCat/Utils/Recorder.swift | 151 --- AutoCat/Utils/RxRealmDataSource.swift | 207 ---- AutoCat/Utils/RxSectionedDataSource.swift | 87 -- 18 files changed, 1 insertion(+), 3121 deletions(-) delete mode 100644 AutoCat/Cells/AudioRecordCell.swift delete mode 100644 AutoCat/Cells/ConfigurableCell.swift delete mode 100644 AutoCat/Cells/EventCell.swift delete mode 100644 AutoCat/Cells/VehicleCell.swift delete mode 100644 AutoCat/Cells/VehicleNoteCell.swift delete mode 100644 AutoCat/Controllers/RecordsController.swift delete mode 100644 AutoCat/ThirdParty/ATGMediaBrowser/ContentTransformers.swift delete mode 100644 AutoCat/ThirdParty/ATGMediaBrowser/DismissAnimationController.swift delete mode 100644 AutoCat/ThirdParty/ATGMediaBrowser/MediaBrowserViewController.swift delete mode 100644 AutoCat/ThirdParty/ATGMediaBrowser/MediaContentView.swift delete mode 100644 AutoCat/Utils/ActivityItemSource.swift delete mode 100644 AutoCat/Utils/AudioPlayer.swift delete mode 100644 AutoCat/Utils/Recorder.swift delete mode 100644 AutoCat/Utils/RxRealmDataSource.swift delete mode 100644 AutoCat/Utils/RxSectionedDataSource.swift diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index 54bf53f..6916f47 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -20,8 +20,6 @@ 7A1022772C557EC400B84627 /* LocationPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1022762C557EC400B84627 /* LocationPickerScreen.swift */; }; 7A1022792C557ED600B84627 /* LocationPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1022782C557ED600B84627 /* LocationPickerViewModel.swift */; }; 7A10227B2C557EE900B84627 /* LocationPickerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10227A2C557EE900B84627 /* LocationPickerCoordinator.swift */; }; - 7A1090E824A394F100B4F0B2 /* AudioRecordCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */; }; - 7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */; }; 7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090EB24A4E3E100B4F0B2 /* CellProgressView.swift */; }; 7A11470123FDE7E500B424AF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11470023FDE7E500B424AF /* AppDelegate.swift */; }; 7A11470323FDE7E500B424AF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11470223FDE7E500B424AF /* SceneDelegate.swift */; }; @@ -47,14 +45,11 @@ 7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F72CE900440004B740 /* ReportViewModel.swift */; }; 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 */; }; - 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 */; }; 7A2E6FA72C42B3AD00C40DA7 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; }; 7A3399AB299063370087DF98 /* SearchControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3399AA299063370087DF98 /* SearchControllerExt.swift */; }; 7A3E12D72C7B42B700EE710D /* UserDefaults+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */; }; - 7A3E30F32C18840600567704 /* ActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3E30F22C18840600567704 /* ActivityItemSource.swift */; }; 7A3F07AB24360DC800E59687 /* Dated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3F07AA24360DC800E59687 /* Dated.swift */; }; 7A4322912CB2CC8A00085CF6 /* FiltersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322902CB2CC8A00085CF6 /* FiltersScreen.swift */; }; 7A4322932CB2CCAA00085CF6 /* FiltersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322922CB2CCAA00085CF6 /* FiltersViewModel.swift */; }; @@ -62,7 +57,6 @@ 7A45FB382C27073700618694 /* StorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A45FB372C27073700618694 /* StorageService.swift */; }; 7A4927D52CCE438600851C01 /* OptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4927D42CCE438600851C01 /* OptionalBinding.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 */; }; 7A589E0F2D6B6E8E00EF3FBE /* NumberEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */; }; 7A5911EE2D63226F00EC51BA /* SearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5911ED2D63226F00EC51BA /* SearchScreen.swift */; }; @@ -99,10 +93,6 @@ 7A64A2222C19E99E00284124 /* DebugInfoDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A64A2212C19E99E00284124 /* DebugInfoDto.swift */; }; 7A64A2242C1A07EA00284124 /* Formatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A64A2232C1A07EA00284124 /* Formatters.swift */; }; 7A64A2262C1A32C800284124 /* AudioRecordDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A64A2252C1A32C800284124 /* AudioRecordDto.swift */; }; - 7A64AE732469DFB600ABE48E /* DismissAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A64AE6F2469DFB600ABE48E /* DismissAnimationController.swift */; }; - 7A64AE742469DFB600ABE48E /* MediaContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A64AE702469DFB600ABE48E /* MediaContentView.swift */; }; - 7A64AE752469DFB600ABE48E /* MediaBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A64AE712469DFB600ABE48E /* MediaBrowserViewController.swift */; }; - 7A64AE762469DFB600ABE48E /* ContentTransformers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A64AE722469DFB600ABE48E /* ContentTransformers.swift */; }; 7A6B65B32CFB0DB500AABA6B /* NullifyDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B65B22CFB0DB500AABA6B /* NullifyDate.swift */; }; 7A6C4D9E2C56BCA600982597 /* SwiftLocation in Frameworks */ = {isa = PBXBuildFile; productRef = 7A6C4D9D2C56BCA600982597 /* SwiftLocation */; }; 7A6C65222D999325001240C2 /* AudioRecordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6C65212D999325001240C2 /* AudioRecordViewModel.swift */; }; @@ -129,7 +119,6 @@ 7A761C0B267E8FF90005F28F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A761C0A267E8FF90005F28F /* Error.swift */; }; 7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7DADAB2D99738300F52F6C /* AudioRecordView.swift */; }; 7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A809F382D66755B00CF1B3C /* Error+Canceled.swift */; }; - 7A813DC32508EE4F00CC93B9 /* EventCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A813DC22508EE4F00CC93B9 /* EventCell.swift */; }; 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */; }; 7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8AB76425A0DB8F00ECF2C1 /* BundleVersion.swift */; }; 7A912F372D381B7400002938 /* LicensePlateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A912F362D381B7400002938 /* LicensePlateView.swift */; }; @@ -144,8 +133,6 @@ 7A961C6E2C4C3C9E00CE2211 /* LinkRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A961C6D2C4C3C9E00CE2211 /* LinkRowView.swift */; }; 7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */; }; 7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; }; - 7A99406426E4BFAE002E9CB6 /* VehicleNoteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A99406326E4BFAE002E9CB6 /* VehicleNoteCell.swift */; }; - 7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */; }; 7AA514E02D0B75B3001CAC50 /* StorageService+Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA514DF2D0B75B3001CAC50 /* StorageService+Events.swift */; }; 7AA515D02D9ABCC800EB3418 /* RecordPlayerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA515CF2D9ABCC800EB3418 /* RecordPlayerService.swift */; }; 7AA515D22D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA515D12D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift */; }; @@ -158,7 +145,6 @@ 7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFADD2C4D23620050410D /* ACImageSliderModel.swift */; }; 7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1F1267E9CC800D7AB32 /* SwiftDate */; }; 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 */; }; 7AB490292D6B1217002F39C6 /* ACKeyboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */; }; 7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */; }; @@ -208,7 +194,6 @@ 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.swift */; }; 7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE24C5E251F1B4E00758E39 /* Buttons.swift */; }; 7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */; }; - 7AEFC3BE2529D3CC00BADFB2 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFC3BD2529D3CC00BADFB2 /* ConfigurableCell.swift */; }; 7AF6D2042677C03B0086EA64 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; }; 7AF6D2052677C03B0086EA64 /* AutoCatCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7AF6D2122677C12E0086EA64 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A000AA124C2EEDE001F5B00 /* Location.swift */; }; @@ -303,8 +288,6 @@ 7A1022762C557EC400B84627 /* LocationPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerScreen.swift; sourceTree = ""; }; 7A1022782C557ED600B84627 /* LocationPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerViewModel.swift; sourceTree = ""; }; 7A10227A2C557EE900B84627 /* LocationPickerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerCoordinator.swift; sourceTree = ""; }; - 7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordCell.swift; sourceTree = ""; }; - 7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = ""; }; 7A1090EB24A4E3E100B4F0B2 /* CellProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellProgressView.swift; sourceTree = ""; }; 7A1146FD23FDE7E500B424AF /* AutoCat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AutoCat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7A11470023FDE7E500B424AF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -336,8 +319,6 @@ 7A1E78F72CE900440004B740 /* ReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewModel.swift; sourceTree = ""; }; 7A1E78F92CE9005C0004B740 /* ReportCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportCoordinator.swift; sourceTree = ""; }; 7A1E78FE2CE91A740004B740 /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; - 7A27ADF2249F8B650035F39E /* RecordsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsController.swift; sourceTree = ""; }; - 7A27ADF6249FEF690035F39E /* Recorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recorder.swift; sourceTree = ""; }; 7A27ADF824A09CAD0035F39E /* CocoaError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaError.swift; sourceTree = ""; }; 7A2C96112C3B155B00AE46B5 /* NoteAlertModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteAlertModifier.swift; sourceTree = ""; }; 7A2DE69725868AC800A113FC /* VehicleAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleAd.swift; sourceTree = ""; }; @@ -346,7 +327,6 @@ 7A333813249A532400D878F1 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = ""; }; 7A3399AA299063370087DF98 /* SearchControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchControllerExt.swift; sourceTree = ""; }; 7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Settings.swift"; sourceTree = ""; }; - 7A3E30F22C18840600567704 /* ActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityItemSource.swift; sourceTree = ""; }; 7A3F07AA24360DC800E59687 /* Dated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dated.swift; sourceTree = ""; }; 7A4322902CB2CC8A00085CF6 /* FiltersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersScreen.swift; sourceTree = ""; }; 7A4322922CB2CCAA00085CF6 /* FiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewModel.swift; sourceTree = ""; }; @@ -356,7 +336,6 @@ 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 = ""; }; - 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 = ""; }; 7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberEditView.swift; sourceTree = ""; }; @@ -400,10 +379,6 @@ 7A64A2232C1A07EA00284124 /* Formatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formatters.swift; sourceTree = ""; }; 7A64A2252C1A32C800284124 /* AudioRecordDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordDto.swift; sourceTree = ""; }; 7A64AE6B2469DC6900ABE48E /* AutoCat.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AutoCat.entitlements; sourceTree = ""; }; - 7A64AE6F2469DFB600ABE48E /* DismissAnimationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DismissAnimationController.swift; sourceTree = ""; }; - 7A64AE702469DFB600ABE48E /* MediaContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaContentView.swift; sourceTree = ""; }; - 7A64AE712469DFB600ABE48E /* MediaBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaBrowserViewController.swift; sourceTree = ""; }; - 7A64AE722469DFB600ABE48E /* ContentTransformers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentTransformers.swift; sourceTree = ""; }; 7A659B5824A2B1BA0043A0F2 /* AudioRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecord.swift; sourceTree = ""; }; 7A6B65B22CFB0DB500AABA6B /* NullifyDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NullifyDate.swift; sourceTree = ""; }; 7A6C65212D999325001240C2 /* AudioRecordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordViewModel.swift; sourceTree = ""; }; @@ -427,7 +402,6 @@ 7A7DADAB2D99738300F52F6C /* AudioRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordView.swift; sourceTree = ""; }; 7A809F382D66755B00CF1B3C /* Error+Canceled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Canceled.swift"; sourceTree = ""; }; 7A813DBD2506A57100CC93B9 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/AuthenticationServices.framework; sourceTree = DEVELOPER_DIR; }; - 7A813DC22508EE4F00CC93B9 /* EventCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCell.swift; sourceTree = ""; }; 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResizeImage.swift; sourceTree = ""; }; 7A8AB76425A0DB8F00ECF2C1 /* BundleVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleVersion.swift; sourceTree = ""; }; 7A8AB76725A0DC8200ECF2C1 /* DebugInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugInfo.swift; sourceTree = ""; }; @@ -446,8 +420,6 @@ 7A96AE2E246B2BCD00297C33 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 7A96AE30246B2FE400297C33 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 7A96AE32246C095700297C33 /* Base64FS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64FS.swift; sourceTree = ""; }; - 7A99406326E4BFAE002E9CB6 /* VehicleNoteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleNoteCell.swift; sourceTree = ""; }; - 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxRealmDataSource.swift; sourceTree = ""; }; 7AA514DF2D0B75B3001CAC50 /* StorageService+Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageService+Events.swift"; sourceTree = ""; }; 7AA515CF2D9ABCC800EB3418 /* RecordPlayerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordPlayerService.swift; sourceTree = ""; }; 7AA515D12D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordPlayerServiceProtocol.swift; sourceTree = ""; }; @@ -457,7 +429,6 @@ 7AAAFADB2C4D1E130050410D /* ACImageSliderView+Modifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ACImageSliderView+Modifier.swift"; sourceTree = ""; }; 7AAAFADD2C4D23620050410D /* ACImageSliderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACImageSliderModel.swift; sourceTree = ""; }; 7AABBE3A2CF9F85600346588 /* Binding+Map.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Map.swift"; sourceTree = ""; }; - 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxSectionedDataSource.swift; sourceTree = ""; }; 7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = ""; }; 7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLocationProtocol.swift; sourceTree = ""; }; 7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACKeyboardView.swift; sourceTree = ""; }; @@ -506,7 +477,6 @@ 7AE24C5E251F1B4E00758E39 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = ""; }; 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExt.swift; sourceTree = ""; }; 7AE8424D26109F78002F6B31 /* Exportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exportable.swift; sourceTree = ""; }; - 7AEFC3BD2529D3CC00BADFB2 /* ConfigurableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = ""; }; 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 = ""; }; 7AF6D1F22677C03B0086EA64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -652,7 +622,6 @@ 7AB587352C42E3BF00FA7B66 /* Preview */, 7AFBE8C52C30812E003C491D /* SwiftUI */, 7AC355552969742800889457 /* ACUIKit */, - 7A530B7C24017FBE00CBFE6E /* Cells */, 7A11471423FDEAF800B424AF /* Controllers */, 7A1441632C297E9800E79018 /* Screens */, 7A3F07A924360D9100E59687 /* Extensions */, @@ -681,7 +650,6 @@ 7A11471923FE839000B424AF /* AuthController.swift */, 7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */, 7A11471523FDEB2A00B424AF /* MainSplitController.swift */, - 7A27ADF2249F8B650035F39E /* RecordsController.swift */, 7AC3554B29696A1C00889457 /* MainTabController.swift */, 7AC3554D29696C4500889457 /* DummyNewController.swift */, 7AC3554F29696D5A00889457 /* NewNumberController.swift */, @@ -692,7 +660,6 @@ 7A11472C23FECA3E00B424AF /* ThirdParty */ = { isa = PBXGroup; children = ( - 7A64AE6E2469DFB600ABE48E /* ATGMediaBrowser */, 7A6DD90724329144009DE740 /* CenterTextLayer.swift */, ); path = ThirdParty; @@ -701,11 +668,6 @@ 7A11474223FF06B600B424AF /* Utils */ = { isa = PBXGroup; children = ( - 7A27ADF6249FEF690035F39E /* Recorder.swift */, - 7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */, - 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */, - 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */, - 7A3E30F22C18840600567704 /* ActivityItemSource.swift */, 7A14416D2C297F7C00E79018 /* Coordinator.swift */, 7AB4E4652D58A16C0006D052 /* GenericError.swift */, ); @@ -844,18 +806,6 @@ path = Services; sourceTree = ""; }; - 7A530B7C24017FBE00CBFE6E /* Cells */ = { - isa = PBXGroup; - children = ( - 7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */, - 7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */, - 7A813DC22508EE4F00CC93B9 /* EventCell.swift */, - 7AEFC3BD2529D3CC00BADFB2 /* ConfigurableCell.swift */, - 7A99406326E4BFAE002E9CB6 /* VehicleNoteCell.swift */, - ); - path = Cells; - sourceTree = ""; - }; 7A5911EC2D63225500EC51BA /* SearchScreen */ = { isa = PBXGroup; children = ( @@ -934,17 +884,6 @@ path = Protocols; sourceTree = ""; }; - 7A64AE6E2469DFB600ABE48E /* ATGMediaBrowser */ = { - isa = PBXGroup; - children = ( - 7A64AE6F2469DFB600ABE48E /* DismissAnimationController.swift */, - 7A64AE702469DFB600ABE48E /* MediaContentView.swift */, - 7A64AE712469DFB600ABE48E /* MediaBrowserViewController.swift */, - 7A64AE722469DFB600ABE48E /* ContentTransformers.swift */, - ); - path = ATGMediaBrowser; - sourceTree = ""; - }; 7A6C65202D99930C001240C2 /* AudioRecordView */ = { isa = PBXGroup; children = ( @@ -1468,7 +1407,6 @@ buildActionMask = 2147483647; files = ( 7A961C6C2C4C3C8600CE2211 /* TextRowView.swift in Sources */, - 7AEFC3BE2529D3CC00BADFB2 /* ConfigurableCell.swift in Sources */, 7A1022772C557EC400B84627 /* LocationPickerScreen.swift in Sources */, 7A4322932CB2CCAA00085CF6 /* FiltersViewModel.swift in Sources */, 7A5D7E0C2C71EB25002C17E7 /* ToggleRowView.swift in Sources */, @@ -1479,13 +1417,10 @@ 7A3399AB299063370087DF98 /* SearchControllerExt.swift in Sources */, 7A14416E2C297F7C00E79018 /* Coordinator.swift in Sources */, 7A6DD90824329144009DE740 /* CenterTextLayer.swift in Sources */, - 7A99406426E4BFAE002E9CB6 /* VehicleNoteCell.swift in Sources */, 7AC3554C29696A1C00889457 /* MainTabController.swift in Sources */, - 7A813DC32508EE4F00CC93B9 /* EventCell.swift in Sources */, 7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */, 7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */, 7A9519842D80B72B00E69883 /* RecordsCoordinator.swift in Sources */, - 7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */, 7AAAFADA2C4D1AFE0050410D /* Zoomable.swift in Sources */, 7AC8B2762D6A01C700190706 /* UISearchTextField+Dumb.swift in Sources */, 7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */, @@ -1505,8 +1440,6 @@ 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 */, 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */, 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */, 7AC3554E29696C4500889457 /* DummyNewController.swift in Sources */, @@ -1517,7 +1450,6 @@ 7AFBE8CA2C3081C7003C491D /* ACProgressHud+Modifiers.swift in Sources */, 7A71EF572D0A26B200943129 /* EventModel.swift in Sources */, 7AABBE3B2CF9F85600346588 /* Binding+Map.swift in Sources */, - 7A27ADF7249FEF690035F39E /* Recorder.swift in Sources */, 7A1E78F62CE900330004B740 /* ReportScreen.swift in Sources */, 7A10226C2C551EC500B84627 /* LocationEditScreen.swift in Sources */, 7A7158072C44085600852088 /* OsagoScreen.swift in Sources */, @@ -1526,12 +1458,9 @@ 7A912F372D381B7400002938 /* LicensePlateView.swift in Sources */, 7A3F07AB24360DC800E59687 /* Dated.swift in Sources */, 7AC76D7B270083AE0084DB27 /* TextView.swift in Sources */, - 7A1090E824A394F100B4F0B2 /* AudioRecordCell.swift in Sources */, 7A2C96122C3B155B00AE46B5 /* NoteAlertModifier.swift in Sources */, - 7A64AE762469DFB600ABE48E /* ContentTransformers.swift in Sources */, 7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */, 7A11471A23FE839000B424AF /* AuthController.swift in Sources */, - 7A64AE742469DFB600ABE48E /* MediaContentView.swift in Sources */, 7A5911F22D63268400EC51BA /* SearchCoordinator.swift in Sources */, 7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */, 7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */, @@ -1539,20 +1468,17 @@ 7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */, 7A10227B2C557EE900B84627 /* LocationPickerCoordinator.swift in Sources */, 7AB490292D6B1217002F39C6 /* ACKeyboardView.swift in Sources */, - 7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */, 7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */, 7A6DD903242BF4A5009DE740 /* PlateView.swift in Sources */, 7A1022722C554A1300B84627 /* CustomHostingController.swift in Sources */, 7ADF6C9F251201D200F237B2 /* GlobalEventsController.swift in Sources */, 7A1022792C557ED600B84627 /* LocationPickerViewModel.swift in Sources */, 7A11470323FDE7E500B424AF /* SceneDelegate.swift in Sources */, - 7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */, 7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */, 7A10226E2C551EE000B84627 /* LocationEditViewModel.swift in Sources */, 7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */, 7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */, 7A14416C2C297F2100E79018 /* NotesCoordinator.swift in Sources */, - 7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */, 7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */, 7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */, 7AC3555229696E3F00889457 /* UIView+layout.swift in Sources */, @@ -1579,10 +1505,8 @@ 7AB9FE222D08C2A5005DE374 /* EventsScreen.swift in Sources */, 7ADF6C93250B954900F237B2 /* Navigation.swift in Sources */, 7A5911F02D63266B00EC51BA /* SearchViewModel.swift in Sources */, - 7A64AE752469DFB600ABE48E /* MediaBrowserViewController.swift in Sources */, 7ABD1B4B2D044A7D00B43213 /* GalleryCoordinator.swift in Sources */, 7A589E0F2D6B6E8E00EF3FBE /* NumberEditView.swift in Sources */, - 7A64AE732469DFB600ABE48E /* DismissAnimationController.swift in Sources */, 7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */, 7A1022702C551EFD00B84627 /* LocationEditCoordinator.swift in Sources */, 7A7158042C43EAA200852088 /* OwnersCoordinator.swift in Sources */, diff --git a/AutoCat/Cells/AudioRecordCell.swift b/AutoCat/Cells/AudioRecordCell.swift deleted file mode 100644 index 5baf6f9..0000000 --- a/AutoCat/Cells/AudioRecordCell.swift +++ /dev/null @@ -1,81 +0,0 @@ -import UIKit -import PKHUD -import AutoCatCore - -class AudioRecordCell: UITableViewCell, ConfigurableCell { - - @IBOutlet weak var playButton: UIButton! - @IBOutlet weak var duration: UILabel! - @IBOutlet weak var number: UILabel! - @IBOutlet weak var date: UILabel! - @IBOutlet weak var progressView: CellProgressView! - - let dateFormatter = DateFormatter() - let componentsFormatter = DateComponentsFormatter() - - var record: AudioRecordDto? - - override func awakeFromNib() { - super.awakeFromNib() - - self.dateFormatter.dateStyle = .short - self.dateFormatter.timeStyle = .short - - self.componentsFormatter.unitsStyle = .abbreviated - self.componentsFormatter.allowedUnits = [.minute, .second] - self.componentsFormatter.zeroFormattingBehavior = .pad - - DispatchQueue.main.async { - self.progressView.progress = 0 - } - } - - override func prepareForReuse() { - super.prepareForReuse() - self.record = nil - self.progressView.progress = 0 - } - - func configure(with record: AudioRecordDto) { - self.record = record - self.date.text = self.dateFormatter.string(from: Date(timeIntervalSince1970: record.getAddedDate())) - self.number.text = record.number ?? "Unrecognized" - self.duration.text = self.componentsFormatter.string(from: record.duration) - - // TODO: Fix player -// AudioPlayer.shared -// .stateObservable() -// .filter { _ in AudioPlayer.shared.getUrl()?.lastPathComponent == record.path } -// .subscribe(onNext: { state in -// let imgName = state == .playing ? "pause.fill" : "play.fill" -// self.playButton.setImage(UIImage(systemName: imgName), for: .normal) -// -// if state == .stopped { -// self.progressView.progress = 0 -// } -// }, onDisposed: { -// self.playButton.setImage(UIImage(systemName: "play.fill"), for: .normal) -// }) -// -// self.progressDisposable = AudioPlayer.shared -// .progressObservable() -// .filter { _ in AudioPlayer.shared.getUrl()?.lastPathComponent == record.path } -// .subscribe(onNext: { progress in -// self.progressView.progress = progress -// }, onDisposed: { -// self.progressView.progress = 0 -// }) - } - - @IBAction func onPlay(_ sender: UIButton) { - if let record = self.record { - do { - let url = try FileManager.default.url(for: record.path, in: "recordings") - try AudioPlayer.shared.play(url: url) - } catch { - print("Error playing audio record: \(error.localizedDescription)") - HUD.show(error: error) - } - } - } -} diff --git a/AutoCat/Cells/ConfigurableCell.swift b/AutoCat/Cells/ConfigurableCell.swift deleted file mode 100644 index 413a68d..0000000 --- a/AutoCat/Cells/ConfigurableCell.swift +++ /dev/null @@ -1,7 +0,0 @@ -import UIKit - -@MainActor -protocol ConfigurableCell { - associatedtype Item - func configure(with item: Item) -} diff --git a/AutoCat/Cells/EventCell.swift b/AutoCat/Cells/EventCell.swift deleted file mode 100644 index 9c19e8f..0000000 --- a/AutoCat/Cells/EventCell.swift +++ /dev/null @@ -1,39 +0,0 @@ -import UIKit -import AutoCatCore - -class EventCell: UITableViewCell { - @IBOutlet weak var address: UILabel! - @IBOutlet weak var date: UILabel! - @IBOutlet weak var userImageView: UIImageView! - - @Service var settingsService: SettingsServiceProtocol - - let dateFormatter = DateFormatter() - - override func awakeFromNib() { - super.awakeFromNib() - - self.dateFormatter.dateStyle = .short - self.dateFormatter.timeStyle = .short - } - - func configure(with event: VehicleEventDto) { - if let addressString = event.address { - self.address.text = addressString - } else { - self.address.text = "Lat: \(event.latitude), Lon: \(event.longitude)" - } - - let date = Date(timeIntervalSince1970: event.date) - self.date.text = self.dateFormatter.string(from: date) - - if let addedBy = event.addedBy { - let isMe = addedBy == settingsService.user.email - userImageView.image = UIImage(systemName: isMe ? "person.fill" : "person") - userImageView.tintColor = isMe ? self.tintColor : .secondaryLabel - userImageView.isHidden = false - } else { - userImageView.isHidden = true - } - } -} diff --git a/AutoCat/Cells/VehicleCell.swift b/AutoCat/Cells/VehicleCell.swift deleted file mode 100644 index 634f6d3..0000000 --- a/AutoCat/Cells/VehicleCell.swift +++ /dev/null @@ -1,53 +0,0 @@ -import UIKit -import AutoCatCore - -class VehicleCell: UITableViewCell, ConfigurableCell { - - @IBOutlet weak var name: UILabel! - @IBOutlet weak var plate: PlateView! - @IBOutlet weak var addedDate: UILabel! - @IBOutlet weak var updatedDate: UILabel! - @IBOutlet weak var bubbleImage: UIImageView! - @IBOutlet weak var notesCount: UILabel! - @IBOutlet weak var syncImage: UIImageView! - - let formatter = DateFormatter() - - override func awakeFromNib() { - super.awakeFromNib() - - formatter.dateStyle = .short - formatter.timeStyle = .short - } - - func configure(with vehicle: VehicleDto) { - self.name.text = vehicle.brand?.name?.original ?? "" - self.plate.number = PlateNumber(vehicle.getNumber()) - self.plate.fontSize = 40 - - if vehicle.unrecognized { - self.plate.foreground = .systemRed - } else if vehicle.outdated { - self.plate.foreground = .systemGray3 - } else { - self.plate.foreground = nil - } - - if vehicle.updatedDate - vehicle.addedDate > 60 { - self.addedDate.text = formatter.string(from: Date(timeIntervalSince1970: vehicle.addedDate)) - self.updatedDate.text = formatter.string(from: Date(timeIntervalSince1970: vehicle.updatedDate)) - self.addedDate.isHidden = false - } else { - self.addedDate.isHidden = true - self.updatedDate.text = formatter.string(from: Date(timeIntervalSince1970: vehicle.updatedDate)) - } - - self.syncImage.isHidden = vehicle.synchronized || vehicle.unrecognized - - self.bubbleImage.isHidden = vehicle.notes.isEmpty - self.notesCount.isHidden = vehicle.notes.isEmpty - if !vehicle.notes.isEmpty { - self.notesCount.text = String(vehicle.notes.count) - } - } -} diff --git a/AutoCat/Cells/VehicleNoteCell.swift b/AutoCat/Cells/VehicleNoteCell.swift deleted file mode 100644 index 50e318c..0000000 --- a/AutoCat/Cells/VehicleNoteCell.swift +++ /dev/null @@ -1,24 +0,0 @@ -import UIKit -import AutoCatCore - -class VehicleNoteCell: UITableViewCell { - - @IBOutlet weak var noteText: UILabel! - @IBOutlet weak var date: UILabel! - - private var dateFormatter = DateFormatter() - - override func awakeFromNib() { - super.awakeFromNib() - - DispatchQueue.main.async { - self.dateFormatter.dateStyle = .medium - self.dateFormatter.timeStyle = .medium - } - } - - func configure(with note: VehicleNoteDto) { - self.noteText.text = note.text - self.date.text = self.dateFormatter.string(from: Date(timeIntervalSince1970: note.date)) - } -} diff --git a/AutoCat/Controllers/RecordsController.swift b/AutoCat/Controllers/RecordsController.swift deleted file mode 100644 index 4494532..0000000 --- a/AutoCat/Controllers/RecordsController.swift +++ /dev/null @@ -1,368 +0,0 @@ -import UIKit -import AVFoundation -import RealmSwift -import Intents -import CoreSpotlight -import MobileCoreServices -import os.log -import PKHUD -import AutoCatCore - -class RecordsController: UIViewController, UITableViewDelegate { - - @IBOutlet weak var tableView: UITableView! - - @Service var settingsService: SettingsServiceProtocol - - var recorder: Recorder? - var addButton: UIBarButtonItem! - var audioSessionObserver: NSObjectProtocol? - var recordsDataSource: RealmSectionedDataSource! - - let validLetters = Constants.pnLettersMap.keys.map(String.init) - - override func viewDidLoad() { - super.viewDidLoad() - - guard let realm = try? Realm() else { return } - - self.addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(onAddVoiceRecord(_:))) - self.navigationItem.rightBarButtonItem = self.addButton - - self.recorder = Recorder() - - DispatchQueue.main.async { - self.recordsDataSource = RealmSectionedDataSource(table: self.tableView, data: realm.objects(AudioRecord.self) - .sorted(byKeyPath: "addedDate", ascending: false)) - } - self.tableView.delegate = self - } - - func donateUserActivity() { - let activityId = "pro.aliencat.autocat.addVoiceRecord" - let activity = NSUserActivity(activityType: activityId) - activity.persistentIdentifier = activityId - activity.isEligibleForSearch = true - activity.isEligibleForPrediction = true - activity.title = NSLocalizedString("Add new audio record", comment: "") - activity.suggestedInvocationPhrase = "Запиши номер" - - let attributes = CSSearchableItemAttributeSet() - attributes.contentType = kUTTypeItem as String - attributes.contentDescription = NSLocalizedString("Add new plate number via audio recording", comment: "") - activity.contentAttributeSet = attributes - - self.userActivity = activity - activity.becomeCurrent() - } - - func stopRecording() { - self.recorder?.stopRecording() - } - - // MARK: - Bar button handlers - - @objc func onAddVoiceRecord(_ sender: UIBarButtonItem) { - guard let recorder = self.recorder else { - HUD.flash(.labeledError(title: nil, subtitle: "Audio recorder is not available")) - return - } - - self.addButton.isEnabled = false - - var alert: UIAlertController? - var url: URL! - - Task { @MainActor in - do { - let event = try await RxLocationManager.requestCurrentLocation() - try await recorder.requestPermissions() - - await makeStartSoundIfNeeded() - -#if targetEnvironment(macCatalyst) || targetEnvironment(simulator) - DispatchQueue.main.async { - alert = self.showRecordingAlert() - } -#else - if let observer = self.audioSessionObserver { - NotificationCenter.default.removeObserver(observer, name: AVAudioSession.routeChangeNotification, object: nil) - } - self.audioSessionObserver = NotificationCenter.default.addObserver(forName: AVAudioSession.routeChangeNotification, object: nil, queue: .main) { notification in - guard let dict = notification.userInfo as? [String: Any], - let reasonInt = dict["AVAudioSessionRouteChangeReasonKey"] as? NSNumber, - let reason = AVAudioSession.RouteChangeReason(rawValue: reasonInt.uintValue), - let session = notification.object as? AVAudioSession else { return } - - if reason == .categoryChange && session.category == .playAndRecord { - DispatchQueue.main.async { - alert = self.showRecordingAlert() - } - } - } -#endif - let date = Date() - let fileName = "recording-\(date.timeIntervalSince1970).m4a" - url = try FileManager.default.url(for: fileName, in: "recordings") - - let text = try await recorder.startRecording(to: url) - - let asset = AVURLAsset(url: url) - let duration = TimeInterval(CMTimeGetSeconds(asset.duration)) - let record = AudioRecordDto(path: url.lastPathComponent, - number: self.getPlateNumber(from: text), - raw: text, - duration: duration, - event: event) - - let realm = try await Realm() - try realm.write { - realm.add(AudioRecord(dto: record)) - } - alert?.dismiss(animated: true) - self.addButton.isEnabled = true - } catch { - if let alert = alert { - alert.dismiss(animated: true) { - HUD.show(error: error) - } - } else { - HUD.show(error: error) - } - self.addButton.isEnabled = true - } - } - } - - func showRecordingAlert() -> UIAlertController { - let alert = UIAlertController(title: NSLocalizedString("Recording...", comment: ""), message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: { _ in self.recorder?.cancelRecording() })) - alert.addAction(UIAlertAction(title: NSLocalizedString("Done", comment: ""), style: .default, handler: { _ in self.recorder?.stopRecording() })) - self.present(alert, animated: true) - return alert - } - - // MARK: - Processing - - func getPlateNumber(from recognizedText: String) -> String? { - let trimmed = recognizedText - .replacingOccurrences(of: " ", with: "") - .uppercased() - .replacingOccurrences(of: "Ф", with: "В") - .replacingOccurrences(of: "НОЛЬ", with: "0") - .replacingOccurrences(of: "Э", with: "") - - var result = "" - if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) { - result = String(trimmed[range]) - } else if let range = trimmed.range(of: #"\S\S\S\d\d\d\d\d\d?"#, options: .regularExpression), settingsService.recognizeAlternativeOrder { - let n = String(trimmed[range]) - result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + n.substring(from: 6) - } else if let range = trimmed.range(of: #"\S\d\d\d\S\S"#, options: .regularExpression), settingsService.recognizeShortenedNumbers { - result = String(trimmed[range]) + settingsService.defaultRegion - } else if let range = trimmed.range(of: #"\S\S\S\d\d\d"#, options: .regularExpression), settingsService.recognizeAlternativeOrder && settingsService.recognizeShortenedNumbers { - let n = String(trimmed[range]) - result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + settingsService.defaultRegion - } - - if !result.isEmpty && valid(number: result) { - return result - } else { - return nil - } - } - - func valid(number: String) -> Bool { - guard number.count >= 8 else { return false } - - let first = String(number.prefix(1)) - let second = number.substring(with: 4..<5) - let third = number.substring(with: 5..<6) - let digits = Int(number.substring(with: 1..<4)) - let region = Int(number.substring(from: 6)) - - return self.validLetters.contains(first) - && self.validLetters.contains(second) - && self.validLetters.contains(third) - && digits != nil - && region != nil - && region! < 1000 - } - - func makeStartSoundIfNeeded() async { - guard settingsService.recordBeep else { - return - } - - return await withCheckedContinuation { continuation in - var soundId = SystemSoundID() - let url = URL(fileURLWithPath: "/System/Library/Audio/UISounds/short_double_high.caf") - AudioServicesCreateSystemSoundID(url as CFURL, &soundId) - AudioServicesPlaySystemSoundWithCompletion(soundId) { - continuation.resume() - } - } - } - - // MARK: - UITableViewDelegate - - func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let record = self.recordsDataSource.item(at: indexPath) - - return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in - let check = UIAction(title: NSLocalizedString("Check", comment: ""), image: UIImage(systemName: "eye")) { action in - if let number = record.number { - self.check(number: number, event: record.event) - } - } - - let delete = UIAction(title: NSLocalizedString("Delete", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { action in - self.delete(record: record) - } - - let share = UIAction(title: NSLocalizedString("Share", comment: ""), image: UIImage(systemName: "square.and.arrow.up")) { action in - self.share(record: record) - } - - let showText = UIAction(title: NSLocalizedString("Show recognized text", comment: ""), image: UIImage(systemName: "textformat")) { action in - self.showAlert(title: NSLocalizedString("Recognized text", comment: ""), message: record.rawText) - } - - let showMap = UIAction(title: NSLocalizedString("Show on map", comment: ""), image: UIImage(systemName: "mappin.and.ellipse")) { action in - self.showOnMap(record) - } - - let edit = UIAction(title: NSLocalizedString("Edit plate number", comment: ""), image: UIImage(systemName: "pencil")) { action in - self.edit(record: record) - } - - var actions = [edit, showText, showMap, share, delete] - if record.number != nil { - actions.insert(check, at: 0) - } - - return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: actions) - } - } - - func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - guard let cell = tableView.cellForRow(at: indexPath) else { return nil } - - let record = self.recordsDataSource.item(at: indexPath) - - let check = UIContextualAction(style: .normal, title: NSLocalizedString("Check", comment: "")) { action, view, completion in - if let number = record.number { - self.check(number: number, event: record.event) - } - completion(true) - } - check.backgroundColor = .systemGray2 - check.image = UIImage(systemName: "eye") - - let action = UIContextualAction(style: .normal, title: NSLocalizedString("Action", comment: "")) { action, view, completion in - self.moreActions(for: record, cell: cell) - completion(true) - } - action.backgroundColor = .systemGray2 - action.image = UIImage(systemName: "ellipsis" /*"square.and.arrow.up"*/) - - let delete = UIContextualAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { action, view, completion in - self.delete(record: record) - completion(true) - } - delete.image = UIImage(systemName: "trash") - - let actions = record.number == nil ? [delete, action] : [delete, check, action] - let configuration = UISwipeActionsConfiguration(actions: actions) - configuration.performsFirstActionWithFullSwipe = false - return configuration - } - - func moreActions(for record: AudioRecordDto, cell: UITableViewCell) { - let sheet = UIAlertController(title: NSLocalizedString("More actions", comment: ""), message: nil, preferredStyle: .actionSheet) - let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) } - let share = UIAlertAction(title: NSLocalizedString("Share", comment: ""), style: .default) { _ in - self.share(record: record) - } - let showText = UIAlertAction(title: NSLocalizedString("Show recognized text", comment: ""), style: .default) { action in - self.showAlert(title: NSLocalizedString("Recognized text", comment: ""), message: record.rawText) - } - let editNumber = UIAlertAction(title: NSLocalizedString("Edit plate number", comment: ""), style: .default) { action in - self.edit(record: record) - } - let showOnMap = UIAlertAction(title: NSLocalizedString("Show on map", comment: ""), style: .default) { action in - self.showOnMap(record) - } - - sheet.addAction(editNumber) - sheet.addAction(showText) - if record.event != nil { - sheet.addAction(showOnMap) - } - sheet.addAction(share) - sheet.addAction(cancel) - sheet.popoverPresentationController?.sourceView = cell - sheet.popoverPresentationController?.sourceRect = cell.frame - self.present(sheet, animated: true, completion: nil) - } - - func check(number: String, event: VehicleEventDto?) { - // TODO: Implement checking number without quick action - self.tabBarController?.selectedIndex = 0 - } - - func edit(record: AudioRecordDto) { - let alert = UIAlertController(title: NSLocalizedString("Edit plate number", comment: ""), message: nil, preferredStyle: .alert) - let done = UIAlertAction(title: NSLocalizedString("Done", comment: ""), style: .default) { action in - guard let tf = alert.textFields?.first else { return } - if let realm = try? Realm(), let realmRecord = realm.object(ofType: AudioRecord.self, forPrimaryKey: record.path) { - try? realm.write { - realmRecord.number = tf.text?.uppercased() - } - } - } - alert.addAction(done) - alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: { action in - alert.dismiss(animated: true) - })) - alert.addTextField { tf in - tf.text = record.number ?? record.rawText.replacingOccurrences(of: " ", with: "") - NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: tf, queue: nil) { _ in - DispatchQueue.main.async { - done.isEnabled = self.valid(number: tf.text?.uppercased() ?? "") - } - } - } - self.present(alert, animated: true) - } - - func delete(record: AudioRecordDto) { - do { - if let realm = try? Realm(), let realmRecord = realm.object(ofType: AudioRecord.self, forPrimaryKey: record.path) { - try realm.write { - realm.delete(realmRecord) - } - } - } catch { - print(error) - } - } - - func share(record: AudioRecordDto) { - do { - let url = try FileManager.default.url(for: record.path, in: "recordings") - let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil) - self.present(controller, animated: true) - } catch { - print("Error sharing audio record: \(error.localizedDescription)") - HUD.show(error: error) - } - } - - func showOnMap(_ record: AudioRecordDto) { - let controller = ShowEventController() - controller.event = record.event - controller.hidesBottomBarWhenPushed = true - self.navigationController?.pushViewController(controller, animated: true) - } -} diff --git a/AutoCat/SceneDelegate.swift b/AutoCat/SceneDelegate.swift index ba7afb8..63d3935 100644 --- a/AutoCat/SceneDelegate.swift +++ b/AutoCat/SceneDelegate.swift @@ -136,23 +136,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. - - 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 { - print(type(of: child)) - if let record = child as? RecordsController { - record.stopRecording() - } - } - } - } - - do { - try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) - } catch { - print("sceneDidEnterBackground failed to deactivate audio session: \(error.localizedDescription)") - } } func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { diff --git a/AutoCat/SwiftUI/VehicleCellView.swift b/AutoCat/SwiftUI/VehicleCellView.swift index 3db23d8..b4cfabb 100644 --- a/AutoCat/SwiftUI/VehicleCellView.swift +++ b/AutoCat/SwiftUI/VehicleCellView.swift @@ -71,5 +71,5 @@ struct VehicleCellView: View { } #Preview { - VehicleCell() + VehicleCellView(vehicle: .preview) } diff --git a/AutoCat/ThirdParty/ATGMediaBrowser/ContentTransformers.swift b/AutoCat/ThirdParty/ATGMediaBrowser/ContentTransformers.swift deleted file mode 100644 index a76cfc7..0000000 --- a/AutoCat/ThirdParty/ATGMediaBrowser/ContentTransformers.swift +++ /dev/null @@ -1,237 +0,0 @@ -// -// ContentTransformers.swift -// ATGMediaBrowser -// -// Created by Suraj Thomas K on 7/17/18. -// Copyright © 2018 Al Tayer Group LLC. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software -// and associated documentation files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import UIKit - -/** - Content transformer used for transition between media item views. - - - parameter contentView: The content view on which transform corresponding to the position has to be applied. - - parameter position: Current position for the passed content view. - - - note: - The trasnform to be applied on the contentView has to be dependent on the position passed. - The position value can be -ve, 0.0 or positive. - - Try to visualize content views at -1.0[previous]=>0.0[current]=>1.0[next]. - - 1. When position is -1.0, the content view should be at the place meant for previous view. - - 2. When the position is 0.0, the transform applied on the content view should make it visible full screen at origin. - - 3. When position is 1.0, the content view should be at the place meant for next view. - - Be mindful of the drawing order, when designing new transitions. - */ -public typealias ContentTransformer = (_ contentView: UIView, _ position: CGFloat) -> Void - -// MARK: - Default Transitions - -/// An enumeration to hold default content transformers -@MainActor -public enum DefaultContentTransformers { - - /** - Horizontal move-in-out content transformer. - - - Requires: - * GestureDirection: Horizontal - */ - public static let horizontalMoveInOut: ContentTransformer = { contentView, position in - - let widthIncludingGap = contentView.bounds.size.width + MediaContentView.interItemSpacing - contentView.transform = CGAffineTransform(translationX: widthIncludingGap * position, y: 0.0) - } - - /** - Vertical move-in-out content transformer. - - - Requires: - * GestureDirection: Vertical - */ - public static let verticalMoveInOut: ContentTransformer = { contentView, position in - - let heightIncludingGap = contentView.bounds.size.height + MediaContentView.interItemSpacing - contentView.transform = CGAffineTransform(translationX: 0.0, y: heightIncludingGap * position) - } - - /** - Horizontal slide-out content transformer. - - - Requires: - * GestureDirection: Horizontal - * DrawOrder: PreviousToNext - */ - public static let horizontalSlideOut: ContentTransformer = { contentView, position in - - var scale: CGFloat = 1.0 - if position < -0.5 { - scale = 0.9 - } else if -0.5...0.0 ~= Double(position) { - scale = 1.0 + (position * 0.2) - } - var transform = CGAffineTransform(scaleX: scale, y: scale) - - let widthIncludingGap = contentView.bounds.size.width + MediaContentView.interItemSpacing - let x = position >= 0.0 ? widthIncludingGap * position : 0.0 - transform = transform.translatedBy(x: x, y: 0.0) - - contentView.transform = transform - - let margin: CGFloat = 0.0000001 - contentView.isHidden = ((1.0-margin)...(1.0+margin) ~= abs(position)) - } - - /** - Vertical slide-out content transformer. - - - Requires: - * GestureDirection: Vertical - * DrawOrder: PreviousToNext - */ - public static let verticalSlideOut: ContentTransformer = { contentView, position in - - var scale: CGFloat = 1.0 - if position < -0.5 { - scale = 0.9 - } else if -0.5...0.0 ~= Double(position) { - scale = 1.0 + (position * 0.2) - } - var transform = CGAffineTransform(scaleX: scale, y: scale) - - let heightIncludingGap = contentView.bounds.size.height + MediaContentView.interItemSpacing - let y = position >= 0.0 ? heightIncludingGap * position : 0.0 - transform = transform.translatedBy(x: 0.0, y: y) - - contentView.transform = transform - - let margin: CGFloat = 0.0000001 - contentView.isHidden = ((1.0-margin)...(1.0+margin) ~= abs(position)) - } - - /** - Horizontal slide-in content transformer. - - - Requires: - * GestureDirection: Horizontal - * DrawOrder: NextToPrevious - */ - public static let horizontalSlideIn: ContentTransformer = { contentView, position in - - var scale: CGFloat = 1.0 - if position > 0.5 { - scale = 0.9 - } else if 0.0...0.5 ~= Double(position) { - scale = 1.0 - (position * 0.2) - } - var transform = CGAffineTransform(scaleX: scale, y: scale) - - let widthIncludingGap = contentView.bounds.size.width + MediaContentView.interItemSpacing - let x = position > 0.0 ? 0.0 : widthIncludingGap * position - transform = transform.translatedBy(x: x, y: 0.0) - - contentView.transform = transform - - let margin: CGFloat = 0.0000001 - contentView.isHidden = ((1.0-margin)...(1.0+margin) ~= abs(position)) - } - - /** - Vertical slide-in content transformer. - - - Requires: - * GestureDirection: Vertical - * DrawOrder: NextToPrevious - */ - public static let verticalSlideIn: ContentTransformer = { contentView, position in - - var scale: CGFloat = 1.0 - if position > 0.5 { - scale = 0.9 - } else if 0.0...0.5 ~= Double(position) { - scale = 1.0 - (position * 0.2) - } - var transform = CGAffineTransform(scaleX: scale, y: scale) - - let heightIncludingGap = contentView.bounds.size.height + MediaContentView.interItemSpacing - let y = position > 0.0 ? 0.0 : heightIncludingGap * position - transform = transform.translatedBy(x: 0.0, y: y) - - contentView.transform = transform - - let margin: CGFloat = 0.0000001 - contentView.isHidden = ((1.0-margin)...(1.0+margin) ~= abs(position)) - } - - /** - Horizontal zoom-in-out content transformer. - - - Requires: - * GestureDirection: Horizontal - */ - public static let horizontalZoomInOut: ContentTransformer = { contentView, position in - - let minScale: CGFloat = 0.5 - // Scale factor is used to reduce the scale animation speed. - let scaleFactor: CGFloat = 0.5 - var scale: CGFloat = CGFloat.maximum(minScale, 1.0 - abs(position * scaleFactor)) - - // Actual gap will be scaleFactor * 0.5 times of contentView.bounds.size.width. - let actualGap = contentView.bounds.size.width * scaleFactor * 0.5 - let gapCorrector = MediaContentView.interItemSpacing - actualGap - - let widthIncludingGap = contentView.bounds.size.width + gapCorrector - let translation = (widthIncludingGap * position)/scale - - var transform = CGAffineTransform(scaleX: scale, y: scale) - transform = transform.translatedBy(x: translation, y: 0.0) - - contentView.transform = transform - } - - /** - Vertical zoom-in-out content transformer. - - - Requires: - * GestureDirection: Vertical - */ - public static let verticalZoomInOut: ContentTransformer = { contentView, position in - - let minScale: CGFloat = 0.5 - // Scale factor is used to reduce the scale animation speed. - let scaleFactor: CGFloat = 0.5 - let scale: CGFloat = CGFloat.maximum(minScale, 1.0 - abs(position * scaleFactor)) - - // Actual gap will be scaleFactor * 0.5 times of contentView.bounds.size.height. - let actualGap = contentView.bounds.size.height * scaleFactor * 0.5 - let gapCorrector = MediaContentView.interItemSpacing - actualGap - - let heightIncludingGap = contentView.bounds.size.height + gapCorrector - let translation = (heightIncludingGap * position)/scale - - var transform = CGAffineTransform(scaleX: scale, y: scale) - transform = transform.translatedBy(x: 0.0, y: translation) - - contentView.transform = transform - } -} diff --git a/AutoCat/ThirdParty/ATGMediaBrowser/DismissAnimationController.swift b/AutoCat/ThirdParty/ATGMediaBrowser/DismissAnimationController.swift deleted file mode 100644 index 1abbac7..0000000 --- a/AutoCat/ThirdParty/ATGMediaBrowser/DismissAnimationController.swift +++ /dev/null @@ -1,289 +0,0 @@ -// -// DismissAnimationController.swift -// ATGMediaBrowser -// -// Created by Suraj Thomas K on 7/19/18. -// Copyright © 2018 Al Tayer Group LLC. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software -// and associated documentation files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import UIKit - -@MainActor -internal class DismissAnimationController: NSObject { - - private enum Constants { - - static let minimumVelocity: CGFloat = 15.0 - static let minimumTranslation: CGFloat = 0.25 - static let transitionDuration = 0.3 - static let updateFrameRate: CGFloat = 60.0 - static let transitionSpeedFactor: CGFloat = 0.15 - static let minimumZoomDuringInteraction: CGFloat = 0.9 - } - - internal var image: UIImage? - internal let gestureDirection: MediaBrowserViewController.GestureDirection - internal weak var viewController: MediaBrowserViewController? - internal var interactionInProgress = false - - private lazy var imageView = UIImageView() - private var backgroundView: UIView? - - private var timer: Timer? - private var distanceToMove: CGPoint = .zero - private var relativePosition: CGPoint = .zero - private var progressValue: CGFloat { - return (gestureDirection == .horizontal) ? relativePosition.y : relativePosition.x - } - private var shouldZoomOutOnInteraction = false - - init( - image: UIImage? = nil, - gestureDirection: MediaBrowserViewController.GestureDirection, - viewController: MediaBrowserViewController - ) { - - self.image = image - self.gestureDirection = gestureDirection - self.viewController = viewController - } - - internal func handleInteractiveTransition(_ recognizer: UIPanGestureRecognizer) { - - let translation = recognizer.translation(in: recognizer.view) - - let progress = CGPoint( - x: translation.x / UIScreen.main.bounds.size.width, - y: translation.y / UIScreen.main.bounds.size.height - ) - - switch recognizer.state { - case .began: - beginTransition() - fallthrough - case .changed: - relativePosition = progress - updateTransition() - case .ended, .cancelled, .failed: - var toMove: CGFloat = 0.0 - - if abs(progressValue) > Constants.minimumTranslation { - if let viewController = viewController, - let targetFrame = viewController.dataSource?.targetFrameForDismissal(viewController) { - - animateToTargetFrame(targetFrame) - return - - } else { - toMove = (progressValue / abs(progressValue)) - } - } else { - toMove = -progressValue - } - - if gestureDirection == .horizontal { - distanceToMove.x = -relativePosition.x - distanceToMove.y = toMove - } else { - distanceToMove.x = toMove - distanceToMove.y = -relativePosition.y - } - - if timer == nil { - timer = Timer.scheduledTimer( - timeInterval: 1.0/Double(Constants.updateFrameRate), - target: self, - selector: #selector(update(_:)), - userInfo: nil, - repeats: true - ) - } - default: - break - } - } - - internal func animateToTargetFrame(_ target: CGRect) { - - let frame = imageViewFrame(for: imageView.bounds.size, in: target, mode: .scaleAspectFill) - UIView.animate(withDuration: Constants.transitionDuration, animations: { - - self.imageView.frame = frame - self.backgroundView?.alpha = 0.0 - }) { finished in - - if finished { - self.interactionInProgress = false - if self.gestureDirection == .horizontal { - self.relativePosition.y = -1.0 - } else { - self.relativePosition.x = -1.0 - } - self.finishTransition() - } - } - } - - @objc private func update(_ timeInterval: TimeInterval) { - - let speed = (Constants.updateFrameRate * Constants.transitionSpeedFactor) - let xDistance = distanceToMove.x / speed - let yDistance = distanceToMove.y / speed - distanceToMove.x -= xDistance - distanceToMove.y -= yDistance - relativePosition.x += xDistance - relativePosition.y += yDistance - updateTransition() - - let translation = CGPoint( - x: xDistance * (UIScreen.main.bounds.size.width), - y: yDistance * (UIScreen.main.bounds.size.height) - ) - let directionalTranslation = (gestureDirection == .horizontal) ? translation.y : translation.x - if abs(directionalTranslation) < 1.0 { - - relativePosition.x += distanceToMove.x - relativePosition.y += distanceToMove.y - updateTransition() - interactionInProgress = false - - finishTransition() - } - } - - internal func beginTransition() { - - shouldZoomOutOnInteraction = false - if let viewController = viewController { - shouldZoomOutOnInteraction = viewController.dataSource?.targetFrameForDismissal(viewController) != nil - } - - createTransitionViews() - - viewController?.mediaContainerView.isHidden = true - viewController?.hideControls = true - viewController?.visualEffectContainer.isHidden = true - } - - private func finishTransition() { - - distanceToMove = .zero - timer?.invalidate() - timer = nil - - imageView.removeFromSuperview() - - backgroundView?.removeFromSuperview() - backgroundView = nil - - let directionalPosition = (gestureDirection == .horizontal) ? relativePosition.y : relativePosition.x - if directionalPosition != 0.0 { - viewController?.dismiss(animated: false, completion: nil) - } else { - viewController?.mediaContainerView.isHidden = false - viewController?.hideControls = false - viewController?.visualEffectContainer.isHidden = false - } - } - - private func createTransitionViews() { - - backgroundView?.removeFromSuperview() - backgroundView = nil - - if let viewController = viewController, - let bg = viewController.visualEffectContainer.snapshotView(afterScreenUpdates: true) { - backgroundView = bg - viewController.view.addSubview(bg) - NSLayoutConstraint.activate([ - bg.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor), - bg.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor), - bg.topAnchor.constraint(equalTo: viewController.view.topAnchor), - bg.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor) - ]) - } - - imageView.image = image - imageView.frame = imageViewFrame( - for: image?.size ?? .zero, - in: viewController?.view.bounds ?? .zero - ) - viewController?.view.addSubview(imageView) - imageView.transform = CGAffineTransform.identity - } - - private func updateTransition() { - - var transform = CGAffineTransform.identity - let directionalPosition = (gestureDirection == .horizontal) ? relativePosition.y : relativePosition.x - - if shouldZoomOutOnInteraction { - let scale = CGFloat.maximum(Constants.minimumZoomDuringInteraction, 1.0 - abs(directionalPosition)) - transform = transform.scaledBy(x: scale, y: scale) - } - - if gestureDirection == .horizontal { - transform = transform.translatedBy( - x: shouldZoomOutOnInteraction ? relativePosition.x * UIScreen.main.bounds.size.width : 0.0, - y: relativePosition.y * UIScreen.main.bounds.size.height - ) - } else { - transform = transform.translatedBy( - x: relativePosition.x * UIScreen.main.bounds.size.width, - y: shouldZoomOutOnInteraction ? relativePosition.y * UIScreen.main.bounds.size.height : 0.0 - ) - } - imageView.transform = transform - - let alpha = (directionalPosition < 0.0) ? directionalPosition + 1.0 : 1.0 - directionalPosition - backgroundView?.alpha = alpha - } - - - private func imageViewFrame(for imageSize: CGSize, in frame: CGRect, mode: UIView.ContentMode = .scaleAspectFit) -> CGRect { - - guard imageSize != .zero, - mode == .scaleAspectFit || mode == .scaleAspectFill else { - return frame - } - - var targetImageSize = frame.size - - let aspectHeight = frame.size.width / imageSize.width * imageSize.height - let aspectWidth = frame.size.height / imageSize.height * imageSize.width - - if imageSize.width / imageSize.height > frame.size.width / frame.size.height { - if mode == .scaleAspectFit { - targetImageSize.height = aspectHeight - } else { - targetImageSize.width = aspectWidth - } - } else { - if mode == .scaleAspectFit { - targetImageSize.width = aspectWidth - } else { - targetImageSize.height = aspectHeight - } - } - - let x = frame.minX + (frame.size.width - targetImageSize.width) / 2.0 - let y = frame.minY + (frame.size.height - targetImageSize.height) / 2.0 - - return CGRect(origin: CGPoint(x: x, y: y), size: targetImageSize) - } -} diff --git a/AutoCat/ThirdParty/ATGMediaBrowser/MediaBrowserViewController.swift b/AutoCat/ThirdParty/ATGMediaBrowser/MediaBrowserViewController.swift deleted file mode 100644 index ea0f59e..0000000 --- a/AutoCat/ThirdParty/ATGMediaBrowser/MediaBrowserViewController.swift +++ /dev/null @@ -1,1022 +0,0 @@ -// -// MediaBrowserViewController.swift -// ATGMediaBrowser -// -// Created by Suraj Thomas K on 7/10/18. -// Copyright © 2018 Al Tayer Group LLC. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software -// and associated documentation files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import UIKit - -// MARK: - MediaBrowserViewControllerDataSource protocol -/// Protocol to supply media browser contents. -@MainActor -public protocol MediaBrowserViewControllerDataSource: AnyObject { - - /** - Completion block for passing requested media image with details. - - parameter index: Index of the requested media. - - parameter image: Image to be passed back to media browser. - - parameter zoomScale: Zoom scale to be applied to the image including min and max levels. - - parameter error: Error received while fetching the media image. - - - note: - Remember to pass the index received in the datasource method back. - This index is used to set the image to the correct image view. - */ - typealias CompletionBlock = @MainActor @Sendable (_ index: Int, _ image: UIImage?, _ zoomScale: ZoomScale?, _ error: Error?) -> Void - - /** - Method to supply number of items to be shown in media browser. - - parameter mediaBrowser: Reference to media browser object. - - returns: An integer with number of items to be shown in media browser. - */ - func numberOfItems(in mediaBrowser: MediaBrowserViewController) -> Int - - /** - Method to supply image for specific index. - - parameter mediaBrowser: Reference to media browser object. - - parameter index: Index of the requested media. - - parameter completion: Completion block to be executed on fetching the media image. - */ - func mediaBrowser(_ mediaBrowser: MediaBrowserViewController, imageAt index: Int, completion: @escaping CompletionBlock) - - /** - This **optional method** callback is provided to update the styling of close button. - - parameter mediaBrowser: Reference to media browser object. - - parameter button: Reference to close button - - - note: - You can modify the styling of the supplied button, and even add constraints to position - the button relative to it's superview. Remember that if no constraints are applied on the button, - default constraints will be applied on, and will be shown on top-right side of the view. - - On top of that you can add target to this button to handle the closebutton event manually. By - default touch-up-inside event is used to close the media browser. - */ - func mediaBrowser(_ mediaBrowser: MediaBrowserViewController, updateCloseButton button: UIButton) - - /** - This method is used to get the target frame into which the browser will perform the dismiss transition. - - parameter mediaBrowser: Reference to media browser object. - - - note: - If this method is not implemented, the media browser will perform slide up/down transition on dismissal. - */ - func targetFrameForDismissal(_ mediaBrowser: MediaBrowserViewController) -> CGRect? -} - -extension MediaBrowserViewControllerDataSource { - - public func mediaBrowser(_ mediaBrowser: MediaBrowserViewController, updateCloseButton button: UIButton) {} - public func targetFrameForDismissal(_ mediaBrowser: MediaBrowserViewController) -> CGRect? { return nil } -} - -// MARK: - MediaBrowserViewControllerDelegate protocol - -@MainActor -public protocol MediaBrowserViewControllerDelegate: AnyObject { - - /** - Method invoked on scrolling to next/previous media items. - - parameter mediaBrowser: Reference to media browser object. - - parameter index: Index of the newly focussed media item. - - note: - This method will not be called on first load, and will be called only on swiping left and right. - */ - func mediaBrowser(_ mediaBrowser: MediaBrowserViewController, didChangeFocusTo index: Int) -} - -extension MediaBrowserViewControllerDelegate { - - public func mediaBrowser(_ mediaBrowser: MediaBrowserViewController, didChangeFocusTo index: Int) {} -} - -public class MediaBrowserViewController: UIViewController { - - // MARK: - Exposed Enumerations - - /** - Enum to hold supported gesture directions. - - ``` - case horizontal - case vertical - ``` - */ - public enum GestureDirection { - - /// Horizontal (left - right) gestures. - case horizontal - /// Vertical (up - down) gestures. - case vertical - } - - /** - Enum to hold supported browser styles. - - ``` - case linear - case carousel - ``` - */ - public enum BrowserStyle { - - /// Linear browser with *0* as first index and *numItems-1* as last index. - case linear - /// Carousel browser. The media items are repeated in a circular fashion. - case carousel - } - - /** - Enum to hold supported content draw orders. - - ``` - case previousToNext - case nextToPrevious - ``` - - note: - Remember that this is draw order, not positioning. This order decides which item will - be above or below other items, when they overlap. - */ - public enum ContentDrawOrder { - - /// In this mode, media items are rendered in [previous]-[current]-[next] order. - case previousToNext - /// In this mode, media items are rendered in [next]-[current]-[previous] order. - case nextToPrevious - } - - /** - Struct to hold support for customize title style - - ``` - font - textColor - ``` - */ - public struct TitleStyle { - - /// Title style font - public var font: UIFont = UIFont.preferredFont(forTextStyle: .subheadline) - /// Title style text color. - public var textColor: UIColor = .white - } - - // MARK: - Exposed variables - - /// Data-source object to supply media browser contents. - public weak var dataSource: MediaBrowserViewControllerDataSource? - /// Delegate object to get callbacks on media browser events. - public weak var delegate: MediaBrowserViewControllerDelegate? - - /// Gesture direction. Default is `horizontal`. - public var gestureDirection: GestureDirection = .horizontal - /// Content transformer closure. Default is `horizontalMoveInOut`. - public var contentTransformer: ContentTransformer = DefaultContentTransformers.horizontalMoveInOut { - didSet { - - MediaContentView.contentTransformer = contentTransformer - contentViews.forEach({ $0.updateTransform() }) - } - } - /// Content draw order. Default is `previousToNext`. - public var drawOrder: ContentDrawOrder = .previousToNext { - didSet { - if oldValue != drawOrder { - mediaContainerView.exchangeSubview(at: 0, withSubviewAt: 2) - } - } - } - /// Browser style. Default is carousel. - public var browserStyle: BrowserStyle = .carousel - /// Gap between consecutive media items. Default is `50.0`. - public var gapBetweenMediaViews: CGFloat = Constants.gapBetweenContents { - didSet { - MediaContentView.interItemSpacing = gapBetweenMediaViews - contentViews.forEach({ $0.updateTransform() }) - } - } - /// Variable to set title style in media browser. - public var titleStyle: TitleStyle = TitleStyle() { - didSet { - configureTitleLabel() - } - } - /// Variable to set title in media browser - public override var title: String? { - didSet { - titleLabel.text = title - } - } - /// Variable to hide/show title control in media browser. Default is false. - public var shouldShowTitle: Bool = false { - didSet { - titleLabel.isHidden = !shouldShowTitle - } - } - /// Variable to hide/show page control in media browser. - public var shouldShowPageControl: Bool = true { - didSet { - pageControl.isHidden = !shouldShowPageControl - } - } - /// Variable to hide/show controls(close & page control). Default is false. - public var hideControls: Bool = false { - didSet { - hideControlViews(hideControls) - } - } - /** - Variable to schedule/cancel auto-hide controls(close & page control). Default is false. - Default delay is `3.0` seconds. - - todo: Update to accept auto-hide-delay. - */ - public var autoHideControls: Bool = false { - didSet { - if autoHideControls { - DispatchQueue.main.asyncAfter( - deadline: .now() + Constants.controlHideDelay, - execute: controlToggleTask - ) - } else { - controlToggleTask.cancel() - } - } - } - /// Enable or disable interactive dismissal. Default is enabled. - public var enableInteractiveDismissal: Bool = true - /// Item index of the current item. In range `0.. 0 { - updateContents(of: mediaView) - } - } - if drawOrder == .nextToPrevious { - mediaContainerView.exchangeSubview(at: 0, withSubviewAt: 2) - } - } - - private func addCloseButton() { - - view.addSubview(closeButton) - dataSource?.mediaBrowser(self, updateCloseButton: closeButton) - - if closeButton.constraints.isEmpty { - closeButton.translatesAutoresizingMaskIntoConstraints = false - var topAnchor = view.topAnchor - if #available(iOS 11.0, *) { - if view.responds(to: #selector(getter: UIView.safeAreaLayoutGuide)) { - topAnchor = view.safeAreaLayoutGuide.topAnchor - } - } - - NSLayoutConstraint.activate([ - closeButton.topAnchor.constraint(equalTo: topAnchor, constant: Constants.Close.top), - closeButton.trailingAnchor.constraint( - equalTo: view.trailingAnchor, - constant: Constants.Close.trailing - ), - closeButton.widthAnchor.constraint(greaterThanOrEqualToConstant: Constants.Close.minWidth), - closeButton.heightAnchor.constraint(equalToConstant: Constants.Close.height) - ]) - } - controlViews.append(closeButton) - } - - private func addPageControl() { - - view.addSubview(pageControl) - pageControl.translatesAutoresizingMaskIntoConstraints = false - var bottomAnchor = view.bottomAnchor - if #available(iOS 11.0, *) { - if view.responds(to: #selector(getter: UIView.safeAreaLayoutGuide)) { - bottomAnchor = view.safeAreaLayoutGuide.bottomAnchor - } - } - NSLayoutConstraint.activate([ - pageControl.bottomAnchor.constraint(equalTo: bottomAnchor, constant: Constants.PageControl.bottom), - pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor) - ]) - - controlViews.append(pageControl) - } - - private func addTitleLabel() { - - view.addSubview(titleLabel) - titleLabel.translatesAutoresizingMaskIntoConstraints = false - var topAnchor = view.topAnchor - if #available(iOS 11.0, *) { - if view.responds(to: #selector(getter: UIView.safeAreaLayoutGuide)) { - topAnchor = view.safeAreaLayoutGuide.topAnchor - } - } - NSLayoutConstraint.activate([ - titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: Constants.Title.top), - titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor) - ]) - - controlViews.append(titleLabel) - } - - private func configureTitleLabel() { - - titleLabel.font = self.titleStyle.font - titleLabel.textColor = self.titleStyle.textColor - } - - private func hideControlViews(_ hide: Bool) { - - UIView.animate( - withDuration: Constants.animationDuration, - delay: 0.0, - options: .beginFromCurrentState, - animations: { - self.controlViews.forEach { $0.alpha = hide ? 0.0 : 1.0 } - }, - completion: nil - ) - } - - @objc private func didTapOnClose(_ sender: UIButton) { - - if let targetFrame = dataSource?.targetFrameForDismissal(self) { - dismissController.image = sourceImage() - dismissController.beginTransition() - dismissController.animateToTargetFrame(targetFrame) - } else { - dismiss(animated: true, completion: nil) - } - } -} - -// MARK: - Gesture Recognizers - -extension MediaBrowserViewController { - - @objc private func panGestureEvent(_ recognizer: UIPanGestureRecognizer) { - - if dismissController.interactionInProgress { - dismissController.handleInteractiveTransition(recognizer) - return - } - - guard numMediaItems > 0 else { - return - } - - let translation = recognizer.translation(in: view) - - switch recognizer.state { - case .began: - previousTranslation = translation - distanceToMove = 0.0 - timer?.invalidate() - timer = nil - case .changed: - moveViews(by: CGPoint(x: translation.x - previousTranslation.x, y: translation.y - previousTranslation.y)) - case .ended, .failed, .cancelled: - let velocity = recognizer.velocity(in: view) - - var viewsCopy = contentViews - let previousView = viewsCopy.removeFirst() - let middleView = viewsCopy.removeFirst() - let nextView = viewsCopy.removeFirst() - - var toMove: CGFloat = 0.0 - let directionalVelocity = gestureDirection == .horizontal ? velocity.x : velocity.y - - if abs(directionalVelocity) < Constants.minimumVelocity && - abs(middleView.position) < Constants.minimumTranslation { - toMove = -middleView.position - } else if directionalVelocity < 0.0 { - if middleView.position >= 0.0 { - toMove = -middleView.position - } else { - toMove = -nextView.position - } - } else { - if middleView.position <= 0.0 { - toMove = -middleView.position - } else { - toMove = -previousView.position - } - } - - if browserStyle == .linear || numMediaItems <= 1 { - if (middleView.index == 0 && ((middleView.position + toMove) > 0.0)) || - (middleView.index == (numMediaItems - 1) && (middleView.position + toMove) < 0.0) { - - toMove = -middleView.position - } - } - - distanceToMove = toMove - - if timer == nil { - timer = Timer.scheduledTimer( - timeInterval: 1.0/Double(Constants.updateFrameRate), - target: self, - selector: #selector(update(_:)), - userInfo: nil, - repeats: true - ) - } - default: - break - } - - previousTranslation = translation - } - - @objc private func tapGestureEvent(_ recognizer: UITapGestureRecognizer) { - - guard !dismissController.interactionInProgress else { - return - } - - if !controlToggleTask.isCancelled { - controlToggleTask.cancel() - } - hideControls = !hideControls - } -} - -// MARK: - Updating View Positions - -@MainActor -extension MediaBrowserViewController { - - @objc private func update(_ timeInterval: TimeInterval) { - - guard distanceToMove != 0.0 else { - - timer?.invalidate() - timer = nil - return - } - - let distance = distanceToMove / (Constants.updateFrameRate * 0.1) - distanceToMove -= distance - moveViewsNormalized(by: CGPoint(x: distance, y: distance)) - - let translation = CGPoint( - x: distance * (view.frame.size.width + gapBetweenMediaViews), - y: distance * (view.frame.size.height + gapBetweenMediaViews) - ) - let directionalTranslation = (gestureDirection == .horizontal) ? translation.x : translation.y - if abs(directionalTranslation) < 0.1 { - - moveViewsNormalized(by: CGPoint(x: distanceToMove, y: distanceToMove)) - distanceToMove = 0.0 - timer?.invalidate() - timer = nil - } - } - - private func moveViews(by translation: CGPoint) { - - let viewSizeIncludingGap = CGSize( - width: view.frame.size.width + gapBetweenMediaViews, - height: view.frame.size.height + gapBetweenMediaViews - ) - - let normalizedTranslation = calculateNormalizedTranslation( - translation: translation, - viewSize: viewSizeIncludingGap - ) - - moveViewsNormalized(by: normalizedTranslation) - } - - private func moveViewsNormalized(by normalizedTranslation: CGPoint) { - - let isGestureHorizontal = (gestureDirection == .horizontal) - - contentViews.forEach({ - $0.position += isGestureHorizontal ? normalizedTranslation.x : normalizedTranslation.y - }) - - var viewsCopy = contentViews - let previousView = viewsCopy.removeFirst() - let middleView = viewsCopy.removeFirst() - let nextView = viewsCopy.removeFirst() - - let viewSizeIncludingGap = CGSize( - width: view.frame.size.width + gapBetweenMediaViews, - height: view.frame.size.height + gapBetweenMediaViews - ) - - let viewSize = isGestureHorizontal ? viewSizeIncludingGap.width : viewSizeIncludingGap.height - let normalizedGap = gapBetweenMediaViews/viewSize - let normalizedCenter = (middleView.frame.size.width / viewSize) * 0.5 - let viewCount = contentViews.count - - if middleView.position < -(normalizedGap + normalizedCenter) { - - index = sanitizeIndex(index + 1) - - // Previous item is taken and placed on right/down most side - previousView.position += CGFloat(viewCount) - previousView.index += viewCount - updateContents(of: previousView) - - if let image = nextView.image { - self.visualEffectContentView.image = image - } - - contentViews.removeFirst() - contentViews.append(previousView) - - switch drawOrder { - case .previousToNext: - mediaContainerView.bringSubviewToFront(previousView) - case .nextToPrevious: - mediaContainerView.sendSubviewToBack(previousView) - } - - delegate?.mediaBrowser(self, didChangeFocusTo: index) - - } else if middleView.position > (1 + normalizedGap - normalizedCenter) { - - index = sanitizeIndex(index - 1) - - // Next item is taken and placed on left/top most side - nextView.position -= CGFloat(viewCount) - nextView.index -= viewCount - updateContents(of: nextView) - - if let image = previousView.image { - self.visualEffectContentView.image = image - } - - contentViews.removeLast() - contentViews.insert(nextView, at: 0) - - switch drawOrder { - case .previousToNext: - mediaContainerView.sendSubviewToBack(nextView) - case .nextToPrevious: - mediaContainerView.bringSubviewToFront(nextView) - } - - delegate?.mediaBrowser(self, didChangeFocusTo: index) - } - } - - private func calculateNormalizedTranslation(translation: CGPoint, viewSize: CGSize) -> CGPoint { - - guard let middleView = mediaView(at: 1) else { - return .zero - } - - var normalizedTranslation = CGPoint( - x: (translation.x)/viewSize.width, - y: (translation.y)/viewSize.height - ) - - if browserStyle != .carousel || numMediaItems <= 1 { - let isGestureHorizontal = (gestureDirection == .horizontal) - let directionalTranslation = isGestureHorizontal ? normalizedTranslation.x : normalizedTranslation.y - if (middleView.index == 0 && ((middleView.position + directionalTranslation) > 0.0)) || - (middleView.index == (numMediaItems - 1) && (middleView.position + directionalTranslation) < 0.0) { - if isGestureHorizontal { - normalizedTranslation.x *= Constants.bounceFactor - } else { - normalizedTranslation.y *= Constants.bounceFactor - } - } - } - return normalizedTranslation - } - - private func updateContents(of contentView: MediaContentView) { - - contentView.image = nil - let convertedIndex = sanitizeIndex(contentView.index) - contentView.isLoading = true - dataSource?.mediaBrowser( - self, - imageAt: convertedIndex, - completion: { [weak self] (index, image, zoom, _) in - - guard let strongSelf = self else { - return - } - - if index == strongSelf.sanitizeIndex(contentView.index) { - if image != nil { - contentView.image = image - contentView.zoomLevels = zoom - - if index == strongSelf.index { - strongSelf.visualEffectContentView.image = image - } - } - contentView.isLoading = false - } - } - ) - } - - private func sanitizeIndex(_ index: Int) -> Int { - - let newIndex = index % numMediaItems - if newIndex < 0 { - return newIndex + numMediaItems - } - return newIndex - } - - private func sourceImage() -> UIImage? { - - return mediaView(at: 1)?.image - } - - private func mediaView(at index: Int) -> MediaContentView? { - - guard index < contentViews.count else { - - assertionFailure("Content views does not have this many views. : \(index)") - return nil - } - return contentViews[index] - } -} - -// MARK: - UIGestureRecognizerDelegate - -extension MediaBrowserViewController: UIGestureRecognizerDelegate { - - public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - - guard enableInteractiveDismissal else { - return true - } - - let middleView = mediaView(at: 1) - if middleView?.zoomScale == middleView?.zoomLevels?.minimumZoomScale, - let recognizer = gestureRecognizer as? UIPanGestureRecognizer { - - let translation = recognizer.translation(in: recognizer.view) - - if gestureDirection == .horizontal { - dismissController.interactionInProgress = abs(translation.y) > abs(translation.x) - } else { - dismissController.interactionInProgress = abs(translation.x) > abs(translation.y) - } - if dismissController.interactionInProgress { - dismissController.image = sourceImage() - } - } - return true - } - - public func gestureRecognizer( - _ gestureRecognizer: UIGestureRecognizer, - shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer - ) -> Bool { - - if gestureRecognizer is UIPanGestureRecognizer, - let scrollView = otherGestureRecognizer.view as? MediaContentView { - return scrollView.zoomScale == 1.0 - } - return false - } - - public func gestureRecognizer( - _ gestureRecognizer: UIGestureRecognizer, - shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer - ) -> Bool { - - if gestureRecognizer is UITapGestureRecognizer { - return otherGestureRecognizer.view is MediaContentView - } - return false - } -} diff --git a/AutoCat/ThirdParty/ATGMediaBrowser/MediaContentView.swift b/AutoCat/ThirdParty/ATGMediaBrowser/MediaContentView.swift deleted file mode 100644 index 9dcb5a1..0000000 --- a/AutoCat/ThirdParty/ATGMediaBrowser/MediaContentView.swift +++ /dev/null @@ -1,315 +0,0 @@ -// -// MediaContentView.swift -// ATGMediaBrowser -// -// Created by Suraj Thomas K on 7/10/18. -// Copyright © 2018 Al Tayer Group LLC. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software -// and associated documentation files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -import UIKit - -/// Holds the value of minimumZoomScale and maximumZoomScale of the image. -public struct ZoomScale: Sendable { - - /// Minimum zoom level, the image can be zoomed out to. - public var minimumZoomScale: CGFloat - - /// Maximum zoom level, the image can be zoomed into. - public var maximumZoomScale: CGFloat - - /// Default zoom scale. minimum is 1.0 and maximum is 3.0 - public static let `default` = ZoomScale( - minimum: 1.0, - maximum: 3.0 - ) - - /// Identity zoom scale. Pass this to disable zoom. - public static let identity = ZoomScale( - minimum: 1.0, - maximum: 1.0 - ) - - /** - Initializer. - - parameter minimum: The minimum zoom level. - - parameter maximum: The maximum zoom level. - */ - public init(minimum: CGFloat, maximum: CGFloat) { - - minimumZoomScale = minimum - maximumZoomScale = maximum - } -} - -internal class MediaContentView: UIScrollView { - - // MARK: - Exposed variables - internal static var interItemSpacing: CGFloat = 0.0 - internal var index: Int { - didSet { - resetZoom() - } - } - internal static var contentTransformer: ContentTransformer = DefaultContentTransformers.horizontalMoveInOut - - internal var position: CGFloat { - didSet { - updateTransform() - } - } - internal var image: UIImage? { - didSet { - updateImageView() - } - } - internal var isLoading: Bool = false { - didSet { - indicatorContainer.isHidden = !isLoading - if isLoading { - indicator.startAnimating() - } else { - indicator.stopAnimating() - } - } - } - internal var zoomLevels: ZoomScale? { - didSet { - zoomScale = ZoomScale.default.minimumZoomScale - minimumZoomScale = zoomLevels?.minimumZoomScale ?? ZoomScale.default.minimumZoomScale - maximumZoomScale = zoomLevels?.maximumZoomScale ?? ZoomScale.default.maximumZoomScale - } - } - - // MARK: - Private enumerations - - private enum Constants { - - static let indicatorViewSize: CGFloat = 60.0 - } - - // MARK: - Private variables - - private lazy var imageView: UIImageView = { - let imageView = UIImageView() - imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - imageView.contentMode = .scaleAspectFit - imageView.clipsToBounds = true - return imageView - }() - - private lazy var indicator: UIActivityIndicatorView = { - let indicatorView = UIActivityIndicatorView() - indicatorView.style = UIActivityIndicatorView.Style.large - indicatorView.hidesWhenStopped = true - return indicatorView - }() - - private lazy var indicatorContainer: UIView = { - let container = UIView() - container.backgroundColor = .darkGray - container.layer.cornerRadius = Constants.indicatorViewSize * 0.5 - container.layer.masksToBounds = true - return container - }() - - private lazy var doubleTapGestureRecognizer: UITapGestureRecognizer = { [unowned self] in - let gesture = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap(_:))) - gesture.numberOfTapsRequired = 2 - gesture.numberOfTouchesRequired = 1 - return gesture - }() - - init(index itemIndex: Int, position: CGFloat, frame: CGRect) { - - self.index = itemIndex - self.position = position - - super.init(frame: frame) - - initializeViewComponents() - } - - required init?(coder aDecoder: NSCoder) { - - fatalError("Do nto use `init?(coder:)`") - } -} - -// MARK: - View Composition and Events - -extension MediaContentView { - - private func initializeViewComponents() { - - addSubview(imageView) - imageView.frame = frame - - setupIndicatorView() - - configureScrollView() - - addGestureRecognizer(doubleTapGestureRecognizer) - - updateTransform() - } - - private func configureScrollView() { - - isMultipleTouchEnabled = true - showsHorizontalScrollIndicator = false - showsVerticalScrollIndicator = false - contentSize = imageView.bounds.size - canCancelContentTouches = false - zoomLevels = ZoomScale.default - delegate = self - bouncesZoom = false - } - - private func resetZoom() { - - setZoomScale(1.0, animated: false) - imageView.transform = CGAffineTransform.identity - contentSize = imageView.frame.size - contentOffset = .zero - } - - private func setupIndicatorView() { - - addSubview(indicatorContainer) - indicatorContainer.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - indicatorContainer.widthAnchor.constraint(equalToConstant: Constants.indicatorViewSize), - indicatorContainer.heightAnchor.constraint(equalToConstant: Constants.indicatorViewSize), - indicatorContainer.centerXAnchor.constraint(equalTo: centerXAnchor), - indicatorContainer.centerYAnchor.constraint(equalTo: centerYAnchor) - ]) - - indicatorContainer.addSubview(indicator) - indicator.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - indicator.leadingAnchor.constraint(equalTo: indicatorContainer.leadingAnchor), - indicator.trailingAnchor.constraint(equalTo: indicatorContainer.trailingAnchor), - indicator.topAnchor.constraint(equalTo: indicatorContainer.topAnchor), - indicator.bottomAnchor.constraint(equalTo: indicatorContainer.bottomAnchor) - ]) - - indicatorContainer.setNeedsLayout() - indicatorContainer.layoutIfNeeded() - - indicatorContainer.isHidden = true - } - - internal func updateTransform() { - - MediaContentView.contentTransformer(self, position) - } - - internal func handleChangeInViewSize(to size: CGSize) { - - let oldScale = zoomScale - zoomScale = 1.0 - imageView.frame = CGRect(origin: .zero, size: size) - - updateImageView() - updateTransform() - setZoomScale(oldScale, animated: false) - - contentSize = imageView.frame.size - } - - @objc private func didDoubleTap(_ recognizer: UITapGestureRecognizer) { - - let locationInImage = recognizer.location(in: imageView) - - let isImageCoveringScreen = imageView.frame.size.width > bounds.size.width && - imageView.frame.size.height > bounds.size.height - let zoomTo = (isImageCoveringScreen || zoomScale == maximumZoomScale) ? minimumZoomScale : maximumZoomScale - - guard zoomTo != zoomScale else { - return - } - - let width = bounds.size.width / zoomTo - let height = bounds.size.height / zoomTo - - let zoomRect = CGRect( - x: locationInImage.x - width * 0.5, - y: locationInImage.y - height * 0.5, - width: width, - height: height - ) - - zoom(to: zoomRect, animated: true) - } -} - -// MARK: - UIScrollViewDelegate - -extension MediaContentView: UIScrollViewDelegate { - - internal func viewForZooming(in scrollView: UIScrollView) -> UIView? { - - let shouldAllowZoom = (image != nil && position == 0.0) - return shouldAllowZoom ? imageView : nil - } - - internal func scrollViewDidZoom(_ scrollView: UIScrollView) { - - centerImageView() - } - - private func centerImageView() { - - var imageViewFrame = imageView.frame - - if imageViewFrame.size.width < bounds.size.width { - imageViewFrame.origin.x = (bounds.size.width - imageViewFrame.size.width) / 2.0 - } else { - imageViewFrame.origin.x = 0.0 - } - - if imageViewFrame.size.height < bounds.size.height { - imageViewFrame.origin.y = (bounds.size.height - imageViewFrame.size.height) / 2.0 - } else { - imageViewFrame.origin.y = 0.0 - } - - imageView.frame = imageViewFrame - } - - private func updateImageView() { - - imageView.image = image - - if let contentImage = image { - - let imageViewSize = bounds.size - let imageSize = contentImage.size - var targetImageSize = imageViewSize - - if imageSize.width / imageSize.height > imageViewSize.width / imageViewSize.height { - targetImageSize.height = imageViewSize.width / imageSize.width * imageSize.height - } else { - targetImageSize.width = imageViewSize.height / imageSize.height * imageSize.width - } - - imageView.frame = CGRect(origin: .zero, size: targetImageSize) - } - centerImageView() - } -} diff --git a/AutoCat/Utils/ActivityItemSource.swift b/AutoCat/Utils/ActivityItemSource.swift deleted file mode 100644 index 90fecd7..0000000 --- a/AutoCat/Utils/ActivityItemSource.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// ActivityItemSource.swift -// AutoCat -// -// Created by Selim Mustafaev on 11.06.2024. -// Copyright © 2024 Selim Mustafaev. All rights reserved. -// - -import UIKit -import LinkPresentation - -class ActivityItemSource: NSObject, UIActivityItemSource { - - let url: URL - let title: String - - init(url: URL, title: String) { - self.url = url - self.title = title - } - - func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { - return UIImage() - } - - func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - return url - } - - func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? { - - let metadata = LPLinkMetadata() - metadata.title = title - metadata.originalURL = url - metadata.url = url - metadata.imageProvider = NSItemProvider(contentsOf: url) - metadata.iconProvider = NSItemProvider(contentsOf: url) - return metadata - } -} diff --git a/AutoCat/Utils/AudioPlayer.swift b/AutoCat/Utils/AudioPlayer.swift deleted file mode 100644 index e3bf69f..0000000 --- a/AutoCat/Utils/AudioPlayer.swift +++ /dev/null @@ -1,107 +0,0 @@ -import Foundation -import AVFoundation - -enum PlayerState { - case stopped - case paused - case playing -} - -class AudioPlayer: NSObject, AVAudioPlayerDelegate { - - @MainActor static let shared = AudioPlayer() - - private var player: AVAudioPlayer? - private var url: URL? - private var state: PlayerState = .stopped - private var progress: Double = 0 - private var progressTimer: Timer? - - func set(url: URL) throws { - if let curUrl = self.url, curUrl == url { - return - } - - self.stop() - self.url = url - self.player = try AVAudioPlayer(contentsOf: url) - self.player?.delegate = self - } - - func play() throws { - if let player = self.player { - if player.isPlaying { - player.pause() - try self.deactivateSession() - self.state = .paused - } else { - try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.duckOthers]) - try AVAudioSession.sharedInstance().setActive(true) - player.play() - self.state = .playing - if self.progressTimer == nil { - self.progressTimer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(progressTick), userInfo: nil, repeats: true) - } - } - } - } - - func play(url: URL) throws { - try self.set(url: url) - try self.play() - } - - func pause() { - if let player = self.player { - player.pause() - try? self.deactivateSession() - self.state = .paused - } - } - - func stop() { - if let player = self.player { - player.stop() - try? self.deactivateSession() - self.state = .stopped - self.progressTimer?.invalidate() - self.progressTimer = nil - } - } - - func getState() -> PlayerState { - return self.state - } - - func getProgress() -> Double { - return self.progress - } - - func getUrl() -> URL? { - return self.url - } - - func duration() -> TimeInterval { - return self.player?.duration ?? 0 - } - - func deactivateSession() throws { - try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) - } - - // MARK: - AVAudioPlayerDelegate - - func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { - try? self.deactivateSession() - self.progress = 1 - self.stop() - self.state = .stopped - } - - @objc func progressTick() { - if let player = self.player { - let progress = player.currentTime/player.duration - self.progress = progress - } - } -} diff --git a/AutoCat/Utils/Recorder.swift b/AutoCat/Utils/Recorder.swift deleted file mode 100644 index 9c6b99a..0000000 --- a/AutoCat/Utils/Recorder.swift +++ /dev/null @@ -1,151 +0,0 @@ -import Foundation -import Speech -import AVFoundation -import AudioToolbox -import os.log -import ExceptionCatcher - -final class Recorder { - - let engine = AVAudioEngine() - var fileRef: ExtAudioFileRef? = nil - let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "ru_RU")) - let request = SFSpeechAudioBufferRecognitionRequest() - var recognitionTask: SFSpeechRecognitionTask? - var endRecognitionTimer: Timer? - var result: String = "" - - let recordingSettings: [String:Any] = [ - AVFormatIDKey:kAudioFormatMPEG4AAC_HE, - AVSampleRateKey:44100.0, - AVNumberOfChannelsKey:2, - //AVEncoderBitRateKey:320*1024, - //AVLinearPCMBitDepthKey:16, - AVEncoderAudioQualityKey:AVAudioQuality.max.rawValue - ] - - init() { - self.request.contextualStrings = ["61", "161", "761", "123", "750", "777", "799"] - } - - func microphoneAvailable() -> Bool { - // FIXME: - return true - } - - func requestPermissions() async throws { - - try await withCheckedThrowingContinuation { continuation in - AVAudioSession.sharedInstance().requestRecordPermission { allowed in - if allowed { - SFSpeechRecognizer.requestAuthorization { status in - switch status { - case .authorized: - continuation.resume() - break - case .denied: - let error = CocoaError.error("Access error", reason: "Access to speech recognition is denied", suggestion: "Please give permission to use speech recognition in system settings") - continuation.resume(throwing: error) - case .restricted: - let error = CocoaError.error("Access error", reason: "Speech recognition is restricted on this device") - continuation.resume(throwing: error) - case .notDetermined: - let error = CocoaError.error("Access error", reason: "Speech recognition status is not yet determined") - continuation.resume(throwing: error) - @unknown default: - let error = CocoaError.error("Access error", reason: "Unknown error accessing speech recognizer") - continuation.resume(throwing: error) - } - } - } else { - let error = CocoaError.error("Access error", reason: "Access to microphone is denied", suggestion: "Please give permission to use microphone in system settings") - continuation.resume(throwing: error) - } - } - } - } - - func startRecording(to file: URL) async throws -> String { - guard self.microphoneAvailable() else { - throw CocoaError.error("Recording error", reason: "Microphone not found") - } - - return try await withCheckedThrowingContinuation { continuation in - guard let aac = AVAudioFormat(settings: self.recordingSettings) else { - continuation.resume(throwing: CocoaError.error("Recording error", reason: "Format not supported")) - return - } - - ExtAudioFileCreateWithURL(file as CFURL, kAudioFileM4AType, aac.streamDescription, nil, AudioFileFlags.eraseFile.rawValue, &self.fileRef) - guard let fileRef = self.fileRef else { - continuation.resume(throwing: CocoaError.error(CocoaError.Code.fileWriteUnknown)) - return - } - - do { - try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: []) - try AVAudioSession.sharedInstance().setActive(true) - - let inFormat = self.engine.inputNode.outputFormat(forBus: 0) - ExtAudioFileSetProperty(fileRef, kExtAudioFileProperty_ClientDataFormat, UInt32(MemoryLayout.size), inFormat.streamDescription) - - try ExceptionCatcher.catch { - self.engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: inFormat) { buffer, time in - self.request.append(buffer) - ExtAudioFileWrite(fileRef, buffer.frameLength, buffer.audioBufferList) - } - } - - self.recognitionTask = self.recognizer!.recognitionTask(with: self.request) { result, error in - if let transcription = result?.bestTranscription { - self.result = transcription.formattedString - self.endRecognitionTimer?.invalidate() - - // TODO: Check if it actually works - RunLoop.current.schedule(after: .init(Date()).advanced(by: .seconds(5)), tolerance: .seconds(1), options: nil) { - self.finishRecording() - continuation.resume(returning: self.result) - } - } - } - - RunLoop.current.schedule(after: .init(Date()).advanced(by: .seconds(5)), tolerance: .seconds(1), options: nil) { - self.finishRecording() - continuation.resume(returning: self.result) - } - - self.engine.prepare() - try self.engine.start() - } catch { - continuation.resume(throwing: error) - } - } - } - - func cancelRecording() { - self.finishRecording() - self.endRecognitionTimer?.invalidate() - self.endRecognitionTimer = nil - } - - func finishRecording() { - guard self.engine.isRunning else { return } - - self.engine.stop() - self.engine.inputNode.removeTap(onBus: 0) - self.request.endAudio() - self.recognitionTask?.cancel() - try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) - try? AVAudioSession.sharedInstance().setCategory(.soloAmbient) - - if let fileRef = self.fileRef { - ExtAudioFileDispose(fileRef) - } - } - - func stopRecording() { - self.finishRecording() - self.endRecognitionTimer?.fire() - self.endRecognitionTimer = nil - } -} diff --git a/AutoCat/Utils/RxRealmDataSource.swift b/AutoCat/Utils/RxRealmDataSource.swift deleted file mode 100644 index 1cbd2b1..0000000 --- a/AutoCat/Utils/RxRealmDataSource.swift +++ /dev/null @@ -1,207 +0,0 @@ -import UIKit -import RealmSwift -import ExceptionCatcher -import AutoCatCore - -typealias FilterPredicate = (T) -> Bool - -class RealmSectionedDataSource: NSObject, UITableViewDataSource - where Item: Object & DtoConvertible, - Item.Dto: Identifiable & Dated, - Cell: UITableViewCell & ConfigurableCell, - Cell.Item == Item.Dto { - - private var tv: UITableView - private var data: Results - private var notificationToken: NotificationToken? - private var sections: [DateSection] = [] - private var cellIdentifier: String - private var filterPredicate: FilterPredicate? - private var searchPredicate: FilterPredicate? - private let groupQueue = DispatchQueue(label: "group") - - private var objects: [Item.Dto] = [] - - private let onSizeChanged: ((Int) -> Void)? - - init(table: UITableView, data: Results, cellIdentifier: String = String(describing: Cell.self), onSizeChanged: ((Int) -> Void)? = nil) { - self.tv = table - self.data = data - self.cellIdentifier = cellIdentifier - self.onSizeChanged = onSizeChanged - super.init() - self.tv.dataSource = self - self.tv.reloadData() - - startObservingChanges() - } - - func startObservingChanges() { - self.notificationToken = self.data.observe { changes in - switch changes { - case .initial: - self.objects = self.data.toArray().map(\.shallowDto) - self.reload(animated: false) - case .update(_, let deletions, let insertions, let modifications): - - deletions.forEach { self.objects.remove(at: $0) } - insertions.forEach { self.objects.insert(self.data[$0].shallowDto, at: $0) } - modifications.forEach { self.objects[$0] = self.data[$0].shallowDto } - self.reload() - -// if deletions.isEmpty && modifications.isEmpty && insertions.count == 1 && insertions.first == 0 { -// // Most common use case - adding new element to the top of the list -// // Example - checking new number -// self.insertFirst() -// } else if deletions.isEmpty && insertions.isEmpty && modifications.count == 1 && modifications.first == 0 { -// self.reloadFirst() -// } else if modifications.isEmpty && deletions.count == 1 && insertions.count == 1 && insertions.first == 0 { -// // Probably moving an item to the first position -// // For example - updating number info -// self.moveToFirst(deleteIndex: deletions.first!) -// } else { -// self.reload(animated: false) -// } - case .error(let err): - print("Realm observer error: \(err)") - } - } - } - - // MARK: - UITableViewDataSource - - func numberOfSections(in tableView: UITableView) -> Int { - return self.sections.count - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.sections[section].elements.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath) as? Cell else { - return UITableViewCell() - } - - let item = self.sections[indexPath.section].elements[indexPath.row] - cell.configure(with: item) - return cell - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return self.sections[section].header - } - - // MARK: - Public - - func item(at indexPath: IndexPath) -> Item.Dto { - return self.sections[indexPath.section].elements[indexPath.row] - } - - func reload(animated: Bool = true) { - var items = objects - if let filterPredicate = self.filterPredicate { - items = items.filter(filterPredicate) - } - - if let searchPredicate = self.searchPredicate { - items = items.filter(searchPredicate) - } - - self.onSizeChanged?(items.count) - - self.sections = items.groupedByDate() - self.tv.reloadData() - } - - func insertFirst() { - guard let item = data.first?.shallowDto else { - reload(animated: false) - return - } - - if sections.isEmpty { - sections = [item].groupedByDate() - tv.insertSections(IndexSet(integer: 0), with: .fade) - } else { - sections[0].insert(item, at: 0) - tv.insertRows(at: [IndexPath(row: 0, section: 0)], with: .fade) - } - } - - func reloadFirst() { - guard !sections.isEmpty, let item = data.first?.dto else { - reload(animated: false) - return - } - - sections[0].replace(item, at: 0) - tv.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .none) - } - - func moveToFirst(deleteIndex: Int) { - var itemIndex = deleteIndex - for index in 0.. Int? { - return nil - } - - func setFilterPredicate(_ predicate: FilterPredicate?) { - guard self.filterPredicate != nil || predicate != nil else { - return - } - - self.filterPredicate = predicate - self.reload() - } - - func setSearchPredicate(_ predicate: FilterPredicate?) { - guard self.searchPredicate != nil || predicate != nil else { - return - } - - self.searchPredicate = predicate - self.reload() - } - - func makeCsv() throws -> String { - guard let type = Item.self as? Exportable.Type else { - throw CocoaError.error("Type \(Item.self) is not exportable") - } - - var items = self.data.toArray().map(\.dto) - if let predicate = self.filterPredicate { - items = items.filter(predicate) - } - - var result = "" - let newLine: Character = "\r\n" //"\u{000B}" - result.append(type.csvHeader) - result.append(newLine) - - for item in items { - if let exportableItem = item as? Exportable { - result.append(exportableItem.csvLine) - result.append(newLine) - } - } - - return result - } -} diff --git a/AutoCat/Utils/RxSectionedDataSource.swift b/AutoCat/Utils/RxSectionedDataSource.swift deleted file mode 100644 index 7ac38c3..0000000 --- a/AutoCat/Utils/RxSectionedDataSource.swift +++ /dev/null @@ -1,87 +0,0 @@ -import UIKit -import AutoCatCore - -class SectionedDataSource: NSObject, UITableViewDataSource where Item: Dated & Decodable & Identifiable & Sendable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item { - private var tv: UITableView - private var cellIdentifier: String - private var sections: [DateSection] = [] - private var items: [Item] = [] - private var sortParam: SortParameter = .updatedDate - private(set) var pageToken: String? = nil - private(set) var count: Int? = nil - - init(table: UITableView, cellIdentifier: String = String(describing: Cell.self)) { - self.tv = table - self.cellIdentifier = cellIdentifier - super.init() - self.tv.dataSource = self - } - - // MARK: - UITableViewDataSource - - func numberOfSections(in tableView: UITableView) -> Int { - return self.sections.count - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.sections[section].elements.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath) as? Cell else { - return UITableViewCell() - } - - let item = self.sections[indexPath.section].elements[indexPath.row] - cell.configure(with: item) - return cell - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return self.sections[section].header - } - - // MARK: - Public - - func item(at indexPath: IndexPath) -> Item { - return self.sections[indexPath.section].elements[indexPath.row] - } - - func set(item: Item, at indexPath: IndexPath) { - self.sections[indexPath.section].elements[indexPath.row] = item - } - - func update(with data: PagedResponse) { - if let count = data.count { - self.count = count - self.items = data.items - } else { - self.items.append(contentsOf: data.items) - } - self.pageToken = data.pageToken - - // TODO: Grouping on background thread - // DispatchQueue.global().async { - let newSections = self.items.groupedByDate(type: self.sortParam) - // DispatchQueue.main.async { - self.sections = newSections - self.tv.reloadData() - // } - // } - } - - func needMoreData() -> Bool { - guard let count = self.count else { return true } - return self.items.count < count - } - - func reset() { - self.pageToken = nil - self.count = nil - self.items = [] - } - - func setSortParameter(_ param: SortParameter) { - self.sortParam = param - } -}