@@ -1,65 +1,66 @@ | |||
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() | |||
} | |||
} | |||
@@ -99,6 +100,7 @@ struct AccountLoginView: View { | |||
struct AccountLoginView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
AccountLoginView(account: AccountModel()) | |||
AccountLoginView() | |||
.environmentObject(WriteFreelyModel()) | |||
} | |||
} |
@@ -1,14 +1,14 @@ | |||
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: { | |||
@@ -18,12 +18,13 @@ struct AccountLogoutView: View { | |||
} | |||
func logoutHandler() { | |||
account.logout() | |||
model.logout() | |||
} | |||
} | |||
struct AccountLogoutView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
AccountLogoutView(account: AccountModel()) | |||
AccountLogoutView() | |||
.environmentObject(WriteFreelyModel()) | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
import Foundation | |||
import WriteFreely | |||
enum AccountError: Error { | |||
case invalidPassword | |||
@@ -6,66 +7,18 @@ enum AccountError: Error { | |||
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<UUID, AccountError>) -> Void | |||
) { | |||
self.isLoggingIn = true | |||
let result: Result<UUID, AccountError> | |||
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 |
@@ -1,18 +1,18 @@ | |||
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() | |||
} | |||
} | |||
@@ -20,6 +20,7 @@ struct AccountView: View { | |||
struct AccountLogin_Previews: PreviewProvider { | |||
static var previews: some View { | |||
AccountView(account: AccountModel()) | |||
AccountView() | |||
.environmentObject(WriteFreelyModel()) | |||
} | |||
} |
@@ -1,4 +1,5 @@ | |||
import Foundation | |||
import WriteFreely | |||
// MARK: - WriteFreelyModel | |||
@@ -7,6 +8,9 @@ class WriteFreelyModel: ObservableObject { | |||
@Published var preferences = PreferencesModel() | |||
@Published var store = PostStore() | |||
@Published var post: Post? | |||
@Published var isLoggingIn: Bool = false | |||
private var client: WFClient? | |||
init() { | |||
#if DEBUG | |||
@@ -18,5 +22,44 @@ class WriteFreelyModel: ObservableObject { | |||
// 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<WFUser, Error>) { | |||
isLoggingIn = false | |||
do { | |||
let user = try result.get() | |||
account.login(user) | |||
dump(user) | |||
} catch { | |||
dump(error) | |||
} | |||
} | |||
func logoutHandler(result: Result<Bool, Error>) { | |||
do { | |||
let loggedOut = try result.get() | |||
if loggedOut { | |||
client = nil | |||
account.logout() | |||
} | |||
} catch { | |||
dump(error) | |||
} | |||
} | |||
} |
@@ -18,7 +18,8 @@ struct WriteFreely_MultiPlatformApp: App { | |||
#if os(macOS) | |||
Settings { | |||
TabView(selection: $selectedTab) { | |||
MacAccountView(account: model.account) | |||
MacAccountView() | |||
.environmentObject(model) | |||
.tabItem { | |||
Image(systemName: "person.crop.circle") | |||
Text("Account") | |||
@@ -10,7 +10,7 @@ struct SettingsView: View { | |||
SettingsHeaderView(isPresented: $isPresented) | |||
Form { | |||
Section(header: Text("Login Details")) { | |||
AccountView(account: model.account) | |||
AccountView() | |||
} | |||
Section(header: Text("Appearance")) { | |||
PreferencesView(preferences: model.preferences) | |||
@@ -1,12 +1,12 @@ | |||
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() | |||
} | |||
} | |||
} | |||
@@ -14,6 +14,7 @@ struct MacAccountView: View { | |||
struct MacAccountView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
MacAccountView(account: AccountModel()) | |||
MacAccountView() | |||
.environmentObject(WriteFreelyModel()) | |||
} | |||
} |