Replacing settings screen with SwiftUI version. Adding SettingService.
This commit is contained in:
parent
f78becf791
commit
17d00fbf65
@ -7,6 +7,11 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
7A06E0AC2C7065AC005731AC /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A06E0AB2C7065AB005731AC /* SettingsScreen.swift */; };
|
||||
7A06E0AE2C7065C7005731AC /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A06E0AD2C7065C7005731AC /* SettingsViewModel.swift */; };
|
||||
7A06E0B02C7065D8005731AC /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A06E0AF2C7065D8005731AC /* SettingsCoordinator.swift */; };
|
||||
7A06E0B32C707E13005731AC /* SettingsServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A06E0B22C707E13005731AC /* SettingsServiceProtocol.swift */; };
|
||||
7A06E0B52C707E2B005731AC /* SettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A06E0B42C707E2B005731AC /* SettingsService.swift */; };
|
||||
7A1022692C55197D00B84627 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 7ADF23052C25B5BF002624FF /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
7A10226C2C551EC500B84627 /* LocationEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10226B2C551EC500B84627 /* LocationEditScreen.swift */; };
|
||||
7A10226E2C551EE000B84627 /* LocationEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10226D2C551EE000B84627 /* LocationEditViewModel.swift */; };
|
||||
@ -50,6 +55,7 @@
|
||||
7A33381124990DAE00D878F1 /* FiltersController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33381024990DAE00D878F1 /* FiltersController.swift */; };
|
||||
7A3399AB299063370087DF98 /* SearchControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3399AA299063370087DF98 /* SearchControllerExt.swift */; };
|
||||
7A35177B27E23F8800DC538C /* Eureka in Frameworks */ = {isa = PBXBuildFile; productRef = 7A35177A27E23F8800DC538C /* Eureka */; };
|
||||
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 */; };
|
||||
7A3F07AD2436350B00E59687 /* SearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3F07AC2436350B00E59687 /* SearchController.swift */; };
|
||||
@ -59,6 +65,7 @@
|
||||
7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C352C18AC7F00D47C18 /* ApiError.swift */; };
|
||||
7A599C392C18B22900D47C18 /* FbRefreshTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C382C18B22900D47C18 /* FbRefreshTokenModel.swift */; };
|
||||
7A599C3B2C18B36A00D47C18 /* FbVerifyTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C3A2C18B36A00D47C18 /* FbVerifyTokenModel.swift */; };
|
||||
7A5D7E0C2C71EB25002C17E7 /* ToggleRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5D7E0B2C71EB25002C17E7 /* ToggleRowView.swift */; };
|
||||
7A5D84B92C1AD3C200C2209B /* DtoConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5D84B82C1AD3C200C2209B /* DtoConvertible.swift */; };
|
||||
7A5D84BC2C1AD81000C2209B /* VehicleOwnershipPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5D84BB2C1AD81000C2209B /* VehicleOwnershipPeriod.swift */; };
|
||||
7A5D84BE2C1AE44700C2209B /* VehiclePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5D84BD2C1AE44700C2209B /* VehiclePhoto.swift */; };
|
||||
@ -161,7 +168,6 @@
|
||||
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */; };
|
||||
7AE26A3524F31B0700625033 /* EventsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3424F31B0700625033 /* EventsController.swift */; };
|
||||
7AEFC3BE2529D3CC00BADFB2 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFC3BD2529D3CC00BADFB2 /* ConfigurableCell.swift */; };
|
||||
7AEFE728240455E200910EB7 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFE727240455E200910EB7 /* SettingsController.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 */; };
|
||||
@ -242,6 +248,11 @@
|
||||
7A000AA124C2EEDE001F5B00 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; };
|
||||
7A0420A925619AEC00034941 /* Osago.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Osago.swift; sourceTree = "<group>"; };
|
||||
7A0516192414FF0900FC55AC /* DateSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateSection.swift; sourceTree = "<group>"; };
|
||||
7A06E0AB2C7065AB005731AC /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
|
||||
7A06E0AD2C7065C7005731AC /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
7A06E0AF2C7065D8005731AC /* SettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A06E0B22C707E13005731AC /* SettingsServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
7A06E0B42C707E2B005731AC /* SettingsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsService.swift; sourceTree = "<group>"; };
|
||||
7A10226B2C551EC500B84627 /* LocationEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEditScreen.swift; sourceTree = "<group>"; };
|
||||
7A10226D2C551EE000B84627 /* LocationEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEditViewModel.swift; sourceTree = "<group>"; };
|
||||
7A10226F2C551EFD00B84627 /* LocationEditCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEditCoordinator.swift; sourceTree = "<group>"; };
|
||||
@ -291,6 +302,7 @@
|
||||
7A33381024990DAE00D878F1 /* FiltersController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersController.swift; sourceTree = "<group>"; };
|
||||
7A333813249A532400D878F1 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = "<group>"; };
|
||||
7A3399AA299063370087DF98 /* SearchControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchControllerExt.swift; sourceTree = "<group>"; };
|
||||
7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Settings.swift"; sourceTree = "<group>"; };
|
||||
7A3E30F22C18840600567704 /* ActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
7A3F07AA24360DC800E59687 /* Dated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dated.swift; sourceTree = "<group>"; };
|
||||
7A3F07AC2436350B00E59687 /* SearchController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchController.swift; sourceTree = "<group>"; };
|
||||
@ -303,6 +315,7 @@
|
||||
7A599C352C18AC7F00D47C18 /* ApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiError.swift; sourceTree = "<group>"; };
|
||||
7A599C382C18B22900D47C18 /* FbRefreshTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FbRefreshTokenModel.swift; sourceTree = "<group>"; };
|
||||
7A599C3A2C18B36A00D47C18 /* FbVerifyTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FbVerifyTokenModel.swift; sourceTree = "<group>"; };
|
||||
7A5D7E0B2C71EB25002C17E7 /* ToggleRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleRowView.swift; sourceTree = "<group>"; };
|
||||
7A5D84B82C1AD3C200C2209B /* DtoConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DtoConvertible.swift; sourceTree = "<group>"; };
|
||||
7A5D84BB2C1AD81000C2209B /* VehicleOwnershipPeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleOwnershipPeriod.swift; sourceTree = "<group>"; };
|
||||
7A5D84BD2C1AE44700C2209B /* VehiclePhoto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehiclePhoto.swift; sourceTree = "<group>"; };
|
||||
@ -409,7 +422,6 @@
|
||||
7AE26A3424F31B0700625033 /* EventsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsController.swift; sourceTree = "<group>"; };
|
||||
7AE8424D26109F78002F6B31 /* Exportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exportable.swift; sourceTree = "<group>"; };
|
||||
7AEFC3BD2529D3CC00BADFB2 /* ConfigurableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = "<group>"; };
|
||||
7AEFE727240455E200910EB7 /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
7AF6D1F22677C03B0086EA64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@ -472,6 +484,26 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
7A06E0AA2C706550005731AC /* SettingsScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A06E0AB2C7065AB005731AC /* SettingsScreen.swift */,
|
||||
7A06E0AD2C7065C7005731AC /* SettingsViewModel.swift */,
|
||||
7A06E0AF2C7065D8005731AC /* SettingsCoordinator.swift */,
|
||||
);
|
||||
path = SettingsScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A06E0B12C707DD7005731AC /* SettingsService */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A06E0B22C707E13005731AC /* SettingsServiceProtocol.swift */,
|
||||
7A06E0B42C707E2B005731AC /* SettingsService.swift */,
|
||||
7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */,
|
||||
);
|
||||
path = SettingsService;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A0B969D257D6CB3000B39AD /* eureka */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -564,7 +596,6 @@
|
||||
7A27ADC6249D43210035F39E /* RegionsController.swift */,
|
||||
7A11471723FDEBFA00B424AF /* ReportController.swift */,
|
||||
7A3F07AC2436350B00E59687 /* SearchController.swift */,
|
||||
7AEFE727240455E200910EB7 /* SettingsController.swift */,
|
||||
7AC3554B29696A1C00889457 /* MainTabController.swift */,
|
||||
7AC3554D29696C4500889457 /* DummyNewController.swift */,
|
||||
7AC3554F29696D5A00889457 /* NewNumberController.swift */,
|
||||
@ -628,6 +659,7 @@
|
||||
7A1441632C297E9800E79018 /* Screens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A06E0AA2C706550005731AC /* SettingsScreen */,
|
||||
7A1022752C557E3F00B84627 /* LocationPickerScreen */,
|
||||
7A10226A2C551EA200B84627 /* LocationEditScreen */,
|
||||
7A71580A2C44451B00852088 /* AdsScreen */,
|
||||
@ -680,6 +712,7 @@
|
||||
7A45FB362C2706D000618694 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A06E0B12C707DD7005731AC /* SettingsService */,
|
||||
7A60D24B2C5A9D2700D13F7B /* LocationService */,
|
||||
7AB5873D2C42FF4000FA7B66 /* ApiService */,
|
||||
7AB587302C42D35900FA7B66 /* StorageService */,
|
||||
@ -966,6 +999,7 @@
|
||||
7A961C6D2C4C3C9E00CE2211 /* LinkRowView.swift */,
|
||||
7AAAFAD92C4D1AFE0050410D /* Zoomable.swift */,
|
||||
7A1022712C554A1300B84627 /* CustomHostingController.swift */,
|
||||
7A5D7E0B2C71EB25002C17E7 /* ToggleRowView.swift */,
|
||||
);
|
||||
path = SwiftUI;
|
||||
sourceTree = "<group>";
|
||||
@ -1200,6 +1234,7 @@
|
||||
7A961C6C2C4C3C8600CE2211 /* TextRowView.swift in Sources */,
|
||||
7AEFC3BE2529D3CC00BADFB2 /* ConfigurableCell.swift in Sources */,
|
||||
7A1022772C557EC400B84627 /* LocationPickerScreen.swift in Sources */,
|
||||
7A5D7E0C2C71EB25002C17E7 /* ToggleRowView.swift in Sources */,
|
||||
7A7158092C44087E00852088 /* OsagoCoordinator.swift in Sources */,
|
||||
7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */,
|
||||
7A11470123FDE7E500B424AF /* AppDelegate.swift in Sources */,
|
||||
@ -1224,6 +1259,7 @@
|
||||
7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */,
|
||||
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */,
|
||||
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */,
|
||||
7A06E0AE2C7065C7005731AC /* SettingsViewModel.swift in Sources */,
|
||||
7A961C6E2C4C3C9E00CE2211 /* LinkRowView.swift in Sources */,
|
||||
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */,
|
||||
7A3E30F32C18840600567704 /* ActivityItemSource.swift in Sources */,
|
||||
@ -1232,7 +1268,6 @@
|
||||
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 */,
|
||||
@ -1276,10 +1311,12 @@
|
||||
7A8A220B248D67B60073DFD9 /* VehicleReportImage.swift in Sources */,
|
||||
7AFBE8C42C302561003C491D /* ACHudContainer.swift in Sources */,
|
||||
7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */,
|
||||
7A06E0AC2C7065AC005731AC /* SettingsScreen.swift in Sources */,
|
||||
7A7158002C43EA6900852088 /* OwnersScreen.swift in Sources */,
|
||||
7A1441702C2998B200E79018 /* Formatters.swift in Sources */,
|
||||
7ADF6C95250D037700F237B2 /* ShowEventController.swift in Sources */,
|
||||
7A71580C2C44453200852088 /* AdsScreen.swift in Sources */,
|
||||
7A06E0B02C7065D8005731AC /* SettingsCoordinator.swift in Sources */,
|
||||
7A91894F29A2BD8700519C74 /* GestureRecognizers.swift in Sources */,
|
||||
7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */,
|
||||
7AFBE8CC2C3085C6003C491D /* ACProgressView.swift in Sources */,
|
||||
@ -1343,12 +1380,15 @@
|
||||
7AF6D21E2677C1680086EA64 /* PlateNumber.swift in Sources */,
|
||||
7A5D84C62C1AE72E00C2209B /* VehicleName.swift in Sources */,
|
||||
7A64A2122C19E2A100284124 /* VehicleModelDto.swift in Sources */,
|
||||
7A06E0B32C707E13005731AC /* SettingsServiceProtocol.swift in Sources */,
|
||||
7A06E0B52C707E2B005731AC /* SettingsService.swift in Sources */,
|
||||
7AF6D21F2677C1680086EA64 /* Response.swift in Sources */,
|
||||
7A60D24D2C5A9D4900D13F7B /* LocationService.swift in Sources */,
|
||||
7A60D24F2C5A9DA800D13F7B /* LocationServiceProtocol.swift in Sources */,
|
||||
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
|
||||
7A64A2032C19DA1000284124 /* VehicleDto.swift in Sources */,
|
||||
7AB587322C42D38E00FA7B66 /* StorageServiceProtocol.swift in Sources */,
|
||||
7A3E12D72C7B42B700EE710D /* UserDefaults+Settings.swift in Sources */,
|
||||
7A64A2222C19E99E00284124 /* DebugInfoDto.swift in Sources */,
|
||||
7A5D84BC2C1AD81000C2209B /* VehicleOwnershipPeriod.swift in Sources */,
|
||||
7A64A2202C19E93500284124 /* VehicleNoteDto.swift in Sources */,
|
||||
@ -1567,7 +1607,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 134;
|
||||
CURRENT_PROJECT_VERSION = 135;
|
||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
||||
@ -1594,7 +1634,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 134;
|
||||
CURRENT_PROJECT_VERSION = 135;
|
||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23089" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="pme-aR-UNJ">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23091" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="pme-aR-UNJ">
|
||||
<device id="retina4_7" orientation="portrait" appearance="dark"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23077"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23079"/>
|
||||
<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"/>
|
||||
@ -55,17 +54,17 @@
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="EventCell" id="QIb-Hv-tvk" customClass="EventCell" customModule="AutoCat" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="435"/>
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="436"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QIb-Hv-tvk" id="Ypt-ch-fGT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="435"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="436"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="HP8-oO-yhP">
|
||||
<rect key="frame" x="16" y="8" width="343" height="419"/>
|
||||
<rect key="frame" x="16" y="8" width="343" height="420"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="k4Z-KM-byE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="419"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="420"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xcQ-Wz-gJ0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="201"/>
|
||||
@ -74,7 +73,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1tQ-zM-6T9">
|
||||
<rect key="frame" x="0.0" y="209" width="335" height="210"/>
|
||||
<rect key="frame" x="0.0" y="209" width="335" height="211"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@ -82,7 +81,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="750" verticalHuggingPriority="251" image="person" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="CFI-xa-eLs">
|
||||
<rect key="frame" x="343" y="1" width="0.0" height="416.5"/>
|
||||
<rect key="frame" x="343" y="1" width="0.0" height="417.5"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
@ -342,22 +341,6 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="5688.8000000000002" y="142.57871064467767"/>
|
||||
</scene>
|
||||
<!--Settings-->
|
||||
<scene sceneID="G47-o0-Caa">
|
||||
<objects>
|
||||
<viewController id="4jU-Z3-PF2" customClass="SettingsController" customModule="AutoCat" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="NFd-we-MVH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="Uix-8K-fxh"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" title="Settings" image="settings" landscapeImage="settings-compact" id="zEL-ph-E2f"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="trD-gZ-yAv" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3262" y="918"/>
|
||||
</scene>
|
||||
<!--Voice records-->
|
||||
<scene sceneID="9pI-G0-wG0">
|
||||
<objects>
|
||||
@ -655,7 +638,6 @@
|
||||
<segue destination="RK6-pn-2Bg" kind="relationship" relationship="viewControllers" id="KNz-WF-Kyy"/>
|
||||
<segue destination="dxo-XS-x8Q" kind="relationship" relationship="viewControllers" id="MgV-gx-h7p"/>
|
||||
<segue destination="GCa-Re-j14" kind="relationship" relationship="viewControllers" id="FGp-f6-fUh"/>
|
||||
<segue destination="4jU-Z3-PF2" kind="relationship" relationship="viewControllers" id="aH2-IT-86l"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="AJs-8F-Qbu" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
@ -928,8 +910,6 @@
|
||||
<image name="record-compact" width="23" height="23"/>
|
||||
<image name="search" width="23" height="23"/>
|
||||
<image name="search-compact" width="17" height="17"/>
|
||||
<image name="settings" width="25" height="25"/>
|
||||
<image name="settings-compact" width="18" height="18"/>
|
||||
<image name="square.and.arrow.up" catalog="system" width="110" height="128"/>
|
||||
<image name="text.bubble" catalog="system" width="128" height="110"/>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
|
||||
@ -45,13 +45,12 @@ class GoogleSignInController: UIViewController, WKNavigationDelegate {
|
||||
self.webView.load(request)
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
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 {
|
||||
decisionHandler(.cancel)
|
||||
|
||||
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 {
|
||||
@ -62,13 +61,15 @@ class GoogleSignInController: UIViewController, WKNavigationDelegate {
|
||||
HUD.flash(.labeledError(title: nil, subtitle: error.localizedDescription))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
return .cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .allow
|
||||
}
|
||||
|
||||
@IBAction func close(_ sender: UIBarButtonItem) {
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
|
||||
@ -4,6 +4,8 @@ import AutoCatCore
|
||||
|
||||
class MainTabController: UITabBarController, UITabBarControllerDelegate {
|
||||
|
||||
var settingsCoordinator: SettingsCoordinator?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.delegate = self
|
||||
@ -14,6 +16,15 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate {
|
||||
viewControllers?.remove(at: 2)
|
||||
|
||||
#endif
|
||||
|
||||
Task { await addSettings() }
|
||||
}
|
||||
|
||||
func addSettings() async {
|
||||
|
||||
let coordinator = SettingsCoordinator(tabController: self)
|
||||
settingsCoordinator = coordinator
|
||||
try? await coordinator.start()
|
||||
}
|
||||
|
||||
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
|
||||
|
||||
@ -91,11 +91,10 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
||||
var alert: UIAlertController?
|
||||
var url: URL!
|
||||
|
||||
Task {
|
||||
Task { @MainActor in
|
||||
do {
|
||||
async let locationTask = RxLocationManager.requestCurrentLocation()
|
||||
async let permissionTask: () = recorder.requestPermissions()
|
||||
let (event, _) = try await (locationTask, permissionTask)
|
||||
let event = try await RxLocationManager.requestCurrentLocation()
|
||||
try await recorder.requestPermissions()
|
||||
|
||||
await makeStartSoundIfNeeded()
|
||||
|
||||
|
||||
@ -129,10 +129,6 @@ 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)
|
||||
|
||||
if let ads = self.vehicle?.ads, let navController = self.navigationController {
|
||||
let coordinator = AdsCoordinator(navController: navController, ads: ads)
|
||||
Task { try await coordinator.start() }
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
import UIKit
|
||||
import Eureka
|
||||
import AuthenticationServices
|
||||
import AutoCatCore
|
||||
|
||||
class SettingsController: FormViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
form +++ Section()
|
||||
<<< LabelRow() { row in
|
||||
row.title = NSLocalizedString("Version", comment: "")
|
||||
row.value = Bundle.main.fullVersion
|
||||
}
|
||||
|
||||
form +++ Section(NSLocalizedString("Profile", comment: ""))
|
||||
<<< LabelRow("AutoCatAccount") { row in
|
||||
row.title = NSLocalizedString("AutoCat Account", comment: "")
|
||||
row.value = Settings.shared.user.email
|
||||
}
|
||||
<<< LabelRow("GoogleAccount") { row in
|
||||
row.title = NSLocalizedString("Google", comment: "")
|
||||
if let jwtString = Settings.shared.user.firebaseIdToken, let jwt = JWT<FirebasePayload>(string: jwtString) {
|
||||
row.value = jwt.payload.email
|
||||
} else {
|
||||
row.value = NSLocalizedString("Log In", comment: "")
|
||||
}
|
||||
row.cellUpdate { cell, _ in
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
}
|
||||
}.onCellSelection { cell, row in
|
||||
if Settings.shared.user.firebaseIdToken != nil {
|
||||
self.displayLogoutSheet(for: "GoogleAccount")
|
||||
} else {
|
||||
self.loginToGoogle()
|
||||
}
|
||||
}
|
||||
|
||||
+++ Section(header: NSLocalizedString("Plate number recognition", comment: ""), footer: NSLocalizedString("Recognize plate numbers in alternative form. For example 'ЕВА 123 777' instead of 'Е123ВА 777'", comment: ""))
|
||||
<<< SwitchRow("AlternateRecognition") { row in
|
||||
row.title = NSLocalizedString("Alternative order", comment: "")
|
||||
row.value = Settings.shared.recognizeAlternativeOrder
|
||||
}.onChange { row in
|
||||
if let val = row.value {
|
||||
Settings.shared.recognizeAlternativeOrder = val
|
||||
}
|
||||
}
|
||||
|
||||
+++ Section(footer: NSLocalizedString("If enabled, app will try to recognize shortened plate numbers (without region) and add default region", comment: ""))
|
||||
<<< SwitchRow("ShortenedNumbers") { row in
|
||||
row.title = NSLocalizedString("Shortened numbers", comment: "")
|
||||
row.value = Settings.shared.recognizeShortenedNumbers
|
||||
}.onChange { row in
|
||||
if let val = row.value {
|
||||
Settings.shared.recognizeShortenedNumbers = val
|
||||
}
|
||||
}
|
||||
<<< TextRow("") { row in
|
||||
row.title = NSLocalizedString("Default region", comment: "")
|
||||
row.placeholder = "161"
|
||||
row.value = Settings.shared.defaultRegion
|
||||
row.hidden = Condition.function(["ShortenedNumbers"], { form in
|
||||
return !((form.rowBy(tag: "ShortenedNumbers") as? SwitchRow)?.value ?? false)
|
||||
})
|
||||
}.onChange { row in
|
||||
if let val = row.value {
|
||||
Settings.shared.defaultRegion = val
|
||||
}
|
||||
}
|
||||
|
||||
+++ Section(footer: NSLocalizedString("When enabled, you will hear short sound before starting audio recording. This will only work when audio record is started via Siri", comment: "")) { $0.tag = "BeepRecordSection" }
|
||||
<<< SwitchRow("BeepRecord") { row in
|
||||
row.title = NSLocalizedString("Beep before record", comment: "")
|
||||
row.value = Settings.shared.recordBeep
|
||||
}.onChange{ row in
|
||||
if let val = row.value {
|
||||
Settings.shared.recordBeep = val
|
||||
}
|
||||
}
|
||||
|
||||
+++ Section(NSLocalizedString("Debug", comment: ""))
|
||||
<<< SwitchRow() { row in
|
||||
row.title = NSLocalizedString("Show debug info", comment: "")
|
||||
row.value = Settings.shared.showDebugInfo
|
||||
}.onChange { row in
|
||||
if let val = row.value {
|
||||
Settings.shared.showDebugInfo = val
|
||||
}
|
||||
}
|
||||
|
||||
+++ Section("")
|
||||
<<< ButtonRow("SignOut") { $0.title = NSLocalizedString("Sign Out", comment: "") }.onCellSelection { cell, row in
|
||||
self.logout()
|
||||
}
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
if let beepSection = self.form.sectionBy(tag: "BeepRecordSection") {
|
||||
beepSection.hidden = true
|
||||
beepSection.evaluateHidden()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func logout() {
|
||||
Settings.shared.user.token = ""
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
self.view.window?.rootViewController = storyboard.instantiateViewController(identifier: "AuthController")
|
||||
}
|
||||
|
||||
func displayLogoutSheet(for row: String) {
|
||||
guard let jwtString = Settings.shared.user.firebaseIdToken, let jwt = JWT<FirebasePayload>(string: jwtString) else { return }
|
||||
|
||||
let msg = String.localizedStringWithFormat(NSLocalizedString("You are currently signed in with email %@. It will help to gather more data about vehicles.", comment: ""), jwt.payload.email)
|
||||
let sheet = UIAlertController(title: jwt.payload.name, message: msg, preferredStyle: .actionSheet)
|
||||
|
||||
let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) }
|
||||
let logout = UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in
|
||||
Settings.shared.user.firebaseIdToken = nil
|
||||
Settings.shared.user.firebaseRefreshToken = nil
|
||||
if let row = self.form.rowBy(tag: row) as? LabelRow {
|
||||
row.value = NSLocalizedString("Log In", comment: "")
|
||||
row.reload()
|
||||
}
|
||||
}
|
||||
|
||||
sheet.addAction(logout)
|
||||
sheet.addAction(cancel)
|
||||
self.present(sheet, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func loginToGoogle() {
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
if let vc = storyboard.instantiateViewController(identifier: "GoogleSignInController") as? GoogleSignInController {
|
||||
vc.completion = {
|
||||
guard let googleRow = self.form.rowBy(tag: "GoogleAccount") as? LabelRow else { return }
|
||||
if let jwtString = Settings.shared.user.firebaseIdToken, let jwt = JWT<FirebasePayload>(string: jwtString) {
|
||||
googleRow.value = jwt.payload.email
|
||||
} else {
|
||||
googleRow.value = NSLocalizedString("Log In", comment: "")
|
||||
}
|
||||
googleRow.reload()
|
||||
}
|
||||
self.present(vc, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func loginToApple() {
|
||||
|
||||
}
|
||||
}
|
||||
51
AutoCat/Screens/SettingsScreen/SettingsCoordinator.swift
Normal file
51
AutoCat/Screens/SettingsScreen/SettingsCoordinator.swift
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// SettingsCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
class SettingsCoordinator: Coordinator {
|
||||
|
||||
weak var viewController: UITabBarController?
|
||||
var settingsController: UIViewController?
|
||||
|
||||
init(tabController: UITabBarController) {
|
||||
|
||||
self.viewController = tabController
|
||||
}
|
||||
|
||||
func start() async throws {
|
||||
|
||||
let viewModel = SettingsViewModel(settingService: SettingsService.shared)
|
||||
viewModel.coordinator = self
|
||||
let controller = UIHostingController(rootView: SettingsScreen(viewModel: viewModel))
|
||||
settingsController = controller
|
||||
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
navController.tabBarItem = UITabBarItem(title: NSLocalizedString("Settings", comment: ""),
|
||||
image: UIImage(systemName: "gear"), tag: 0)
|
||||
|
||||
viewController?.viewControllers?.append(navController)
|
||||
}
|
||||
|
||||
func openAuthScreen() {
|
||||
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
viewController?.view.window?.rootViewController = storyboard.instantiateViewController(identifier: "AuthController")
|
||||
}
|
||||
|
||||
func openGoogleOauthPage() {
|
||||
print("===== openGoogleOauthPage")
|
||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||
if let vc = storyboard.instantiateViewController(identifier: "GoogleSignInController") as? GoogleSignInController {
|
||||
settingsController?.present(vc, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
87
AutoCat/Screens/SettingsScreen/SettingsScreen.swift
Normal file
87
AutoCat/Screens/SettingsScreen/SettingsScreen.swift
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// SettingsScreen.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
struct SettingsScreen: View {
|
||||
|
||||
@State var viewModel: SettingsViewModel
|
||||
@State var googleSheetOpened = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
TextRowView(title: "Version", value: Bundle.main.fullVersion)
|
||||
}
|
||||
|
||||
Section("Profile") {
|
||||
SimpleTextRowView(title: "AutoCat Account", value: viewModel.autocatEmail)
|
||||
SimpleTextRowView(title: "Google",
|
||||
value: viewModel.googleEmail ?? "Log In",
|
||||
showArrow: true)
|
||||
.onTapGesture {
|
||||
if viewModel.googleAuthorized {
|
||||
googleSheetOpened = true
|
||||
} else {
|
||||
viewModel.googleSignIn()
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.canSignOut {
|
||||
Button("Sign Out", action: viewModel.signOut)
|
||||
}
|
||||
}
|
||||
|
||||
Section("Plate number recognition") {
|
||||
ToggleRowView(title: "Alternative order",
|
||||
description: "Recognize plate numbers in alternative form. For example 'ЕВА 123 777' instead of 'Е123ВА 777'",
|
||||
toggle: $viewModel.settingService.recognizeAlternativeOrder)
|
||||
ToggleRowView(title: "Shortened numbers",
|
||||
description: "If enabled, app will try to recognize shortened plate numbers (without region) and add default region",
|
||||
toggle: $viewModel.settingService.recognizeShortenedNumbers)
|
||||
if viewModel.settingService.recognizeShortenedNumbers {
|
||||
LabeledContent("Default region") {
|
||||
TextField("", text: $viewModel.settingService.defaultRegion)
|
||||
.frame(width: 50)
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
ToggleRowView(title: "Beep before record",
|
||||
description: "When enabled, you will hear short sound before starting audio recording. This will only work when audio record is started via Siri",
|
||||
toggle: $viewModel.settingService.recordBeep)
|
||||
}
|
||||
|
||||
Section("Debug") {
|
||||
ToggleRowView(title: "Show debug info",
|
||||
description: nil,
|
||||
toggle: $viewModel.settingService.showDebugInfo)
|
||||
ToggleRowView(title: "Use test backend",
|
||||
description: nil,
|
||||
toggle: $viewModel.settingService.useTestBackend)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.confirmationDialog(viewModel.googleUsername ?? "",
|
||||
isPresented: $googleSheetOpened,
|
||||
titleVisibility: .visible) {
|
||||
Button("Sign Out", role: .destructive) {
|
||||
viewModel.googleSignout()
|
||||
}
|
||||
} message: {
|
||||
if let email = viewModel.googleEmail {
|
||||
Text("You are currently signed in with email \(email). It will help to gather more data about vehicles.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SettingsScreen(viewModel: .init(settingService: SettingsService(defaults: .standard)))
|
||||
}
|
||||
62
AutoCat/Screens/SettingsScreen/SettingsViewModel.swift
Normal file
62
AutoCat/Screens/SettingsScreen/SettingsViewModel.swift
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// SettingsViewModel.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
class SettingsViewModel {
|
||||
|
||||
weak var coordinator: SettingsCoordinator?
|
||||
var settingService: SettingsServiceProtocol
|
||||
|
||||
var autocatEmail: String {
|
||||
settingService.user.email
|
||||
}
|
||||
|
||||
var canSignOut: Bool {
|
||||
!settingService.user.token.isEmpty
|
||||
}
|
||||
|
||||
var googleAuthorized: Bool {
|
||||
settingService.user.firebaseIdToken != nil
|
||||
}
|
||||
|
||||
var googleUsername: String? {
|
||||
guard let jwtString = settingService.user.firebaseIdToken,
|
||||
let jwt = JWT<FirebasePayload>(string: jwtString) else { return nil }
|
||||
|
||||
return jwt.payload.name
|
||||
}
|
||||
|
||||
var googleEmail: String? {
|
||||
guard let jwtString = settingService.user.firebaseIdToken,
|
||||
let jwt = JWT<FirebasePayload>(string: jwtString) else { return nil }
|
||||
|
||||
return jwt.payload.email
|
||||
}
|
||||
|
||||
init(settingService: SettingsServiceProtocol) {
|
||||
self.settingService = settingService
|
||||
}
|
||||
|
||||
func signOut() {
|
||||
settingService.user.token = ""
|
||||
coordinator?.openAuthScreen()
|
||||
}
|
||||
|
||||
func googleSignIn() {
|
||||
coordinator?.openGoogleOauthPage()
|
||||
}
|
||||
|
||||
func googleSignout() {
|
||||
settingService.user.firebaseIdToken = nil
|
||||
settingService.user.firebaseRefreshToken = nil
|
||||
}
|
||||
}
|
||||
@ -12,11 +12,18 @@ struct TextRowView: View {
|
||||
|
||||
let title: LocalizedStringKey
|
||||
let value: String?
|
||||
let showArrow: Bool
|
||||
|
||||
init(title: LocalizedStringKey, value: String?, showArrow: Bool = false) {
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.showArrow = showArrow
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
ViewThatFits(in: .horizontal) {
|
||||
SimpleTextRowView(title: title, value: value)
|
||||
SimpleTextRowView(title: title, value: value, showArrow: showArrow)
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(title)
|
||||
HStack {
|
||||
@ -25,11 +32,16 @@ struct TextRowView: View {
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.padding(.leading)
|
||||
if showArrow {
|
||||
Image(systemName: "chevron.right")
|
||||
.padding(.leading, 8)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SimpleTextRowView(title: title, value: value)
|
||||
SimpleTextRowView(title: title, value: value, showArrow: showArrow)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -38,21 +50,35 @@ struct SimpleTextRowView: View {
|
||||
|
||||
let title: LocalizedStringKey
|
||||
let value: String?
|
||||
let showArrow: Bool
|
||||
|
||||
init(title: LocalizedStringKey, value: String?, showArrow: Bool = false) {
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.showArrow = showArrow
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 0) {
|
||||
Text(title)
|
||||
Spacer()
|
||||
Text(value ?? "")
|
||||
.lineLimit(1)
|
||||
.foregroundStyle(.secondary)
|
||||
if showArrow {
|
||||
Image(systemName: "chevron.right")
|
||||
.padding(.leading, 8)
|
||||
.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: "Title", value: "Value", showArrow: true)
|
||||
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.", showArrow: true)
|
||||
TextRowView(title: "Some longer title", value: "Value with long long text")
|
||||
SimpleTextRowView(title: "Some longer title", value: "Value with long long text")
|
||||
}
|
||||
}
|
||||
|
||||
57
AutoCat/SwiftUI/ToggleRowView.swift
Normal file
57
AutoCat/SwiftUI/ToggleRowView.swift
Normal file
@ -0,0 +1,57 @@
|
||||
//
|
||||
// ToggleRowView.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 18.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension VerticalAlignment {
|
||||
|
||||
enum CustomVerticalAlignment: AlignmentID {
|
||||
|
||||
static func defaultValue(in context: ViewDimensions) -> CGFloat {
|
||||
context[.top]
|
||||
}
|
||||
}
|
||||
|
||||
static let customTop = VerticalAlignment(CustomVerticalAlignment.self)
|
||||
}
|
||||
|
||||
struct ToggleRowView: View {
|
||||
|
||||
let title: LocalizedStringKey
|
||||
let description: LocalizedStringKey?
|
||||
let toggle: Binding<Bool>
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .customTop) {
|
||||
VStack(alignment: .leading) {
|
||||
Text(title)
|
||||
.alignmentGuide(.customTop, computeValue: { $0[.firstTextBaseline] })
|
||||
if let description {
|
||||
Text(description)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
Toggle(isOn: toggle) {
|
||||
Text(" ")
|
||||
.alignmentGuide(.customTop, computeValue: { $0[.firstTextBaseline] })
|
||||
}
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
List {
|
||||
ToggleRowView(title: "Title",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas cursus posuere varius. Maecenas diam neque, lacinia molestie tristique eget, faucibus egestas nibh.",
|
||||
toggle: .constant(true))
|
||||
ToggleRowView(title: "Title", description: nil, toggle: .constant(true))
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@ import AudioToolbox
|
||||
import os.log
|
||||
import ExceptionCatcher
|
||||
|
||||
class Recorder {
|
||||
final class Recorder {
|
||||
|
||||
let engine = AVAudioEngine()
|
||||
var fileRef: ExtAudioFileRef? = nil
|
||||
|
||||
@ -6,8 +6,8 @@ public struct User: Codable, Sendable {
|
||||
public var firebaseIdToken: String?
|
||||
public var firebaseRefreshToken: String?
|
||||
|
||||
public init() {
|
||||
self.email = ""
|
||||
self.token = ""
|
||||
public init(email: String = "", token: String = "") {
|
||||
self.email = email
|
||||
self.token = token
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,13 @@ import Foundation
|
||||
|
||||
public actor ApiService: ApiServiceProtocol {
|
||||
|
||||
public static let shared = ApiService()
|
||||
public static let shared = ApiService(settingsService: SettingsService.shared)
|
||||
|
||||
let settingsService: SettingsServiceProtocol
|
||||
|
||||
init(settingsService: SettingsServiceProtocol) {
|
||||
self.settingsService = settingsService
|
||||
}
|
||||
|
||||
private let session: URLSession = {
|
||||
let sessionConfig = URLSessionConfiguration.default
|
||||
@ -13,12 +19,11 @@ public actor ApiService: ApiServiceProtocol {
|
||||
|
||||
// MARK: - Private wrappres
|
||||
|
||||
private func genError(_ msg: String, suggestion: String, code: Int = 0) -> Error {
|
||||
return NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: msg, NSLocalizedRecoverySuggestionErrorKey: suggestion])
|
||||
}
|
||||
|
||||
private func createRequest<B,P>(api: String, method: String, body: B? = nil, params: [String:P]? = nil) -> URLRequest? where B: Encodable, P: LosslessStringConvertible {
|
||||
guard var urlComponents = URLComponents(string: Constants.baseUrl + api) else { return nil }
|
||||
|
||||
let baseUrl = settingsService.useTestBackend ? Constants.baseUrlDebug : Constants.baseUrl
|
||||
|
||||
guard var urlComponents = URLComponents(string: baseUrl + api) else { return nil }
|
||||
|
||||
if let params = params, method.uppercased() == "GET" {
|
||||
urlComponents.queryItems = params.map { URLQueryItem(name: $0, value: String($1)) }
|
||||
@ -42,9 +47,9 @@ public actor ApiService: ApiServiceProtocol {
|
||||
}
|
||||
|
||||
private func makeRequest<T,B,P>(api: String,
|
||||
method: String = "GET",
|
||||
body: B?,
|
||||
params: [String:P]? = nil) async throws -> T where T: Decodable, B: Encodable, P: LosslessStringConvertible {
|
||||
method: String = "GET",
|
||||
body: B?,
|
||||
params: [String:P]? = nil) async throws -> T where T: Decodable, B: Encodable, P: LosslessStringConvertible {
|
||||
|
||||
guard let request = self.createRequest(api: api, method: method, body: body, params: params) else {
|
||||
throw ApiError.generic
|
||||
|
||||
122
AutoCatCore/Services/SettingsService/SettingsService.swift
Normal file
122
AutoCatCore/Services/SettingsService/SettingsService.swift
Normal file
@ -0,0 +1,122 @@
|
||||
//
|
||||
// SettingsService.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
public final class SettingsService: SettingsServiceProtocol {
|
||||
|
||||
public static let shared = SettingsService(defaults: .standard)
|
||||
|
||||
let defaults: UserDefaults
|
||||
var observations: [NSKeyValueObservation] = []
|
||||
|
||||
let jsonEncoder = JSONEncoder()
|
||||
let jsonDecoder = JSONDecoder()
|
||||
|
||||
public var user: User {
|
||||
get {
|
||||
if let data = userData {
|
||||
let result = try? jsonDecoder.decode(User.self, from: data)
|
||||
return result ?? User()
|
||||
} else {
|
||||
return User()
|
||||
}
|
||||
}
|
||||
|
||||
set {
|
||||
if let json = try? jsonEncoder.encode(newValue) {
|
||||
userData = json
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var userData: Data? = nil {
|
||||
didSet {
|
||||
defaults.user = userData
|
||||
}
|
||||
}
|
||||
|
||||
public var recognizeAlternativeOrder: Bool = false {
|
||||
didSet {
|
||||
defaults.recognizeAlternativeOrder = recognizeAlternativeOrder
|
||||
}
|
||||
}
|
||||
|
||||
public var recognizeShortenedNumbers: Bool = false {
|
||||
didSet {
|
||||
defaults.recognizeShortenedNumbers = recognizeShortenedNumbers
|
||||
}
|
||||
}
|
||||
|
||||
public var defaultRegion: String = "161" {
|
||||
didSet {
|
||||
defaults.defaultRegion = defaultRegion
|
||||
}
|
||||
}
|
||||
|
||||
public var recordBeep: Bool = false {
|
||||
didSet {
|
||||
defaults.recordBeep = recordBeep
|
||||
}
|
||||
}
|
||||
|
||||
public var showDebugInfo: Bool = false {
|
||||
didSet {
|
||||
defaults.showDebugInfo = showDebugInfo
|
||||
}
|
||||
}
|
||||
|
||||
public var useTestBackend: Bool = false {
|
||||
didSet {
|
||||
defaults.useTestBackend = useTestBackend
|
||||
}
|
||||
}
|
||||
|
||||
public init(defaults: UserDefaults) {
|
||||
self.defaults = defaults
|
||||
|
||||
observe(key: \.recognizeAlternativeOrder, for: \.recognizeAlternativeOrder)
|
||||
observe(key: \.recognizeShortenedNumbers, for: \.recognizeShortenedNumbers)
|
||||
observe(key: \.defaultRegion, for: \.defaultRegion)
|
||||
observe(key: \.recordBeep, for: \.recordBeep)
|
||||
observe(key: \.showDebugInfo, for: \.showDebugInfo)
|
||||
observe(key: \.useTestBackend, for: \.useTestBackend)
|
||||
observe(key: \.user, for: \.userData)
|
||||
|
||||
register(defaultValues: [
|
||||
.recognizeAlternativeOrder: false,
|
||||
.recognizeShortenedNumbers: false,
|
||||
.defaultRegion: "761",
|
||||
.recordBeep: false,
|
||||
.showDebugInfo: false,
|
||||
.useTestBackend: false
|
||||
])
|
||||
}
|
||||
|
||||
deinit {
|
||||
observations.forEach { $0.invalidate() }
|
||||
}
|
||||
|
||||
func observe<T>(key userDefaultsKey: KeyPath<UserDefaults,T>, for settingsKey: ReferenceWritableKeyPath<SettingsService,T>) where T: Equatable {
|
||||
let observation = defaults.observe(userDefaultsKey, options: [.initial, .new]) { [weak self] _, change in
|
||||
guard let self else { return }
|
||||
|
||||
if let new = change.newValue, self[keyPath: settingsKey] != new {
|
||||
self[keyPath: settingsKey] = new
|
||||
}
|
||||
}
|
||||
observations.append(observation)
|
||||
}
|
||||
|
||||
func register(defaultValues: [SettingsKey: Any]) {
|
||||
defaults.register(defaults: defaultValues.reduce(into: [:], { (partialResult: inout [String: Any], tuple) in
|
||||
partialResult[tuple.key.rawValue] = tuple.value
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
//
|
||||
// SettingsServiceProtocol.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol SettingsServiceProtocol {
|
||||
|
||||
var user: User { get set }
|
||||
|
||||
var recognizeAlternativeOrder: Bool { get set }
|
||||
var recognizeShortenedNumbers: Bool { get set }
|
||||
var defaultRegion: String { get set }
|
||||
var recordBeep: Bool { get set }
|
||||
var showDebugInfo: Bool { get set }
|
||||
var useTestBackend: Bool { get set }
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
//
|
||||
// UserDefaults+Settings.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 25.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum SettingsKey: String {
|
||||
|
||||
case user
|
||||
case recognizeAlternativeOrder
|
||||
case recognizeShortenedNumbers
|
||||
case defaultRegion
|
||||
case recordBeep
|
||||
case showDebugInfo
|
||||
case useTestBackend
|
||||
}
|
||||
|
||||
extension UserDefaults {
|
||||
|
||||
func bool(for key: SettingsKey) -> Bool {
|
||||
bool(forKey: key.rawValue)
|
||||
}
|
||||
|
||||
func string(for key: SettingsKey, defaultValue: String = "") -> String {
|
||||
string(forKey: key.rawValue) ?? defaultValue
|
||||
}
|
||||
|
||||
func data(for key: SettingsKey) -> Data? {
|
||||
data(forKey: key.rawValue)
|
||||
}
|
||||
|
||||
func set<T>(value: T, for key: SettingsKey) {
|
||||
setValue(value, forKey: key.rawValue)
|
||||
}
|
||||
|
||||
@objc dynamic var recognizeAlternativeOrder: Bool {
|
||||
get { bool(for: .recognizeAlternativeOrder) }
|
||||
set { set(value: newValue, for: .recognizeAlternativeOrder) }
|
||||
}
|
||||
|
||||
@objc dynamic var recognizeShortenedNumbers: Bool {
|
||||
get { bool(for: .recognizeShortenedNumbers) }
|
||||
set { set(value: newValue, for: .recognizeShortenedNumbers) }
|
||||
}
|
||||
|
||||
@objc dynamic var defaultRegion: String {
|
||||
get { string(for: .defaultRegion) }
|
||||
set { set(value: newValue, for: .defaultRegion) }
|
||||
}
|
||||
|
||||
@objc dynamic var recordBeep: Bool {
|
||||
get { bool(for: .recordBeep) }
|
||||
set { set(value: newValue, for: .recordBeep) }
|
||||
}
|
||||
|
||||
@objc dynamic var showDebugInfo: Bool {
|
||||
get { bool(for: .showDebugInfo) }
|
||||
set { set(value: newValue, for: .showDebugInfo) }
|
||||
}
|
||||
|
||||
@objc dynamic var useTestBackend: Bool {
|
||||
get { bool(for: .useTestBackend) }
|
||||
set { set(value: newValue, for: .useTestBackend) }
|
||||
}
|
||||
|
||||
@objc dynamic var user: Data? {
|
||||
get { data(for: .user) }
|
||||
set { set(value: newValue, for: .user) }
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,8 @@ public enum Constants {
|
||||
#endif
|
||||
}
|
||||
|
||||
public static var baseUrlDebug = "http://192.168.1.2:3000/"
|
||||
|
||||
public static let pnLettersMap: [Character: Character] = [
|
||||
"А": "A", "В": "B", "Е": "E", "К": "K", "М": "M", "Н": "H", "О": "O", "Р": "P", "С": "C", "Т": "T", "У": "Y", "Х": "X"
|
||||
]
|
||||
|
||||
84
AutoCatCoreTests/SettingsServiceTests.swift
Normal file
84
AutoCatCoreTests/SettingsServiceTests.swift
Normal file
@ -0,0 +1,84 @@
|
||||
//
|
||||
// SettingsServiceTests.swift
|
||||
// AutoCatCoreTests
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import AutoCatCore
|
||||
|
||||
extension Encodable {
|
||||
|
||||
var data: Data? {
|
||||
let encoder = JSONEncoder()
|
||||
return try? encoder.encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsServiceTests {
|
||||
|
||||
let testRegion = "xxx"
|
||||
let testEmail = "test@test.test"
|
||||
let testToken = "testToken"
|
||||
let testUser: User
|
||||
let dbName = "testUserDefaults"
|
||||
|
||||
let defaults: UserDefaults
|
||||
var settingsService: SettingsService
|
||||
|
||||
init() {
|
||||
self.testUser = User(email: testEmail, token: testToken)
|
||||
self.defaults = UserDefaults(suiteName: dbName)!
|
||||
self.defaults.removePersistentDomain(forName: dbName)
|
||||
self.settingsService = SettingsService(defaults: defaults)
|
||||
}
|
||||
|
||||
@Test("Creating default user")
|
||||
mutating func defaultUser() async throws {
|
||||
|
||||
#expect(settingsService.user.email == "")
|
||||
#expect(settingsService.user.token == "")
|
||||
}
|
||||
|
||||
@Test("Loading saved user")
|
||||
func loadSavedUser() async throws {
|
||||
|
||||
let data = try #require(testUser.data)
|
||||
|
||||
defaults.setPersistentDomain(["user": data], forName: dbName)
|
||||
let service = SettingsService(defaults: defaults)
|
||||
|
||||
#expect(service.user.email == testEmail)
|
||||
#expect(service.user.token == testToken)
|
||||
}
|
||||
|
||||
@Test("Save user")
|
||||
func saveUser() async throws {
|
||||
|
||||
settingsService.user = testUser
|
||||
|
||||
let userData = defaults.data(forKey: "user")
|
||||
#expect(userData == testUser.data)
|
||||
}
|
||||
|
||||
@Test("Save settings", .serialized, arguments: [true, false])
|
||||
func saveSettings(value: Bool) {
|
||||
|
||||
settingsService.recognizeAlternativeOrder = value
|
||||
settingsService.recognizeShortenedNumbers = value
|
||||
settingsService.defaultRegion = testRegion
|
||||
settingsService.recordBeep = value
|
||||
settingsService.showDebugInfo = value
|
||||
settingsService.useTestBackend = value
|
||||
|
||||
#expect(defaults.bool(forKey: SettingsKey.recognizeAlternativeOrder.rawValue) == value)
|
||||
#expect(defaults.bool(forKey: SettingsKey.recognizeShortenedNumbers.rawValue) == value)
|
||||
#expect(defaults.string(forKey: SettingsKey.defaultRegion.rawValue) == testRegion)
|
||||
#expect(defaults.bool(forKey: SettingsKey.recordBeep.rawValue) == value)
|
||||
#expect(defaults.bool(forKey: SettingsKey.showDebugInfo.rawValue) == value)
|
||||
#expect(defaults.bool(forKey: SettingsKey.useTestBackend.rawValue) == value)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user