66//
77
88import SwiftUI
9- import Combine
9+ @ preconcurrency import Combine
1010
1111// MARK: - Model
1212struct User : Sendable {
@@ -16,7 +16,7 @@ struct User: Sendable {
1616enum LoginError : Error , LocalizedError , Sendable {
1717 case invalidCredentials
1818 case networkError
19-
19+
2020 var errorDescription : String ? {
2121 switch self {
2222 case . invalidCredentials:
@@ -28,36 +28,39 @@ enum LoginError: Error, LocalizedError, Sendable {
2828}
2929
3030// MARK: - Login Service with Combine
31- @ MainActor
31+
3232final class LoginService : Sendable {
3333 // Subject that never completes, just emits success or failure results
3434 let loginStatus = PassthroughSubject < Result < User , LoginError > , Never > ( )
35-
36- nonisolated func login( username: String , password: String ) {
37- Task { @ MainActor in
35+
36+ func login( username: String , password: String ) async {
37+ do {
3838 try await Task . sleep ( nanoseconds: 1_000_000_000 ) // 1 second delay
39-
40- if username == " admin " && password == " 1234 " {
41- loginStatus. send ( . success( User ( username: username) ) )
42- } else {
43- loginStatus. send ( . failure( . invalidCredentials) )
44- }
39+ } catch {
40+ print ( " manage error here " )
41+ }
42+
43+ if username == " admin " && password == " 1234 " {
44+ loginStatus. send ( . success( User ( username: username) ) )
45+ } else {
46+ loginStatus. send ( . failure( . invalidCredentials) )
4547 }
4648 }
4749}
4850
4951// MARK: - ViewModel
5052@MainActor
53+
5154final class LoginViewModel : ObservableObject {
5255 @Published var username : String = " "
5356 @Published var password : String = " "
5457 @Published var errorMessage : String ?
5558 @Published var isLoggedIn : Bool = false
5659 @Published var isLoading : Bool = false
57-
60+
5861 private var cancellables = Set < AnyCancellable > ( )
5962 private let loginService = LoginService ( )
60-
63+
6164 init ( ) {
6265 loginService. loginStatus
6366 . receive ( on: DispatchQueue . main)
@@ -74,44 +77,48 @@ final class LoginViewModel: ObservableObject {
7477 }
7578 . store ( in: & cancellables)
7679 }
77-
78- func login( ) {
80+
81+ func login( ) async {
7982 errorMessage = nil
8083 isLoading = true
81- loginService. login ( username: username, password: password)
84+ Task . detached { [ self ] in
85+ await self . loginService. login ( username: username, password: password)
86+ }
8287 }
8388}
8489
8590// MARK: - View
8691struct LoginView : View {
8792 @StateObject private var viewModel = LoginViewModel ( )
88-
93+
8994 var body : some View {
9095 NavigationStack {
9196 VStack ( spacing: 16 ) {
9297 TextField ( " Username " , text: $viewModel. username)
9398 . textFieldStyle ( . roundedBorder)
9499 . autocapitalization ( . none)
95-
100+
96101 SecureField ( " Password " , text: $viewModel. password)
97102 . textFieldStyle ( . roundedBorder)
98-
103+
99104 if let errorMessage = viewModel. errorMessage {
100105 Text ( errorMessage)
101106 . foregroundColor ( . red)
102107 . font ( . footnote)
103108 . padding ( . top, 4 )
104109 }
105-
110+
106111 if viewModel. isLoading {
107112 ProgressView ( " Logging in... " )
108113 } else {
109114 Button ( " Login " ) {
110- viewModel. login ( )
115+ Task {
116+ await viewModel. login ( )
117+ }
111118 }
112119 . padding ( . top)
113120 }
114-
121+
115122 Spacer ( )
116123 }
117124 . padding ( )
0 commit comments