SwiftUI version of auth screen

This commit is contained in:
Selim Mustafaev 2025-04-06 13:38:10 +03:00
parent a9bbbfa35b
commit 406b1b02b5
11 changed files with 243 additions and 4 deletions

View File

@ -194,6 +194,10 @@
7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.swift */; }; 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.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 */; };
7AF231932DA1C28100AE5EB3 /* AuthScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */; };
7AF231952DA1C29300AE5EB3 /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */; };
7AF231972DA1C30000AE5EB3 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */; };
7AF231992DA27C1B00AE5EB3 /* ACButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */; };
7AF6D2042677C03B0086EA64 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; }; 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, ); }; }; 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 */; }; 7AF6D2122677C12E0086EA64 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A000AA124C2EEDE001F5B00 /* Location.swift */; };
@ -477,6 +481,10 @@
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>"; };
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>"; };
7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = "<group>"; };
7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACButtonView.swift; sourceTree = "<group>"; };
7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AutoCatCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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>"; }; 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>"; }; 7AF6D1F22677C03B0086EA64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -718,6 +726,7 @@
7A1441632C297E9800E79018 /* Screens */ = { 7A1441632C297E9800E79018 /* Screens */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7AF231912DA1C26C00AE5EB3 /* AuthScreen */,
7A95197E2D80B69800E69883 /* RecordsScreen */, 7A95197E2D80B69800E69883 /* RecordsScreen */,
7A5911EC2D63225500EC51BA /* SearchScreen */, 7A5911EC2D63225500EC51BA /* SearchScreen */,
7A131FD12D37B74100DC7755 /* HistoryScreen */, 7A131FD12D37B74100DC7755 /* HistoryScreen */,
@ -1116,6 +1125,16 @@
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7AF231912DA1C26C00AE5EB3 /* AuthScreen */ = {
isa = PBXGroup;
children = (
7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */,
7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */,
7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */,
);
path = AuthScreen;
sourceTree = "<group>";
};
7AF6D1F02677C03B0086EA64 /* AutoCatCore */ = { 7AF6D1F02677C03B0086EA64 /* AutoCatCore */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1177,6 +1196,7 @@
7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */, 7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */,
7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */, 7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */,
7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */, 7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */,
7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */,
); );
path = SwiftUI; path = SwiftUI;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1446,6 +1466,7 @@
7A7158122C444A6400852088 /* AdsViewModel.swift in Sources */, 7A7158122C444A6400852088 /* AdsViewModel.swift in Sources */,
7A1E78FA2CE9005C0004B740 /* ReportCoordinator.swift in Sources */, 7A1E78FA2CE9005C0004B740 /* ReportCoordinator.swift in Sources */,
7A71580E2C4445A200852088 /* AdsCoordinator.swift in Sources */, 7A71580E2C4445A200852088 /* AdsCoordinator.swift in Sources */,
7AF231952DA1C29300AE5EB3 /* AuthViewModel.swift in Sources */,
7AB4E4662D58A16C0006D052 /* GenericError.swift in Sources */, 7AB4E4662D58A16C0006D052 /* GenericError.swift in Sources */,
7AFBE8CA2C3081C7003C491D /* ACProgressHud+Modifiers.swift in Sources */, 7AFBE8CA2C3081C7003C491D /* ACProgressHud+Modifiers.swift in Sources */,
7A71EF572D0A26B200943129 /* EventModel.swift in Sources */, 7A71EF572D0A26B200943129 /* EventModel.swift in Sources */,
@ -1460,6 +1481,7 @@
7AC76D7B270083AE0084DB27 /* TextView.swift in Sources */, 7AC76D7B270083AE0084DB27 /* TextView.swift in Sources */,
7A2C96122C3B155B00AE46B5 /* NoteAlertModifier.swift in Sources */, 7A2C96122C3B155B00AE46B5 /* NoteAlertModifier.swift in Sources */,
7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */, 7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */,
7AF231972DA1C30000AE5EB3 /* AuthCoordinator.swift in Sources */,
7A11471A23FE839000B424AF /* AuthController.swift in Sources */, 7A11471A23FE839000B424AF /* AuthController.swift in Sources */,
7A5911F22D63268400EC51BA /* SearchCoordinator.swift in Sources */, 7A5911F22D63268400EC51BA /* SearchCoordinator.swift in Sources */,
7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */, 7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */,
@ -1474,6 +1496,7 @@
7ADF6C9F251201D200F237B2 /* GlobalEventsController.swift in Sources */, 7ADF6C9F251201D200F237B2 /* GlobalEventsController.swift in Sources */,
7A1022792C557ED600B84627 /* LocationPickerViewModel.swift in Sources */, 7A1022792C557ED600B84627 /* LocationPickerViewModel.swift in Sources */,
7A11470323FDE7E500B424AF /* SceneDelegate.swift in Sources */, 7A11470323FDE7E500B424AF /* SceneDelegate.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 */,
7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */, 7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */,
@ -1491,6 +1514,7 @@
7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */, 7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */,
7A06E0AC2C7065AC005731AC /* SettingsScreen.swift in Sources */, 7A06E0AC2C7065AC005731AC /* SettingsScreen.swift in Sources */,
7A4322952CB2CD0F00085CF6 /* FiltersCoordinator.swift in Sources */, 7A4322952CB2CD0F00085CF6 /* FiltersCoordinator.swift in Sources */,
7AF231992DA27C1B00AE5EB3 /* ACButtonView.swift in Sources */,
7A131FD72D37B77E00DC7755 /* HistoryCoordinator.swift in Sources */, 7A131FD72D37B77E00DC7755 /* HistoryCoordinator.swift in Sources */,
7A7158002C43EA6900852088 /* OwnersScreen.swift in Sources */, 7A7158002C43EA6900852088 /* OwnersScreen.swift in Sources */,
7A4955822D58CCF900912E66 /* HistoryFilter.swift in Sources */, 7A4955822D58CCF900912E66 /* HistoryFilter.swift in Sources */,

View File

@ -84,7 +84,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let settingsService = ServiceContainer.shared.resolve(SettingsServiceProtocol.self) let settingsService = ServiceContainer.shared.resolve(SettingsServiceProtocol.self)
if settingsService.user.token.isEmpty { if settingsService.user.token.isEmpty {
self.window?.rootViewController = storyboard.instantiateViewController(identifier: "AuthController") let coordinator = AuthCoordinator(window: self.window)
self.window?.rootViewController = coordinator.start()
//self.window?.rootViewController = storyboard.instantiateViewController(identifier: "AuthController")
} else { } else {
self.window?.rootViewController = storyboard.instantiateViewController(identifier: "MainSplitController") self.window?.rootViewController = storyboard.instantiateViewController(identifier: "MainSplitController")
if let number { if let number {

View File

@ -0,0 +1,43 @@
//
// AuthCoordinator.swift
// AutoCat
//
// Created by Selim Mustafaev on 05.04.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import UIKit
import SwiftUI
import AutoCatCore
@MainActor
final class AuthCoordinator {
let window: UIWindow?
init(window: UIWindow?) {
self.window = window
}
func start() -> UIViewController {
let resolver = ServiceContainer.shared
let viewModel = AuthViewModel(
apiService: resolver.resolve(ApiServiceProtocol.self),
storageService: resolver.resolve(StorageServiceProtocol.self),
settingsService: resolver.resolve(SettingsServiceProtocol.self)
)
viewModel.coordinator = self
let view = AuthScreen(viewModel: viewModel)
return UIHostingController(rootView: view)
}
func openMainScreen() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
window?.rootViewController = storyboard.instantiateViewController(identifier: "MainSplitController")
}
}

View File

@ -0,0 +1,47 @@
//
// AuthScreen.swift
// AutoCat
//
// Created by Selim Mustafaev on 05.04.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftUI
struct AuthScreen: View {
@State var viewModel: AuthViewModel
var body: some View {
ZStack {
VStack(spacing: 16) {
Group {
TextField("Email", text: $viewModel.email, prompt: Text("Email"))
.keyboardType(.emailAddress)
SecureField("Password", text: $viewModel.password, prompt: Text("Password"))
}
.padding(8)
.background {
RoundedRectangle(cornerRadius: 8)
.fill(.background)
.stroke(.secondary)
}
Group {
ACButtonView(title: "Log In") {
Task { await viewModel.login() }
}
ACButtonView(title: "Sign up") {
Task { await viewModel.signup() }
}
}
.disabled(!viewModel.actionsEnabled)
.opacity(viewModel.actionsEnabled ? 1 : 0.5)
}
.padding(.horizontal, 32)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.hud($viewModel.hud)
.onAppear(perform: viewModel.onAppear)
}
}

View File

@ -0,0 +1,72 @@
//
// AuthViewModel.swift
// AutoCat
//
// Created by Selim Mustafaev on 05.04.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftUI
import AutoCatCore
@MainActor
@Observable
final class AuthViewModel: ACHudContainer {
let apiService: ApiServiceProtocol
let storageService: StorageServiceProtocol
var settingsService: SettingsServiceProtocol
var coordinator: AuthCoordinator?
var hud: ACHud?
var email: String = ""
var password: String = ""
var actionsEnabled: Bool {
email.count >= 5 && password.count >= 5
}
init(
apiService: ApiServiceProtocol,
storageService: StorageServiceProtocol,
settingsService: SettingsServiceProtocol
) {
self.apiService = apiService
self.storageService = storageService
self.settingsService = settingsService
}
func onAppear() {
if settingsService.user.email.count > 0 {
email = settingsService.user.email
}
}
func login() async {
await wrapWithToast { [weak self] in
guard let self else { return }
let user = try await apiService.login(email: email, password: password)
try await saveUser(user)
coordinator?.openMainScreen()
}
}
func signup() async {
await wrapWithToast { [weak self] in
guard let self else { return }
let user = try await apiService.signUp(email: email, password: password)
try await saveUser(user)
coordinator?.openMainScreen()
}
}
func saveUser(_ user: User) async throws {
if user.email != settingsService.user.email {
try await storageService.deleteAll()
}
settingsService.user = user
}
}

View File

@ -36,9 +36,12 @@ class SettingsCoordinator: Coordinator {
} }
func openAuthScreen() { func openAuthScreen() {
guard let window = viewController?.tabBar.window else {
return
}
let storyboard = UIStoryboard(name: "Main", bundle: nil) let coordinator = AuthCoordinator(window: window)
viewController?.view.window?.rootViewController = storyboard.instantiateViewController(identifier: "AuthController") window.rootViewController = coordinator.start()
} }
func openGoogleOauthPage() { func openGoogleOauthPage() {

View File

@ -0,0 +1,32 @@
//
// ACButtonView.swift
// AutoCat
//
// Created by Selim Mustafaev on 06.04.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftUI
struct ACButtonView: View {
let title: LocalizedStringKey
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
Text(title)
.frame(maxWidth: .infinity)
.padding(12)
.foregroundStyle(.white)
.background {
RoundedRectangle(cornerRadius: 8)
.fill(.blue)
}
}
}
}
#Preview {
ACButtonView(title: "Button") {}
}

View File

@ -206,7 +206,7 @@
"Location adding time" = "Время добавления локаций"; "Location adding time" = "Время добавления локаций";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Log In" = "Войти"; "Log In" = "Вход";
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"Main filters" = "Основные фильтры"; "Main filters" = "Основные фильтры";
@ -423,3 +423,7 @@
"Are you sure you want to delete this event?" = "Вы уверены, что хотите удалить это событие?"; "Are you sure you want to delete this event?" = "Вы уверены, что хотите удалить это событие?";
"Voice records" = "Голосовые записи"; "Voice records" = "Голосовые записи";
"Password" = "Пароль";
"Sign up" = "Регистрация";

View File

@ -11,6 +11,9 @@ import Mockable
@Mockable @Mockable
public protocol ApiServiceProtocol: Sendable { public protocol ApiServiceProtocol: Sendable {
func login(email: String, password: String) async throws -> User
func signUp(email: String, password: String) async throws -> User
func add(notes: [VehicleNoteDto], to number: String) async throws -> VehicleDto func add(notes: [VehicleNoteDto], to number: String) async throws -> VehicleDto
func edit(note: VehicleNoteDto) async throws -> VehicleDto func edit(note: VehicleNoteDto) async throws -> VehicleDto
func remove(note id: String) async throws -> VehicleDto func remove(note id: String) async throws -> VehicleDto

View File

@ -28,6 +28,13 @@ public actor StorageService: StorageServiceProtocol {
} }
} }
public func deleteAll() async throws {
try await realm.asyncWrite {
realm.deleteAll()
}
}
@discardableResult @discardableResult
public func updateVehicle(dto: VehicleDto, policy: DbUpdatePolicy) async throws -> Bool { public func updateVehicle(dto: VehicleDto, policy: DbUpdatePolicy) async throws -> Bool {

View File

@ -15,6 +15,8 @@ public protocol StorageServiceProtocol: Sendable {
// Generic // Generic
var dbFileURL: URL? { get async } var dbFileURL: URL? { get async }
func deleteAll() async throws
// Vehicles // Vehicles
func loadVehicles() async -> [VehicleDto] func loadVehicles() async -> [VehicleDto]
func loadVehicle(number: String) async throws -> VehicleDto func loadVehicle(number: String) async throws -> VehicleDto