diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index f16b15b..c575daa 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -37,7 +37,6 @@ 7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */; }; 7A27ADF7249FEF690035F39E /* Recorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF6249FEF690035F39E /* Recorder.swift */; }; 7A2C96122C3B155B00AE46B5 /* NoteAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2C96112C3B155B00AE46B5 /* NoteAlertModifier.swift */; }; - 7A2DE69B25869ABD00A113FC /* AdsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2DE69A25869ABD00A113FC /* AdsController.swift */; }; 7A2DE69E2589606A00A113FC /* ImageGridRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2DE69D2589606A00A113FC /* ImageGridRow.swift */; }; 7A2E6FA72C42B3AD00C40DA7 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; }; 7A33381124990DAE00D878F1 /* FiltersController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33381024990DAE00D878F1 /* FiltersController.swift */; }; @@ -90,6 +89,9 @@ 7A7158042C43EAA200852088 /* OwnersCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7158032C43EAA200852088 /* OwnersCoordinator.swift */; }; 7A7158072C44085600852088 /* OsagoScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7158062C44085600852088 /* OsagoScreen.swift */; }; 7A7158092C44087E00852088 /* OsagoCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7158082C44087E00852088 /* OsagoCoordinator.swift */; }; + 7A71580C2C44453200852088 /* AdsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A71580B2C44453200852088 /* AdsScreen.swift */; }; + 7A71580E2C4445A200852088 /* AdsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A71580D2C4445A200852088 /* AdsCoordinator.swift */; }; + 7A7158122C444A6400852088 /* AdsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7158112C444A6400852088 /* AdsViewModel.swift */; }; 7A7547E024032CB6004E8406 /* VehiclePhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7547DF24032CB6004E8406 /* VehiclePhotoCell.swift */; }; 7A761C042677F18E0005F28F /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474323FF06CA00B424AF /* ApiService.swift */; }; 7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF824A09CAD0035F39E /* CocoaError.swift */; }; @@ -108,6 +110,8 @@ 7A8AB76B25A1D95500ECF2C1 /* SourceStatusRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8AB76A25A1D95500ECF2C1 /* SourceStatusRow.swift */; }; 7A8C4A5B2C1C55680052DDF3 /* SwiftLocation in Frameworks */ = {isa = PBXBuildFile; productRef = 7A8C4A5A2C1C55680052DDF3 /* SwiftLocation */; }; 7A91894F29A2BD8700519C74 /* GestureRecognizers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A91894E29A2BD8700519C74 /* GestureRecognizers.swift */; }; + 7A961C6C2C4C3C8600CE2211 /* TextRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */; }; + 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 */; }; @@ -116,6 +120,10 @@ 7AA7BC3325A5DFB80053A5D5 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D332402A91C00CE01A0 /* Kingfisher */; }; 7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */; }; 7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; }; + 7AAAFAD32C4D0FD00050410D /* ACImageSliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFAD22C4D0FD00050410D /* ACImageSliderView.swift */; }; + 7AAAFADA2C4D1AFE0050410D /* Zoomable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFAD92C4D1AFE0050410D /* Zoomable.swift */; }; + 7AAAFADC2C4D1E130050410D /* ACImageSliderView+Modifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFADB2C4D1E130050410D /* ACImageSliderView+Modifier.swift */; }; + 7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFADD2C4D23620050410D /* ACImageSliderModel.swift */; }; 7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1F1267E9CC800D7AB32 /* SwiftDate */; }; 7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; }; 7AB5871D2C42C1CF00FA7B66 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7AB5871C2C42C1CF00FA7B66 /* RealmSwift */; }; @@ -167,7 +175,6 @@ 7AF6D22A2677C3AD0086EA64 /* Exportable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8424D26109F78002F6B31 /* Exportable.swift */; }; 7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8BF2C3024E5003C491D /* ACHud.swift */; }; 7AFBE8C42C302561003C491D /* ACHudContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8C32C302561003C491D /* ACHudContainer.swift */; }; - 7AFBE8C82C30816E003C491D /* ACProgressHud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8C72C30816E003C491D /* ACProgressHud.swift */; }; 7AFBE8CA2C3081C7003C491D /* ACProgressHud+Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8C92C3081C7003C491D /* ACProgressHud+Modifiers.swift */; }; 7AFBE8CC2C3085C6003C491D /* ACProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8CB2C3085C6003C491D /* ACProgressView.swift */; }; 7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8CD2C308B53003C491D /* ACMessageView.swift */; }; @@ -264,7 +271,6 @@ 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 = ""; }; - 7A2DE69A25869ABD00A113FC /* AdsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsController.swift; sourceTree = ""; }; 7A2DE69D2589606A00A113FC /* ImageGridRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGridRow.swift; sourceTree = ""; }; 7A2E6FA32C42B3AD00C40DA7 /* AutoCatCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AutoCatCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7A33381024990DAE00D878F1 /* FiltersController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersController.swift; sourceTree = ""; }; @@ -328,6 +334,9 @@ 7A7158032C43EAA200852088 /* OwnersCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwnersCoordinator.swift; sourceTree = ""; }; 7A7158062C44085600852088 /* OsagoScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OsagoScreen.swift; sourceTree = ""; }; 7A7158082C44087E00852088 /* OsagoCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OsagoCoordinator.swift; sourceTree = ""; }; + 7A71580B2C44453200852088 /* AdsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsScreen.swift; sourceTree = ""; }; + 7A71580D2C4445A200852088 /* AdsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsCoordinator.swift; sourceTree = ""; }; + 7A7158112C444A6400852088 /* AdsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsViewModel.swift; sourceTree = ""; }; 7A7547DF24032CB6004E8406 /* VehiclePhotoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehiclePhotoCell.swift; sourceTree = ""; }; 7A761C0A267E8FF90005F28F /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.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; }; @@ -342,12 +351,18 @@ 7A8AB76A25A1D95500ECF2C1 /* SourceStatusRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceStatusRow.swift; sourceTree = ""; }; 7A91894E29A2BD8700519C74 /* GestureRecognizers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureRecognizers.swift; sourceTree = ""; }; 7A92D0AB240425B100EF3B77 /* ATGMediaBrowser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ATGMediaBrowser.framework; path = Carthage/Build/iOS/ATGMediaBrowser.framework; sourceTree = ""; }; + 7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRowView.swift; sourceTree = ""; }; + 7A961C6D2C4C3C9E00CE2211 /* LinkRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkRowView.swift; sourceTree = ""; }; 7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSignInController.swift; sourceTree = ""; }; 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 = ""; }; + 7AAAFAD22C4D0FD00050410D /* ACImageSliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACImageSliderView.swift; sourceTree = ""; }; + 7AAAFAD92C4D1AFE0050410D /* Zoomable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zoomable.swift; sourceTree = ""; }; + 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 = ""; }; 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxSectionedDataSource.swift; sourceTree = ""; }; 7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = ""; }; 7AB562B9249C9E9B00473D53 /* VehicleRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleRegion.swift; sourceTree = ""; }; @@ -383,7 +398,6 @@ 7AF6D1F22677C03B0086EA64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7AFBE8BF2C3024E5003C491D /* ACHud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACHud.swift; sourceTree = ""; }; 7AFBE8C32C302561003C491D /* ACHudContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACHudContainer.swift; sourceTree = ""; }; - 7AFBE8C72C30816E003C491D /* ACProgressHud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACProgressHud.swift; sourceTree = ""; }; 7AFBE8C92C3081C7003C491D /* ACProgressHud+Modifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ACProgressHud+Modifiers.swift"; sourceTree = ""; }; 7AFBE8CB2C3085C6003C491D /* ACProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACProgressView.swift; sourceTree = ""; }; 7AFBE8CD2C308B53003C491D /* ACMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACMessageView.swift; sourceTree = ""; }; @@ -506,7 +520,6 @@ isa = PBXGroup; children = ( 7A813DC7250B5C6E00CC93B9 /* Location */, - 7A2DE69A25869ABD00A113FC /* AdsController.swift */, 7A11471923FE839000B424AF /* AuthController.swift */, 7A530B7924001D3300CBFE6E /* CheckController.swift */, 7A33381024990DAE00D878F1 /* FiltersController.swift */, @@ -580,6 +593,7 @@ 7A1441632C297E9800E79018 /* Screens */ = { isa = PBXGroup; children = ( + 7A71580A2C44451B00852088 /* AdsScreen */, 7A7158052C44083F00852088 /* OsagoScreen */, 7A7157FE2C43EA5200852088 /* OwnersScreen */, 7A1441642C297EA800E79018 /* NotesScreen */, @@ -750,6 +764,16 @@ path = OsagoScreen; sourceTree = ""; }; + 7A71580A2C44451B00852088 /* AdsScreen */ = { + isa = PBXGroup; + children = ( + 7A71580B2C44453200852088 /* AdsScreen.swift */, + 7A71580D2C4445A200852088 /* AdsCoordinator.swift */, + 7A7158112C444A6400852088 /* AdsViewModel.swift */, + ); + path = AdsScreen; + sourceTree = ""; + }; 7A761C06267E8E720005F28F /* ThirdParty */ = { isa = PBXGroup; children = ( @@ -772,6 +796,16 @@ path = Location; sourceTree = ""; }; + 7AAAFAD12C4D0FB00050410D /* ACImageSlider */ = { + isa = PBXGroup; + children = ( + 7AAAFAD22C4D0FD00050410D /* ACImageSliderView.swift */, + 7AAAFADB2C4D1E130050410D /* ACImageSliderView+Modifier.swift */, + 7AAAFADD2C4D23620050410D /* ACImageSliderModel.swift */, + ); + path = ACImageSlider; + sourceTree = ""; + }; 7AB587302C42D35900FA7B66 /* StorageService */ = { isa = PBXGroup; children = ( @@ -870,7 +904,11 @@ 7AFBE8C52C30812E003C491D /* SwiftUI */ = { isa = PBXGroup; children = ( + 7AAAFAD12C4D0FB00050410D /* ACImageSlider */, 7AFBE8C62C30814E003C491D /* ACProgressHud */, + 7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */, + 7A961C6D2C4C3C9E00CE2211 /* LinkRowView.swift */, + 7AAAFAD92C4D1AFE0050410D /* Zoomable.swift */, ); path = SwiftUI; sourceTree = ""; @@ -880,7 +918,6 @@ children = ( 7AFBE8BF2C3024E5003C491D /* ACHud.swift */, 7AFBE8C32C302561003C491D /* ACHudContainer.swift */, - 7AFBE8C72C30816E003C491D /* ACProgressHud.swift */, 7AFBE8C92C3081C7003C491D /* ACProgressHud+Modifiers.swift */, 7AFBE8CB2C3085C6003C491D /* ACProgressView.swift */, 7AFBE8CD2C308B53003C491D /* ACMessageView.swift */, @@ -1103,24 +1140,24 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7A961C6C2C4C3C8600CE2211 /* TextRowView.swift in Sources */, 7AEFC3BE2529D3CC00BADFB2 /* ConfigurableCell.swift in Sources */, 7A7158092C44087E00852088 /* OsagoCoordinator.swift in Sources */, 7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */, 7A11470123FDE7E500B424AF /* AppDelegate.swift in Sources */, 7A813DC9250B5C9700CC93B9 /* LocationRow.swift in Sources */, 7A3399AB299063370087DF98 /* SearchControllerExt.swift in Sources */, - 7A2DE69B25869ABD00A113FC /* AdsController.swift in Sources */, 7A14416E2C297F7C00E79018 /* Coordinator.swift in Sources */, 7A6DD90824329144009DE740 /* CenterTextLayer.swift in Sources */, 7A99406426E4BFAE002E9CB6 /* VehicleNoteCell.swift in Sources */, 7A8AB76B25A1D95500ECF2C1 /* SourceStatusRow.swift in Sources */, - 7AFBE8C82C30816E003C491D /* ACProgressHud.swift in Sources */, 7AC3554C29696A1C00889457 /* MainTabController.swift in Sources */, 7A813DC32508EE4F00CC93B9 /* EventCell.swift in Sources */, 7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */, 7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */, 7A3F07AD2436350B00E59687 /* SearchController.swift in Sources */, 7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */, + 7AAAFADA2C4D1AFE0050410D /* Zoomable.swift in Sources */, 7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */, 7A761C0B267E8FF90005F28F /* Error.swift in Sources */, 7AC3555029696D5A00889457 /* NewNumberController.swift in Sources */, @@ -1131,16 +1168,20 @@ 7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */, 7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */, 7AE26A3324EEF9EC00625033 /* UIViewControllerExt.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 */, + 7A7158122C444A6400852088 /* AdsViewModel.swift in Sources */, 7A659B5B24A3768A0043A0F2 /* Substrings.swift in Sources */, 7AEFE728240455E200910EB7 /* SettingsController.swift in Sources */, + 7A71580E2C4445A200852088 /* AdsCoordinator.swift in Sources */, 7AFBE8CA2C3081C7003C491D /* ACProgressHud+Modifiers.swift in Sources */, 7A27ADF7249FEF690035F39E /* Recorder.swift in Sources */, 7A7158072C44085600852088 /* OsagoScreen.swift in Sources */, + 7AAAFAD32C4D0FD00050410D /* ACImageSliderView.swift in Sources */, 7A3F07AB24360DC800E59687 /* Dated.swift in Sources */, 7A33381124990DAE00D878F1 /* FiltersController.swift in Sources */, 7AC76D7B270083AE0084DB27 /* TextView.swift in Sources */, @@ -1166,17 +1207,20 @@ 7A14416C2C297F2100E79018 /* NotesCoordinator.swift in Sources */, 7A813DCB250B5DC900CC93B9 /* LocationPickerController.swift in Sources */, 7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */, + 7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */, 7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */, 7AC3555229696E3F00889457 /* UIView+layout.swift in Sources */, 7AC355592969746600889457 /* UIControl.swift in Sources */, 7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */, 7AC35554296973E100889457 /* ACButton.swift in Sources */, + 7AAAFADC2C4D1E130050410D /* ACImageSliderView+Modifier.swift in Sources */, 7A8A220B248D67B60073DFD9 /* VehicleReportImage.swift in Sources */, 7AFBE8C42C302561003C491D /* ACHudContainer.swift in Sources */, 7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */, 7A7158002C43EA6900852088 /* OwnersScreen.swift in Sources */, 7A1441702C2998B200E79018 /* Formatters.swift in Sources */, 7ADF6C95250D037700F237B2 /* ShowEventController.swift in Sources */, + 7A71580C2C44453200852088 /* AdsScreen.swift in Sources */, 7A91894F29A2BD8700519C74 /* GestureRecognizers.swift in Sources */, 7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */, 7AFBE8CC2C3085C6003C491D /* ACProgressView.swift in Sources */, @@ -1457,7 +1501,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 125; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = AutoCat; @@ -1483,7 +1527,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 125; + CURRENT_PROJECT_VERSION = 127; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = AutoCat; diff --git a/AutoCat/Controllers/AdsController.swift b/AutoCat/Controllers/AdsController.swift deleted file mode 100644 index a5c90db..0000000 --- a/AutoCat/Controllers/AdsController.swift +++ /dev/null @@ -1,114 +0,0 @@ -import UIKit -import Eureka -import Kingfisher -import SafariServices -import AutoCatCore - -class AdsController: FormViewController, MediaBrowserViewControllerDataSource { - - var ads: [VehicleAdDto] = [] - private var currentAd: VehicleAdDto? - - override func viewDidLoad() { - super.viewDidLoad() - - self.title = NSLocalizedString("Ads", comment: "") - - let formatter = DateFormatter() - formatter.dateStyle = .long - formatter.timeStyle = .none - - for ad in ads { - let date = Date(timeIntervalSince1970: ad.date) - let section = Section(formatter.string(from: date)) - - self.form +++ section - - if let price = ad.price { - section <<< LabelRow() { row in - row.title = NSLocalizedString("Price", comment: "") - row.value = price - } - } - - if let mileage = ad.mileage, mileage != "0" { - section <<< LabelRow() { row in - row.title = NSLocalizedString("Mileage", comment: "") - row.value = mileage - } - } - - if let region = ad.region { - section <<< LabelRow() { row in - row.title = NSLocalizedString("Region", comment: "") - row.value = region - } - } - - if let city = ad.city { - section <<< LabelRow() { row in - row.title = NSLocalizedString("City", comment: "") - row.value = city - } - } - -// if let description = ad.adDescription, !description.isEmpty { -// section <<< MultilineLabelRow() { row in -// row.title = NSLocalizedString("Description", comment: "") -// row.value = description -// } -// } -// -// if let urlStr = ad.url, let url = URL(string: urlStr) { -// section <<< MultilineLinkRow() { row in -// row.title = NSLocalizedString("Link", comment: "") -// row.value = urlStr -// } -// .onCellSelection { _, _ in -// let safari = SFSafariViewController(url: url) -// self.present(safari, animated: true) -// } -// } - - if !ad.photos.isEmpty { - section <<< ImageGridRow() { row in - row.value = ad.photos - } - .onDidSelected { index in - self.currentAd = ad - let mediaBrowser = MediaBrowserViewController(index: index, dataSource: self, delegate: nil) - mediaBrowser.shouldShowTitle = false - self.present(mediaBrowser, animated: true, completion: nil) - } - } - } - } - - // MARK: - MediaBrowserViewControllerDataSource - - func numberOfItems(in mediaBrowser: MediaBrowserViewController) -> Int { - guard let images = self.currentAd?.photos else { return 0 } - return images.count - } - - func mediaBrowser(_ mediaBrowser: MediaBrowserViewController, imageAt index: Int, completion: @escaping MediaBrowserViewControllerDataSource.CompletionBlock) { - guard let images = self.currentAd?.photos, let url = URL(string: images[index]) else { - completion(index, nil, ZoomScale.default, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Image not found"])) - return - } - - KingfisherManager.shared.retrieveImage(with: url) { result in - - Task { @MainActor in - switch result { - case .success(let res): - completion(index, res.image, ZoomScale.default, nil) - break - case .failure(let error): - completion(index, nil, ZoomScale.default, error) - break - } - } - } - } -} diff --git a/AutoCat/Controllers/ReportController.swift b/AutoCat/Controllers/ReportController.swift index 50c1050..d2e066f 100644 --- a/AutoCat/Controllers/ReportController.swift +++ b/AutoCat/Controllers/ReportController.swift @@ -129,9 +129,14 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource } .cellUpdate { cell, _ in cell.accessoryType = .disclosureIndicator } .onCellSelection { _, row in - let controller = AdsController() - controller.ads = self.vehicle?.ads ?? [] - self.navigationController?.pushViewController(controller, animated: true) +// let controller = AdsController() +// controller.ads = self.vehicle?.ads ?? [] +// self.navigationController?.pushViewController(controller, animated: true) + + if let ads = self.vehicle?.ads, let navController = self.navigationController { + let coordinator = AdsCoordinator(navController: navController, ads: ads) + Task { try await coordinator.start() } + } } <<< LabelRow("Notes") { row in diff --git a/AutoCat/Screens/AdsScreen/AdsCoordinator.swift b/AutoCat/Screens/AdsScreen/AdsCoordinator.swift new file mode 100644 index 0000000..f7edf5e --- /dev/null +++ b/AutoCat/Screens/AdsScreen/AdsCoordinator.swift @@ -0,0 +1,30 @@ +// +// AdsCoordinator.swift +// AutoCat +// +// Created by Selim Mustafaev on 14.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +import UIKit +import SwiftUI +import AutoCatCore + +class AdsCoordinator: Coordinator { + + let viewController: UINavigationController? + let ads: [VehicleAdDto] + + init(navController: UINavigationController, ads: [VehicleAdDto]) { + + self.viewController = navController + self.ads = ads + } + + func start() async throws { + + let viewModel = await AdsViewModel(ads: ads) + let controller = await UIHostingController(rootView: AdsScreen(viewModel: viewModel)) + await viewController?.pushViewController(controller, animated: true) + } +} diff --git a/AutoCat/Screens/AdsScreen/AdsScreen.swift b/AutoCat/Screens/AdsScreen/AdsScreen.swift new file mode 100644 index 0000000..3369046 --- /dev/null +++ b/AutoCat/Screens/AdsScreen/AdsScreen.swift @@ -0,0 +1,74 @@ +// +// AdsScreen.swift +// AutoCat +// +// Created by Selim Mustafaev on 14.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +struct AdsScreen: View { + + @StateObject var viewModel: AdsViewModel + + @State var galleryModel: ACImageSliderModel? + + var body: some View { + List(viewModel.ads) { ad in + Section(viewModel.dateStringFrom(ad.date)) { + if let price = ad.price { + TextRowView(title: "Price", value: price) + } + if let mileage = ad.mileage { + TextRowView(title: "Mileage", value: mileage) + } + if let region = ad.region { + TextRowView(title: "Region", value: region) + } + if let city = ad.city { + TextRowView(title: "City", value: city) + } + if let description = ad.adDescription, !description.isEmpty { + TextRowView(title: "Description", value: description) + } + if let link = ad.url { + LinkRowView(title: "Link", value: link) + } + + imageGrid(links: ad.photos) + } + } + .navigationTitle("Ads") + .imageSlider($galleryModel) + } + + func imageGrid(links: [String]) -> some View { + let urls = links.compactMap(URL.init(string:)) + + let columns = [ + GridItem(spacing: 8), + GridItem(spacing: 8), + GridItem(spacing: 8) + ] + + return LazyVGrid(columns: columns, spacing: 8) { + ForEach(urls, id: \.self) { url in + AsyncImage(url: url) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + } placeholder: { + ProgressView() + } + .onTapGesture { + galleryModel = ACImageSliderModel(urls: urls, selected: url) + } + } + } + } +} + +#Preview { + AdsScreen(viewModel: AdsViewModel(ads: [])) +} diff --git a/AutoCat/Screens/AdsScreen/AdsViewModel.swift b/AutoCat/Screens/AdsScreen/AdsViewModel.swift new file mode 100644 index 0000000..c5c5638 --- /dev/null +++ b/AutoCat/Screens/AdsScreen/AdsViewModel.swift @@ -0,0 +1,32 @@ +// +// AdsViewModel.swift +// AutoCat +// +// Created by Selim Mustafaev on 14.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +import Foundation +import AutoCatCore + +@MainActor +class AdsViewModel: ObservableObject { + + let ads: [VehicleAdDto] + + let formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + return formatter + }() + + init(ads: [VehicleAdDto]) { + self.ads = ads + } + + func dateStringFrom(_ timestamp: TimeInterval) -> String { + let date = Date(timeIntervalSince1970: timestamp) + return formatter.string(from: date) + } +} diff --git a/AutoCat/SwiftUI/ACImageSlider/ACImageSliderModel.swift b/AutoCat/SwiftUI/ACImageSlider/ACImageSliderModel.swift new file mode 100644 index 0000000..6177c45 --- /dev/null +++ b/AutoCat/SwiftUI/ACImageSlider/ACImageSliderModel.swift @@ -0,0 +1,20 @@ +// +// ACImageSliderModel.swift +// AutoCat +// +// Created by Selim Mustafaev on 21.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +import Foundation + +struct ACImageSliderModel { + + let urls: [URL] + let selected: URL +} + +extension ACImageSliderModel: Identifiable { + + var id: URL { selected } +} diff --git a/AutoCat/SwiftUI/ACImageSlider/ACImageSliderView+Modifier.swift b/AutoCat/SwiftUI/ACImageSlider/ACImageSliderView+Modifier.swift new file mode 100644 index 0000000..7d9b384 --- /dev/null +++ b/AutoCat/SwiftUI/ACImageSlider/ACImageSliderView+Modifier.swift @@ -0,0 +1,32 @@ +// +// ACImageSliderView+Modifier.swift +// AutoCat +// +// Created by Selim Mustafaev on 21.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +struct ACImageSliderViewModifier: ViewModifier { + + @Binding var model: ACImageSliderModel? + + func body(content: Content) -> some View { + content + .fullScreenCover(item: $model) { model in + ACImageSliderView(urls: model.urls, selected: model.selected) + } +// .transaction { transaction in +// transaction.disablesAnimations = true +// } + } +} + +extension View { + + func imageSlider(_ model: Binding) -> some View { + + modifier(ACImageSliderViewModifier(model: model)) + } +} diff --git a/AutoCat/SwiftUI/ACImageSlider/ACImageSliderView.swift b/AutoCat/SwiftUI/ACImageSlider/ACImageSliderView.swift new file mode 100644 index 0000000..25533fb --- /dev/null +++ b/AutoCat/SwiftUI/ACImageSlider/ACImageSliderView.swift @@ -0,0 +1,71 @@ +// +// ACImageSliderView.swift +// AutoCat +// +// Created by Selim Mustafaev on 21.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +struct ACImageSliderView: View { + + @Environment(\.dismiss) var dismiss + + let urls: [URL] + let initialUrl: URL + + @State var selection: URL? + + init(urls: [URL], selected: URL) { + self.urls = urls + self.initialUrl = selected + } + + var body: some View { + ZStack(alignment: .topTrailing) { + ZStack { + TabView(selection: $selection) { + ForEach(urls, id: \.self) { url in + AsyncImage(url: url) { image in + image + .resizable() + .scaledToFit() + .zoomable() + .ignoresSafeArea() + } placeholder: { + ProgressView() + } + .tag(url) + } + } + .tabViewStyle(.page) + .indexViewStyle(.page(backgroundDisplayMode: .always)) + .onAppear { + selection = initialUrl + } + } + .ignoresSafeArea() + + Button { + dismiss() + } label: { + Image(systemName: "xmark.circle.fill") + .resizable() + .tint(.gray) + .frame(width: 32, height: 32) + .padding() + } + } + } +} + +#Preview { + + let urls = [ + "https://s.nomerogram.ru/photo/anc_MawzZ3O_hl7no_eB9gbm0-WKIhAuv-DnTy9ns6ew2phAq9sAY5UP6YIUjNHhQBgSUYaZGDZk47hEbqo4iAM9NfR8.jpg", "https://s.nomerogram.ru/photo/sxIsdctpGDKZBe-MJRBSy2uXMEln04vk_hK12fe04IkOctMRYhnLlO8AjKNzA8FBEf4of-p0ZYn_q4wkBcabiPGFCXPd.jpg", "https://s.nomerogram.ru/photo/eO8K4Q8QkRvT-b9EYXqbAlUNlzLnv6h0l4YfqWcL6wvVpwnC9lKQmHyzwncTh5EXCTXtXKaE0VoiICLDaAZy54HChMbe.jpg", "https://s.nomerogram.ru/photo/rAiWJpaO1jT_cAucLJg6APiR10-YVCWSQQXARLurj_RHuce2A2TNXY8_xt0e6dRBdRlwJEaTS_O2V4KPxWvznHhVTsGx.jpg", "https://s.nomerogram.ru/photo/uD35kAplNFZzNZ8HFXmTI6WWrZs019fcqcAE-WOz1eC954WHD6x_8x2YmoUk3GcyzDxk3XpFkK87wYF8X7ti5quOkt1G.jpg" + ] + .compactMap(URL.init(string:)) + + ACImageSliderView(urls: urls, selected: urls[1]) +} diff --git a/AutoCat/SwiftUI/ACProgressHud/ACProgressHud.swift b/AutoCat/SwiftUI/ACProgressHud/ACProgressHud.swift deleted file mode 100644 index 3bf0b52..0000000 --- a/AutoCat/SwiftUI/ACProgressHud/ACProgressHud.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// ACProgressHud.swift -// AutoCat -// -// Created by Selim Mustafaev on 29.06.2024. -// Copyright © 2024 Selim Mustafaev. All rights reserved. -// - -import SwiftUI -import AutoCatCore - -struct ACProgressHud: View { - - @State var toast: ACHud? - - var body: some View { - VStack(spacing: 16) { - Rectangle() - .fill(.orange) - .frame(width: 200, height: 200) - Button("Show progress") { - toast = .progress - } - Button("Show error") { - toast = .error(ApiError.generic) - } - Rectangle() - .fill(.orange) - .frame(width: 200, height: 200) - } - .hud($toast) - } -} - -#Preview { - ACProgressHud() -} diff --git a/AutoCat/SwiftUI/LinkRowView.swift b/AutoCat/SwiftUI/LinkRowView.swift new file mode 100644 index 0000000..7607a7f --- /dev/null +++ b/AutoCat/SwiftUI/LinkRowView.swift @@ -0,0 +1,60 @@ +// +// LinkRowView.swift +// AutoCat +// +// Created by Selim Mustafaev on 20.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +struct LinkRowView: View { + + let title: String + let value: String? + + var body: some View { + if #available(iOS 16.0, *) { + ViewThatFits(in: .horizontal) { + SimpleLinkRowView(title: title, value: value) + VStack(alignment: .leading, spacing: 8) { + Text(title) + HStack { + Spacer() + if let url = URL(string: value ?? "") { + Link(url.absoluteString, destination: url) + .multilineTextAlignment(.trailing) + .padding(.leading) + } + } + } + } + } else { + SimpleLinkRowView(title: title, value: value) + } + } +} + +struct SimpleLinkRowView: View { + + let title: String + let value: String? + + var body: some View { + HStack(spacing: 0) { + Text(title) + Spacer() + + if let url = URL(string: value ?? "") { + Link(url.absoluteString, destination: url) + } + } + } +} + +#Preview { + List { + LinkRowView(title: "Title", value: "https://google.com") + LinkRowView(title: "Title", value: "https://developer.apple.com/documentation/swiftui/viewthatfits") + } +} diff --git a/AutoCat/SwiftUI/TextRowView.swift b/AutoCat/SwiftUI/TextRowView.swift new file mode 100644 index 0000000..ba611da --- /dev/null +++ b/AutoCat/SwiftUI/TextRowView.swift @@ -0,0 +1,58 @@ +// +// TextRowView.swift +// AutoCat +// +// Created by Selim Mustafaev on 20.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +struct TextRowView: View { + + let title: String + let value: String? + + var body: some View { + if #available(iOS 16.0, *) { + ViewThatFits(in: .horizontal) { + SimpleTextRowView(title: title, value: value) + VStack(alignment: .leading, spacing: 8) { + Text(title) + HStack { + Spacer() + Text(value ?? "") + .foregroundStyle(.secondary) + .multilineTextAlignment(.trailing) + .padding(.leading) + } + } + } + } else { + SimpleTextRowView(title: title, value: value) + } + } +} + +struct SimpleTextRowView: View { + + let title: String + let value: String? + + var body: some View { + HStack(alignment: .firstTextBaseline, spacing: 0) { + Text(title) + Spacer() + Text(value ?? "") + .foregroundStyle(.secondary) + } + } +} + +#Preview { + List { + TextRowView(title: "Title", value: "Value") + TextRowView(title: "Title", value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas cursus posuere varius. Maecenas diam neque, lacinia molestie tristique eget, faucibus egestas nibh.") + TextRowView(title: "Some longer title", value: "Value with long long text") + } +} diff --git a/AutoCat/SwiftUI/Zoomable.swift b/AutoCat/SwiftUI/Zoomable.swift new file mode 100644 index 0000000..3e11484 --- /dev/null +++ b/AutoCat/SwiftUI/Zoomable.swift @@ -0,0 +1,217 @@ +// +// Zoomable.swift +// AutoCat +// +// Created by Selim Mustafaev on 21.07.2024. +// Copyright © 2024 Selim Mustafaev. All rights reserved. +// + +#if os(iOS) + +import SwiftUI + +struct ZoomableModifier: ViewModifier { + let minZoomScale: CGFloat + let doubleTapZoomScale: CGFloat + + @State private var lastTransform: CGAffineTransform = .identity + @State private var transform: CGAffineTransform = .identity + @State private var contentSize: CGSize = .zero + + func body(content: Content) -> some View { + content + .background(alignment: .topLeading) { + GeometryReader { proxy in + Color.clear + .onAppear { + contentSize = proxy.size + } + } + } + .animatableTransformEffect(transform) + .gesture(dragGesture, including: transform == .identity ? .none : .all) + .modify { view in + if #available(iOS 17.0, *) { + view.gesture(magnificationGesture) + } else { + view.gesture(oldMagnificationGesture) + } + } + //.gesture(doubleTapGesture) + } + + @available(iOS, introduced: 15.0, deprecated: 17.0) + private var oldMagnificationGesture: some Gesture { + MagnificationGesture() + .onChanged { value in + let zoomFactor = 0.5 + let scale = value * zoomFactor + transform = lastTransform.scaledBy(x: scale, y: scale) + } + .onEnded { _ in + onEndGesture() + } + } + + @available(iOS 17.0, *) + private var magnificationGesture: some Gesture { + MagnifyGesture(minimumScaleDelta: 0) + .onChanged { value in + let newTransform = CGAffineTransform.anchoredScale( + scale: value.magnification, + anchor: value.startAnchor.scaledBy(contentSize) + ) + + withAnimation(.interactiveSpring) { + transform = lastTransform.concatenating(newTransform) + } + } + .onEnded { _ in + onEndGesture() + } + } + +// private var doubleTapGesture: some Gesture { +// SpatialTapGesture(count: 2) +// .onEnded { value in +// let newTransform: CGAffineTransform = +// if transform.isIdentity { +// .anchoredScale(scale: doubleTapZoomScale, anchor: value.location) +// } else { +// .identity +// } +// +// withAnimation(.linear(duration: 0.15)) { +// transform = newTransform +// lastTransform = newTransform +// } +// } +// } + + private var dragGesture: some Gesture { + DragGesture() + .onChanged { value in + withAnimation(.interactiveSpring) { + transform = lastTransform.translatedBy( + x: value.translation.width / transform.scaleX, + y: value.translation.height / transform.scaleY + ) + } + } + .onEnded { _ in + onEndGesture() + } + } + + private func onEndGesture() { + let newTransform = limitTransform(transform) + + withAnimation(.snappy(duration: 0.1)) { + transform = newTransform + lastTransform = newTransform + } + } + + private func limitTransform(_ transform: CGAffineTransform) -> CGAffineTransform { + let scaleX = transform.scaleX + let scaleY = transform.scaleY + + if scaleX < minZoomScale + || scaleY < minZoomScale + { + return .identity + } + + let maxX = contentSize.width * (scaleX - 1) + let maxY = contentSize.height * (scaleY - 1) + + if transform.tx > 0 + || transform.tx < -maxX + || transform.ty > 0 + || transform.ty < -maxY + { + let tx = min(max(transform.tx, -maxX), 0) + let ty = min(max(transform.ty, -maxY), 0) + var transform = transform + transform.tx = tx + transform.ty = ty + return transform + } + + return transform + } +} + +public extension View { + @ViewBuilder + func zoomable( + minZoomScale: CGFloat = 1, + doubleTapZoomScale: CGFloat = 3 + ) -> some View { + modifier(ZoomableModifier( + minZoomScale: minZoomScale, + doubleTapZoomScale: doubleTapZoomScale + )) + } + + @ViewBuilder + func zoomable( + minZoomScale: CGFloat = 1, + doubleTapZoomScale: CGFloat = 3, + outOfBoundsColor: Color = .clear + ) -> some View { + GeometryReader { proxy in + ZStack { + outOfBoundsColor + self.zoomable( + minZoomScale: minZoomScale, + doubleTapZoomScale: doubleTapZoomScale + ) + } + } + } +} + +private extension View { + @ViewBuilder + func modify(@ViewBuilder _ fn: (Self) -> some View) -> some View { + fn(self) + } + + @ViewBuilder + func animatableTransformEffect(_ transform: CGAffineTransform) -> some View { + scaleEffect( + x: transform.scaleX, + y: transform.scaleY, + anchor: .zero + ) + .offset(x: transform.tx, y: transform.ty) + } +} + +private extension UnitPoint { + func scaledBy(_ size: CGSize) -> CGPoint { + .init( + x: x * size.width, + y: y * size.height + ) + } +} + +private extension CGAffineTransform { + static func anchoredScale(scale: CGFloat, anchor: CGPoint) -> CGAffineTransform { + CGAffineTransform(translationX: anchor.x, y: anchor.y) + .scaledBy(x: scale, y: scale) + .translatedBy(x: -anchor.x, y: -anchor.y) + } + + var scaleX: CGFloat { + sqrt(a * a + c * c) + } + + var scaleY: CGFloat { + sqrt(b * b + d * d) + } +} + +#endif diff --git a/AutoCatCore/Models/DTO/VehicleAdDto.swift b/AutoCatCore/Models/DTO/VehicleAdDto.swift index b7b655a..9b55136 100644 --- a/AutoCatCore/Models/DTO/VehicleAdDto.swift +++ b/AutoCatCore/Models/DTO/VehicleAdDto.swift @@ -8,7 +8,7 @@ import Foundation -public struct VehicleAdDto: Decodable, Sendable { +public struct VehicleAdDto: Decodable, Sendable, Identifiable { public var id: Int = 0 public var url: String? diff --git a/AutoCatCore/Services/StorageService/StorageService+Notes.swift b/AutoCatCore/Services/StorageService/StorageService+Notes.swift index 13baf7f..8f8bdbc 100644 --- a/AutoCatCore/Services/StorageService/StorageService+Notes.swift +++ b/AutoCatCore/Services/StorageService/StorageService+Notes.swift @@ -18,7 +18,7 @@ extension StorageService { let note = VehicleNote(text: text) - try realm.write { + try await realm.asyncWrite { vehicle.notes.append(note) vehicle.updatedDate = Date().timeIntervalSince1970 } @@ -36,7 +36,7 @@ extension StorageService { throw StorageError.noteNotFound } - try realm.write { + try await realm.asyncWrite { realm.delete(realmNote) vehicle.updatedDate = Date().timeIntervalSince1970 } @@ -54,7 +54,7 @@ extension StorageService { throw StorageError.noteNotFound } - try realm.write { + try await realm.asyncWrite { note.text = text vehicle.updatedDate = Date().timeIntervalSince1970 diff --git a/AutoCatCore/Services/StorageService/StorageService.swift b/AutoCatCore/Services/StorageService/StorageService.swift index 4e5e13e..afd3676 100644 --- a/AutoCatCore/Services/StorageService/StorageService.swift +++ b/AutoCatCore/Services/StorageService/StorageService.swift @@ -51,7 +51,7 @@ public actor StorageService: StorageServiceProtocol { return } - try realm.write { + try await realm.asyncWrite { realm.add(Vehicle(dto: dto), update: .all) } }