SwiftUI version of google oauth signin controller

This commit is contained in:
Selim Mustafaev 2025-04-14 23:00:32 +03:00
parent 7831b7e615
commit 41578579ef
14 changed files with 266 additions and 175 deletions

View File

@ -136,7 +136,6 @@
7A9519842D80B72B00E69883 /* RecordsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9519832D80B72B00E69883 /* RecordsCoordinator.swift */; }; 7A9519842D80B72B00E69883 /* RecordsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9519832D80B72B00E69883 /* RecordsCoordinator.swift */; };
7A961C6C2C4C3C8600CE2211 /* TextRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */; }; 7A961C6C2C4C3C8600CE2211 /* TextRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */; };
7A961C6E2C4C3C9E00CE2211 /* LinkRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A961C6D2C4C3C9E00CE2211 /* LinkRowView.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 */; }; 7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; };
7AA514E02D0B75B3001CAC50 /* StorageService+Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA514DF2D0B75B3001CAC50 /* StorageService+Events.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 */; }; 7AA515D02D9ABCC800EB3418 /* RecordPlayerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA515CF2D9ABCC800EB3418 /* RecordPlayerService.swift */; };
@ -194,8 +193,12 @@
7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */; }; 7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */; };
7ADF6C99250F872C00F237B2 /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */; }; 7ADF6C99250F872C00F237B2 /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */; };
7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.swift */; }; 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.swift */; };
7ADFC9572DAD0288001A43E3 /* GoogleAuthScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADFC9562DAD0288001A43E3 /* GoogleAuthScreen.swift */; };
7ADFC9592DAD1C3D001A43E3 /* GoogleAuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADFC9582DAD1C3D001A43E3 /* GoogleAuthViewModel.swift */; };
7ADFC95B2DAD1F45001A43E3 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADFC95A2DAD1F45001A43E3 /* WebView.swift */; };
7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE24C5E251F1B4E00758E39 /* Buttons.swift */; }; 7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE24C5E251F1B4E00758E39 /* Buttons.swift */; };
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */; }; 7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */; };
7AEAA2A12DAD9C00009954F0 /* TokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEAA2A02DAD9C00009954F0 /* TokenResponse.swift */; };
7AF231932DA1C28100AE5EB3 /* AuthScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */; }; 7AF231932DA1C28100AE5EB3 /* AuthScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */; };
7AF231952DA1C29300AE5EB3 /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */; }; 7AF231952DA1C29300AE5EB3 /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */; };
7AF231972DA1C30000AE5EB3 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */; }; 7AF231972DA1C30000AE5EB3 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */; };
@ -422,7 +425,6 @@
7A9519832D80B72B00E69883 /* RecordsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsCoordinator.swift; sourceTree = "<group>"; }; 7A9519832D80B72B00E69883 /* RecordsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsCoordinator.swift; sourceTree = "<group>"; };
7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRowView.swift; sourceTree = "<group>"; }; 7A961C6B2C4C3C8600CE2211 /* TextRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRowView.swift; sourceTree = "<group>"; };
7A961C6D2C4C3C9E00CE2211 /* LinkRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkRowView.swift; sourceTree = "<group>"; }; 7A961C6D2C4C3C9E00CE2211 /* LinkRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkRowView.swift; sourceTree = "<group>"; };
7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSignInController.swift; sourceTree = "<group>"; };
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; }; 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 = "<group>"; }; 7A96AE30246B2FE400297C33 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
7A96AE32246C095700297C33 /* Base64FS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64FS.swift; sourceTree = "<group>"; }; 7A96AE32246C095700297C33 /* Base64FS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64FS.swift; sourceTree = "<group>"; };
@ -478,9 +480,13 @@
7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNKeyboard.swift; sourceTree = "<group>"; }; 7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNKeyboard.swift; sourceTree = "<group>"; };
7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = RoadNumbers.otf; sourceTree = "<group>"; }; 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = RoadNumbers.otf; sourceTree = "<group>"; };
7ADF6CA02512244400F237B2 /* MapExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapExt.swift; sourceTree = "<group>"; }; 7ADF6CA02512244400F237B2 /* MapExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapExt.swift; sourceTree = "<group>"; };
7ADFC9562DAD0288001A43E3 /* GoogleAuthScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthScreen.swift; sourceTree = "<group>"; };
7ADFC9582DAD1C3D001A43E3 /* GoogleAuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthViewModel.swift; sourceTree = "<group>"; };
7ADFC95A2DAD1F45001A43E3 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = "<group>"; };
7AE24C5E251F1B4E00758E39 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; }; 7AE24C5E251F1B4E00758E39 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExt.swift; sourceTree = "<group>"; }; 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExt.swift; sourceTree = "<group>"; };
7AE8424D26109F78002F6B31 /* Exportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exportable.swift; sourceTree = "<group>"; }; 7AE8424D26109F78002F6B31 /* Exportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exportable.swift; sourceTree = "<group>"; };
7AEAA2A02DAD9C00009954F0 /* TokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenResponse.swift; sourceTree = "<group>"; };
7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthScreen.swift; sourceTree = "<group>"; }; 7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthScreen.swift; sourceTree = "<group>"; };
7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.swift; sourceTree = "<group>"; }; 7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.swift; sourceTree = "<group>"; };
7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = "<group>"; }; 7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = "<group>"; };
@ -655,7 +661,6 @@
7A11471423FDEAF800B424AF /* Controllers */ = { 7A11471423FDEAF800B424AF /* Controllers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */,
7A11471523FDEB2A00B424AF /* MainSplitController.swift */, 7A11471523FDEB2A00B424AF /* MainSplitController.swift */,
7AC3554B29696A1C00889457 /* MainTabController.swift */, 7AC3554B29696A1C00889457 /* MainTabController.swift */,
7AC3554D29696C4500889457 /* DummyNewController.swift */, 7AC3554D29696C4500889457 /* DummyNewController.swift */,
@ -692,7 +697,6 @@
7A333813249A532400D878F1 /* Filter.swift */, 7A333813249A532400D878F1 /* Filter.swift */,
6841A913FABBB0AB20DEF4FC /* PagedResponse.swift */, 6841A913FABBB0AB20DEF4FC /* PagedResponse.swift */,
7A6DD90D24337930009DE740 /* PlateNumber.swift */, 7A6DD90D24337930009DE740 /* PlateNumber.swift */,
7A11474823FF2B2D00B424AF /* Response.swift */,
7A11474623FF2AA500B424AF /* User.swift */, 7A11474623FF2AA500B424AF /* User.swift */,
7AB562B9249C9E9B00473D53 /* VehicleRegion.swift */, 7AB562B9249C9E9B00473D53 /* VehicleRegion.swift */,
7AB4E4372D3D0C5C0006D052 /* VehiclesArchive.swift */, 7AB4E4372D3D0C5C0006D052 /* VehiclesArchive.swift */,
@ -725,6 +729,7 @@
7A1441632C297E9800E79018 /* Screens */ = { 7A1441632C297E9800E79018 /* Screens */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7ADFC9552DAD026C001A43E3 /* GoogleAuthScreen */,
7A386A3E2DABDBFF0051676A /* MapScreen */, 7A386A3E2DABDBFF0051676A /* MapScreen */,
7AF231912DA1C26C00AE5EB3 /* AuthScreen */, 7AF231912DA1C26C00AE5EB3 /* AuthScreen */,
7A95197E2D80B69800E69883 /* RecordsScreen */, 7A95197E2D80B69800E69883 /* RecordsScreen */,
@ -1060,6 +1065,8 @@
7AB587402C42FFE200FA7B66 /* ApiServiceProtocol.swift */, 7AB587402C42FFE200FA7B66 /* ApiServiceProtocol.swift */,
7A11474323FF06CA00B424AF /* ApiService.swift */, 7A11474323FF06CA00B424AF /* ApiService.swift */,
7A599C352C18AC7F00D47C18 /* ApiError.swift */, 7A599C352C18AC7F00D47C18 /* ApiError.swift */,
7A11474823FF2B2D00B424AF /* Response.swift */,
7AEAA2A02DAD9C00009954F0 /* TokenResponse.swift */,
); );
path = ApiService; path = ApiService;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1127,6 +1134,16 @@
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7ADFC9552DAD026C001A43E3 /* GoogleAuthScreen */ = {
isa = PBXGroup;
children = (
7ADFC9562DAD0288001A43E3 /* GoogleAuthScreen.swift */,
7ADFC9582DAD1C3D001A43E3 /* GoogleAuthViewModel.swift */,
7ADFC95A2DAD1F45001A43E3 /* WebView.swift */,
);
path = GoogleAuthScreen;
sourceTree = "<group>";
};
7AF231912DA1C26C00AE5EB3 /* AuthScreen */ = { 7AF231912DA1C26C00AE5EB3 /* AuthScreen */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1446,6 +1463,7 @@
7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */, 7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */,
7A9519842D80B72B00E69883 /* RecordsCoordinator.swift in Sources */, 7A9519842D80B72B00E69883 /* RecordsCoordinator.swift in Sources */,
7AAAFADA2C4D1AFE0050410D /* Zoomable.swift in Sources */, 7AAAFADA2C4D1AFE0050410D /* Zoomable.swift in Sources */,
7ADFC9572DAD0288001A43E3 /* GoogleAuthScreen.swift in Sources */,
7AC8B2762D6A01C700190706 /* UISearchTextField+Dumb.swift in Sources */, 7AC8B2762D6A01C700190706 /* UISearchTextField+Dumb.swift in Sources */,
7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */, 7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */,
7A2E11292CCE395300E5CA17 /* OptionalDatePicker.swift in Sources */, 7A2E11292CCE395300E5CA17 /* OptionalDatePicker.swift in Sources */,
@ -1483,6 +1501,7 @@
7A7158072C44085600852088 /* OsagoScreen.swift in Sources */, 7A7158072C44085600852088 /* OsagoScreen.swift in Sources */,
7ABD1B492D044A4700B43213 /* GalleryViewModel.swift in Sources */, 7ABD1B492D044A4700B43213 /* GalleryViewModel.swift in Sources */,
7A386A442DABDC360051676A /* MapViewModel.swift in Sources */, 7A386A442DABDC360051676A /* MapViewModel.swift in Sources */,
7ADFC9592DAD1C3D001A43E3 /* GoogleAuthViewModel.swift in Sources */,
7AAAFAD32C4D0FD00050410D /* ACImageSliderView.swift in Sources */, 7AAAFAD32C4D0FD00050410D /* ACImageSliderView.swift in Sources */,
7A912F372D381B7400002938 /* LicensePlateView.swift in Sources */, 7A912F372D381B7400002938 /* LicensePlateView.swift in Sources */,
7A3F07AB24360DC800E59687 /* Dated.swift in Sources */, 7A3F07AB24360DC800E59687 /* Dated.swift in Sources */,
@ -1494,7 +1513,6 @@
7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */, 7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */,
7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */, 7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */,
7AB9FE2A2D08CF35005DE374 /* EventsScreenMode.swift in Sources */, 7AB9FE2A2D08CF35005DE374 /* EventsScreenMode.swift in Sources */,
7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */,
7A10227B2C557EE900B84627 /* LocationPickerCoordinator.swift in Sources */, 7A10227B2C557EE900B84627 /* LocationPickerCoordinator.swift in Sources */,
7AB490292D6B1217002F39C6 /* ACKeyboardView.swift in Sources */, 7AB490292D6B1217002F39C6 /* ACKeyboardView.swift in Sources */,
7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */, 7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */,
@ -1505,6 +1523,7 @@
7AF231932DA1C28100AE5EB3 /* AuthScreen.swift in Sources */, 7AF231932DA1C28100AE5EB3 /* AuthScreen.swift in Sources */,
7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */, 7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */,
7A10226E2C551EE000B84627 /* LocationEditViewModel.swift in Sources */, 7A10226E2C551EE000B84627 /* LocationEditViewModel.swift in Sources */,
7ADFC95B2DAD1F45001A43E3 /* WebView.swift in Sources */,
7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */, 7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */,
7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */, 7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */,
7A14416C2C297F2100E79018 /* NotesCoordinator.swift in Sources */, 7A14416C2C297F2100E79018 /* NotesCoordinator.swift in Sources */,
@ -1582,6 +1601,7 @@
7A6B65B32CFB0DB500AABA6B /* NullifyDate.swift in Sources */, 7A6B65B32CFB0DB500AABA6B /* NullifyDate.swift in Sources */,
7A7097C22C9EC139007CFDCA /* ServiceContainer.swift in Sources */, 7A7097C22C9EC139007CFDCA /* ServiceContainer.swift in Sources */,
7A7AA2C42DA2A3CB00276D83 /* LocationError.swift in Sources */, 7A7AA2C42DA2A3CB00276D83 /* LocationError.swift in Sources */,
7AEAA2A12DAD9C00009954F0 /* TokenResponse.swift in Sources */,
7A54BFD32D43B95E00176D6D /* DbUpdatePolicy.swift in Sources */, 7A54BFD32D43B95E00176D6D /* DbUpdatePolicy.swift in Sources */,
7A5D84BE2C1AE44700C2209B /* VehiclePhoto.swift in Sources */, 7A5D84BE2C1AE44700C2209B /* VehiclePhoto.swift in Sources */,
7A64A2262C1A32C800284124 /* AudioRecordDto.swift in Sources */, 7A64A2262C1A32C800284124 /* AudioRecordDto.swift in Sources */,

View File

@ -3,60 +3,9 @@
<device id="retina4_7" orientation="portrait" appearance="dark"/> <device id="retina4_7" orientation="portrait" appearance="dark"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Google Sign In Controller-->
<scene sceneID="ztj-pr-ty7">
<objects>
<viewController storyboardIdentifier="GoogleSignInController" id="Ptg-6q-3w6" customClass="GoogleSignInController" customModule="AutoCat" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="NtL-RA-Nxs">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<wkWebView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0QS-UT-hbi">
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<color key="backgroundColor" red="0.36078431370000003" green="0.38823529410000002" blue="0.4039215686" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="AMg-mc-MMG">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<items>
<navigationItem id="fZb-kM-9an">
<barButtonItem key="rightBarButtonItem" title="Close" id="ZHH-OZ-vHc">
<connections>
<action selector="close:" destination="Ptg-6q-3w6" id="VVY-eV-Yeg"/>
</connections>
</barButtonItem>
</navigationItem>
</items>
</navigationBar>
</subviews>
<viewLayoutGuide key="safeArea" id="4cf-6q-b5U"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="0QS-UT-hbi" firstAttribute="top" secondItem="AMg-mc-MMG" secondAttribute="bottom" id="1l8-UT-leW"/>
<constraint firstItem="AMg-mc-MMG" firstAttribute="top" secondItem="4cf-6q-b5U" secondAttribute="top" id="50U-IM-aiw"/>
<constraint firstItem="AMg-mc-MMG" firstAttribute="leading" secondItem="4cf-6q-b5U" secondAttribute="leading" id="9Si-sE-9y6"/>
<constraint firstItem="AMg-mc-MMG" firstAttribute="trailing" secondItem="4cf-6q-b5U" secondAttribute="trailing" id="DuZ-iN-C4K"/>
<constraint firstItem="0QS-UT-hbi" firstAttribute="leading" secondItem="4cf-6q-b5U" secondAttribute="leading" id="RKF-L4-hnY"/>
<constraint firstItem="0QS-UT-hbi" firstAttribute="trailing" secondItem="4cf-6q-b5U" secondAttribute="trailing" id="gBv-r4-tWa"/>
<constraint firstItem="0QS-UT-hbi" firstAttribute="bottom" secondItem="4cf-6q-b5U" secondAttribute="bottom" id="sYj-Jg-IMn"/>
</constraints>
</view>
<connections>
<outlet property="webView" destination="0QS-UT-hbi" id="vZb-CE-W3J"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="WxN-oK-U9s" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1823" y="143"/>
</scene>
<!--Main Tab Controller--> <!--Main Tab Controller-->
<scene sceneID="YhQ-kn-py3"> <scene sceneID="YhQ-kn-py3">
<objects> <objects>
@ -84,9 +33,4 @@
<point key="canvasLocation" x="199" y="143"/> <point key="canvasLocation" x="199" y="143"/>
</scene> </scene>
</scenes> </scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document> </document>

View File

@ -1,108 +0,0 @@
import UIKit
import WebKit
import CommonCrypto
import PKHUD
import AutoCatCore
struct TokenResponse: Codable {
var id_token: String
var refresh_token: String?
var access_token: String
var expires_in: Int
var token_type: String
var scope: String
}
class GoogleSignInController: UIViewController, WKNavigationDelegate {
@IBOutlet weak var webView: WKWebView!
private var codeVerifier: String = ""
public var completion: (() -> Void)?
let apiService: ApiServiceProtocol = ServiceContainer.shared.resolve(ApiServiceProtocol.self)
override func viewDidLoad() {
super.viewDidLoad()
self.webView.navigationDelegate = self
#if targetEnvironment(macCatalyst)
self.webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15"
#else
self.webView.customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1"
#endif
self.codeVerifier = UUID().uuidString
let codeChallenge = self.sha256(string: self.codeVerifier) ?? ""
let authUrlString = Constants.googleAuthURL
+ "?response_type=code"
+ "&code_challenge_method=S256"
+ "&scope=email%20profile"
+ "&redirect_uri=" + Constants.googleRedirectURL
+ "&client_id=" + Constants.fbClientId
+ "&code_challenge=" + codeChallenge
if let url = URL(string: authUrlString) {
let request = URLRequest(url: url)
self.webView.load(request)
}
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
if let url = navigationAction.request.url {
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
if let queryItems = components.queryItems {
if let code = queryItems.first(where: { $0.name == "code" })?.value {
Task { @MainActor in
do {
let token = try await self.getToken(code: code)
await apiService.fbVerifyAssertion(provider: "google.com", idToken: token.id_token, accessToken: token.access_token)
self.dismiss(animated: true, completion: self.completion)
} catch {
HUD.flash(.labeledError(title: nil, subtitle: error.localizedDescription))
}
}
return .cancel
}
}
}
}
return .allow
}
@IBAction func close(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
}
func sha256(string: String) -> String? {
guard let data = string.data(using: .utf8) else { return nil }
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
return String(data: Data(Base64FS.encode(data: hash)), encoding: .utf8)?.trimmingCharacters(in: CharacterSet(charactersIn: "="))
}
func getToken(code: String) async throws -> TokenResponse {
let tokenUrlString = Constants.googleTokenURL
+ "?grant_type=authorization_code"
+ "&code=" + code
+ "&redirect_uri=" + Constants.googleRedirectURL
+ "&client_id=" + Constants.fbClientId
+ "&code_verifier=" + self.codeVerifier
if let url = URL(string: tokenUrlString) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(TokenResponse.self, from: data)
} else {
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Bad URL"])
}
}
}

View File

@ -0,0 +1,36 @@
//
// GoogleAuthScreen.swift
// AutoCat
//
// Created by Selim Mustafaev on 14.04.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftUI
import AutoCatCore
struct GoogleAuthScreen: View {
@State var viewModel = makeViewModel()
@Environment(\.dismiss) var dismiss
var body: some View {
WebView(url: viewModel.url, userAgent: Constants.userAgent)
.decidePolicy { navAction in
let policy = await viewModel.decidePolicy(for: navAction)
if policy == .cancel {
dismiss()
}
return policy
}
.hud($viewModel.hud)
}
static func makeViewModel() -> GoogleAuthViewModel {
let resolver = ServiceContainer.shared
return GoogleAuthViewModel(
apiService: resolver.resolve(ApiServiceProtocol.self)
)
}
}

View File

@ -0,0 +1,97 @@
//
// GoogleAuthViewModel.swift
// AutoCat
//
// Created by Selim Mustafaev on 14.04.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import AutoCatCore
import SwiftUI
import CommonCrypto
import WebKit
@MainActor
@Observable
final class GoogleAuthViewModel: ACHudContainer {
let apiService: ApiServiceProtocol
var hud: ACHud?
let codeVerifier = UUID().uuidString
var url = URL(fileURLWithPath: "")
init(
apiService: ApiServiceProtocol
) {
self.apiService = apiService
do {
url = try makeAuthURL()
} catch {
hud = .error(error)
}
}
func makeAuthURL() throws -> URL {
guard let codeChallenge = sha256(string: codeVerifier) else {
throw GenericError.somethingWentWrong
}
let authUrlString = Constants.googleAuthURL
+ "?response_type=code"
+ "&code_challenge_method=S256"
+ "&scope=email%20profile"
+ "&redirect_uri=" + Constants.googleRedirectURL
+ "&client_id=" + Constants.fbClientId
+ "&code_challenge=" + codeChallenge
if let url = URL(string: authUrlString) {
return url
} else {
throw GenericError.somethingWentWrong
}
}
func sha256(string: String) -> String? {
guard let data = string.data(using: .utf8) else {
return nil
}
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
let hashData = Data(Base64FS.encode(data: hash))
return String(data: hashData, encoding: .utf8)?
.trimmingCharacters(in: CharacterSet(charactersIn: "="))
}
func decidePolicy(for navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
guard let url = navigationAction.request.url,
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems,
let code = queryItems.first(where: { $0.name == "code" })?.value
else {
return .allow
}
await wrapWithToast { [weak self] in
guard let self else { return }
let token = try await apiService.getToken(
code: code,
codeVerifier: codeVerifier
)
await apiService.fbVerifyAssertion(
provider: "google.com",
idToken: token.id_token,
accessToken: token.access_token
)
}
return .cancel
}
}

View File

@ -0,0 +1,61 @@
//
// WebView.swift
// AutoCat
//
// Created by Selim Mustafaev on 14.04.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import WebKit
import SwiftUI
struct WebView: UIViewRepresentable {
class Coordinator: NSObject, WKNavigationDelegate {
var decidePolicyClosure: ((WKNavigationAction) async -> WKNavigationActionPolicy)?
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
if let decidePolicyClosure {
return await decidePolicyClosure(navigationAction)
} else {
return .allow
}
}
}
let url: URL
let userAgent: String?
@State private var delegate = Coordinator()
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
webView.customUserAgent = userAgent
webView.navigationDelegate = context.coordinator
let request = URLRequest(url: url)
webView.load(request)
}
func makeCoordinator() -> Coordinator {
delegate
}
}
extension WebView {
func decidePolicy(closure: @escaping (WKNavigationAction) async -> WKNavigationActionPolicy) -> Self {
delegate.decidePolicyClosure = closure
return self
}
}

View File

@ -13,7 +13,8 @@ struct SettingsScreen: View {
@State var viewModel: SettingsViewModel @State var viewModel: SettingsViewModel
@State var googleSheetOpened = false @State var googleSheetOpened = false
@State var googleLoginSheetOpened = false
var body: some View { var body: some View {
Form { Form {
Section { Section {
@ -29,7 +30,7 @@ struct SettingsScreen: View {
if viewModel.googleAuthorized { if viewModel.googleAuthorized {
googleSheetOpened = true googleSheetOpened = true
} else { } else {
viewModel.googleSignIn() googleLoginSheetOpened = true
} }
} }
@ -83,6 +84,9 @@ struct SettingsScreen: View {
Text("You are currently signed in with email \(email). It will help to gather more data about vehicles.") Text("You are currently signed in with email \(email). It will help to gather more data about vehicles.")
} }
} }
.sheet(isPresented: $googleLoginSheetOpened) {
GoogleAuthScreen()
}
} }
} }

View File

@ -82,10 +82,6 @@ class SettingsViewModel {
coordinator?.openAuthScreen() coordinator?.openAuthScreen()
} }
func googleSignIn() {
coordinator?.openGoogleOauthPage()
}
func googleSignout() { func googleSignout() {
settingService.user.firebaseIdToken = nil settingService.user.firebaseIdToken = nil
settingService.user.firebaseRefreshToken = nil settingService.user.firebaseRefreshToken = nil

View File

@ -14,6 +14,7 @@ public enum ApiError: LocalizedError, Equatable {
case emptyResponse case emptyResponse
case unauthorized case unauthorized
case threadSafety case threadSafety
case badUrl
case message(String) case message(String)
case httpError(Int) case httpError(Int)
@ -25,6 +26,7 @@ public enum ApiError: LocalizedError, Equatable {
case .threadSafety: "Thread safety error" case .threadSafety: "Thread safety error"
case .message(let message): message case .message(let message): message
case .httpError(let status): "General http error (status \(status))" case .httpError(let status): "General http error (status \(status))"
case .badUrl: "Bad url"
} }
} }
} }

View File

@ -193,6 +193,25 @@ public actor ApiService: ApiServiceProtocol {
} }
} }
public func getToken(code: String, codeVerifier: String) async throws -> TokenResponse {
let tokenUrlString = Constants.googleTokenURL
+ "?grant_type=authorization_code"
+ "&code=" + code
+ "&redirect_uri=" + Constants.googleRedirectURL
+ "&client_id=" + Constants.fbClientId
+ "&code_verifier=" + codeVerifier
if let url = URL(string: tokenUrlString) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(TokenResponse.self, from: data)
} else {
throw ApiError.badUrl
}
}
// MARK: - AutoCat public API // MARK: - AutoCat public API
public func login(email: String, password: String) async throws -> User { public func login(email: String, password: String) async throws -> User {

View File

@ -34,5 +34,6 @@ public protocol ApiServiceProtocol: Sendable {
func getVehicles(with filter: Filter, pageToken: String?, pageSize: Int) async throws -> PagedResponse<VehicleDto> func getVehicles(with filter: Filter, pageToken: String?, pageSize: Int) async throws -> PagedResponse<VehicleDto>
func fbVerifyAssertion(provider: String, idToken: String, accessToken: String?) async func fbVerifyAssertion(provider: String, idToken: String, accessToken: String?) async
func getToken(code: String, codeVerifier: String) async throws -> TokenResponse
func getReport(for number: String) async throws -> VehicleDto func getReport(for number: String) async throws -> VehicleDto
} }

View File

@ -0,0 +1,17 @@
//
// TokenResponse.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 14.04.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
public struct TokenResponse: Codable, Sendable {
public var id_token: String
public var refresh_token: String?
public var access_token: String
public var expires_in: Int
public var token_type: String
public var scope: String
}

View File

@ -52,4 +52,6 @@ public enum Constants {
public static let reportLinkBaseURL = "https://auto.aliencat.pro/report.html" public static let reportLinkBaseURL = "https://auto.aliencat.pro/report.html"
public static let audioRecordsFolder = "recordings" public static let audioRecordsFolder = "recordings"
public static let userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1"
} }