diff --git a/Shared/Account/AccountLoginView.swift b/Shared/Account/AccountLoginView.swift index cab3827..dfaeb99 100644 --- a/Shared/Account/AccountLoginView.swift +++ b/Shared/Account/AccountLoginView.swift @@ -1,104 +1,106 @@ import SwiftUI struct AccountLoginView: View { - @ObservedObject var account: AccountModel + @EnvironmentObject var model: WriteFreelyModel @State private var isShowingAlert: Bool = false @State private var alertMessage: String = "" - + @State private var username: String = "" + @State private var password: String = "" + @State private var server: String = "" var body: some View { VStack { HStack { Image(systemName: "person.circle") .foregroundColor(.gray) #if os(iOS) - TextField("Username", text: $account.username) + TextField("Username", text: $username) .autocapitalization(.none) .disableAutocorrection(true) .textFieldStyle(RoundedBorderTextFieldStyle()) #else - TextField("Username", text: $account.username) + TextField("Username", text: $username) #endif } HStack { Image(systemName: "lock.circle") .foregroundColor(.gray) #if os(iOS) - SecureField("Password", text: $account.password) + SecureField("Password", text: $password) .autocapitalization(.none) .disableAutocorrection(true) .textFieldStyle(RoundedBorderTextFieldStyle()) #else - SecureField("Password", text: $account.password) + SecureField("Password", text: $password) #endif } HStack { Image(systemName: "link.circle") .foregroundColor(.gray) #if os(iOS) - TextField("Server URL", text: $account.server) + TextField("Server URL", text: $server) .keyboardType(.URL) .autocapitalization(.none) .disableAutocorrection(true) .textFieldStyle(RoundedBorderTextFieldStyle()) #else - TextField("Server URL", text: $account.server) + TextField("Server URL", text: $server) #endif } Spacer() - if account.isLoggingIn { + if model.isLoggingIn { ProgressView("Logging in...") .padding() } else { Button(action: { - account.login( - to: account.server, - as: account.username, password: account.password, - completion: loginHandler + model.login( + to: URL(string: server)!, + as: username, password: password ) }, label: { Text("Login") }) - .disabled(account.isLoggedIn) + .disabled(model.account.isLoggedIn) .padding() } } .alert(isPresented: $isShowingAlert) { Alert( title: Text("Error Logging In"), message: Text(alertMessage), dismissButton: .default(Text("OK")) ) } } func loginHandler(result: Result) { do { _ = try result.get() } catch AccountError.serverNotFound { alertMessage = """ The server could not be found. Please check that you've entered the information correctly and try again. """ isShowingAlert = true } catch AccountError.invalidPassword { alertMessage = """ Invalid password. Please check that you've entered your password correctly and try logging in again. """ isShowingAlert = true } catch AccountError.usernameNotFound { alertMessage = """ Username not found. Did you use your email address by mistake? """ isShowingAlert = true } catch { alertMessage = "An unknown error occurred. Please try again." isShowingAlert = true } } } struct AccountLoginView_Previews: PreviewProvider { static var previews: some View { - AccountLoginView(account: AccountModel()) + AccountLoginView() + .environmentObject(WriteFreelyModel()) } } diff --git a/Shared/Account/AccountLogoutView.swift b/Shared/Account/AccountLogoutView.swift index bc84b50..16b9fd0 100644 --- a/Shared/Account/AccountLogoutView.swift +++ b/Shared/Account/AccountLogoutView.swift @@ -1,29 +1,30 @@ import SwiftUI struct AccountLogoutView: View { - @ObservedObject var account: AccountModel + @EnvironmentObject var model: WriteFreelyModel var body: some View { VStack { Spacer() VStack { - Text("Logged in as \(account.username)") - Text("on \(account.server)") + Text("Logged in as \(model.account.user?.username ?? "Anonymous")") + Text("on \(model.account.server)") } Spacer() Button(action: logoutHandler, label: { Text("Logout") }) } } func logoutHandler() { - account.logout() + model.logout() } } struct AccountLogoutView_Previews: PreviewProvider { static var previews: some View { - AccountLogoutView(account: AccountModel()) + AccountLogoutView() + .environmentObject(WriteFreelyModel()) } } diff --git a/Shared/Account/AccountModel.swift b/Shared/Account/AccountModel.swift index 9d26b3c..c1ee3c8 100644 --- a/Shared/Account/AccountModel.swift +++ b/Shared/Account/AccountModel.swift @@ -1,71 +1,24 @@ import Foundation +import WriteFreely enum AccountError: Error { case invalidPassword case usernameNotFound case serverNotFound } -class AccountModel: ObservableObject { - @Published private(set) var id: UUID? - @Published private(set) var isLoggedIn: Bool = false - @Published private(set) var isLoggingIn: Bool = false - @Published var username: String = "" - @Published var password: String = "" - @Published var server: String = "" +struct AccountModel { + var server: String = "" + private(set) var user: WFUser? + private(set) var isLoggedIn: Bool = false - func login( - to server: String, - as username: String, - password: String, - completion: @escaping (Result) -> Void - ) { - self.isLoggingIn = true - let result: Result - - if server != validServer { - result = .failure(.serverNotFound) - } else if username != validCredentials["username"] { - result = .failure(.usernameNotFound) - } else if password != validCredentials["password"] { - result = .failure(.invalidPassword) - } else { - self.id = UUID() - self.username = username - self.password = password - self.server = server - result = .success(self.id!) - } - - #if DEBUG - // Delay to simulate async network call - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - self.isLoggingIn = false - do { - _ = try result.get() - self.isLoggedIn = true - } catch { - self.isLoggedIn = false - } - completion(result) - } - #endif + mutating func login(_ user: WFUser) { + self.user = user + self.isLoggedIn = true } - func logout() { - id = nil - isLoggedIn = false - isLoggingIn = false - username = "" - password = "" - server = "" + mutating func logout() { + self.user = nil + self.isLoggedIn = false } } - -#if DEBUG -let validCredentials = [ - "username": "test-writer", - "password": "12345" -] -let validServer = "https://test.server.url" -#endif diff --git a/Shared/Account/AccountView.swift b/Shared/Account/AccountView.swift index a0382d7..64f23a5 100644 --- a/Shared/Account/AccountView.swift +++ b/Shared/Account/AccountView.swift @@ -1,25 +1,26 @@ import SwiftUI struct AccountView: View { - @ObservedObject var account: AccountModel + @EnvironmentObject var model: WriteFreelyModel var body: some View { - if account.isLoggedIn { + if model.account.isLoggedIn { HStack { Spacer() - AccountLogoutView(account: account) + AccountLogoutView() Spacer() } .padding() } else { - AccountLoginView(account: account) + AccountLoginView() .padding() } } } struct AccountLogin_Previews: PreviewProvider { static var previews: some View { - AccountView(account: AccountModel()) + AccountView() + .environmentObject(WriteFreelyModel()) } } diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index e84d65c..d926ee1 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -1,22 +1,65 @@ import Foundation +import WriteFreely // MARK: - WriteFreelyModel class WriteFreelyModel: ObservableObject { @Published var account = AccountModel() @Published var preferences = PreferencesModel() @Published var store = PostStore() @Published var post: Post? + @Published var isLoggingIn: Bool = false + + private var client: WFClient? init() { #if DEBUG for post in testPostData { store.add(post) } #endif } } // MARK: - WriteFreelyModel API extension WriteFreelyModel { - // API goes here + func login( + to server: URL, + as username: String, + password: String + ) { + isLoggingIn = true + account.server = server.absoluteString + client = WFClient(for: server) + client?.login(username: username, password: password, completion: loginHandler) + } + + func logout () { + guard let loggedInClient = client else { return } + loggedInClient.logout(completion: logoutHandler) + } +} + +private extension WriteFreelyModel { + func loginHandler(result: Result) { + isLoggingIn = false + do { + let user = try result.get() + account.login(user) + dump(user) + } catch { + dump(error) + } + } + + func logoutHandler(result: Result) { + do { + let loggedOut = try result.get() + if loggedOut { + client = nil + account.logout() + } + } catch { + dump(error) + } + } } diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 804f6b1..63c1182 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -1,40 +1,41 @@ import SwiftUI @main struct WriteFreely_MultiPlatformApp: App { @StateObject private var model = WriteFreelyModel() #if os(macOS) @State private var selectedTab = 0 #endif var body: some Scene { WindowGroup { ContentView() .environmentObject(model) // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } #if os(macOS) Settings { TabView(selection: $selectedTab) { - MacAccountView(account: model.account) + MacAccountView() + .environmentObject(model) .tabItem { Image(systemName: "person.crop.circle") Text("Account") } .tag(0) MacPreferencesView(preferences: model.preferences) .tabItem { Image(systemName: "gear") Text("Preferences") } .tag(1) } .frame(minWidth: 300, maxWidth: 300, minHeight: 200, maxHeight: 200) .padding() // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } #endif } } diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index 0de3734..c4718c5 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -1,29 +1,29 @@ import SwiftUI struct SettingsView: View { @EnvironmentObject var model: WriteFreelyModel @Binding var isPresented: Bool var body: some View { VStack { SettingsHeaderView(isPresented: $isPresented) Form { Section(header: Text("Login Details")) { - AccountView(account: model.account) + AccountView() } Section(header: Text("Appearance")) { PreferencesView(preferences: model.preferences) } } } // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } } struct SettingsView_Previews: PreviewProvider { static var previews: some View { SettingsView(isPresented: .constant(true)) .environmentObject(WriteFreelyModel()) } } diff --git a/macOS/Settings/MacAccountView.swift b/macOS/Settings/MacAccountView.swift index b37b6da..e51dfd7 100644 --- a/macOS/Settings/MacAccountView.swift +++ b/macOS/Settings/MacAccountView.swift @@ -1,19 +1,20 @@ import SwiftUI struct MacAccountView: View { - @ObservedObject var account: AccountModel + @EnvironmentObject var model: WriteFreelyModel var body: some View { Form { Section(header: Text("Login Details")) { - AccountView(account: account) + AccountView() } } } } struct MacAccountView_Previews: PreviewProvider { static var previews: some View { - MacAccountView(account: AccountModel()) + MacAccountView() + .environmentObject(WriteFreelyModel()) } }