Files
NetNewsWire/iOS/Account/NewsBlurAddAccountView.swift
2023-09-16 22:04:43 -07:00

192 lines
5.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// NewsBlurAddAccountView.swift
// NetNewsWire-iOS
//
// Created by Stuart Breckenridge on 18/12/2022.
// Copyright © 2022 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
import Secrets
import RSWeb
import RSCore
@MainActor struct NewsBlurAddAccountView: View, Logging {
@Environment(\.dismiss) private var dismiss
@State var account: Account? = nil
@State private var accountUserName: String = ""
@State private var accountPassword: String = ""
@State private var showProgressIndicator: Bool = false
@State private var accountError: (Error?, Bool) = (nil, false)
var body: some View {
NavigationView {
Form {
AccountSectionHeader(accountType: .newsBlur)
accountDetails
accountButton
Section(footer: newsBlurAccountExplainer) {}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { dismiss() }, label: { Text("button.title.cancel", comment: "Cancel") })
.disabled(showProgressIndicator)
}
ToolbarItem(placement: .navigationBarTrailing) {
if showProgressIndicator { ProgressView() }
}
}
.navigationTitle(Text(AccountType.newsBlur.localizedAccountName()))
.navigationBarTitleDisplayMode(.inline)
.task {
retreiveCredentials()
}
.alert(Text("alert.title.error", comment: "Error"), isPresented: $accountError.1) {
} message: {
Text(verbatim: accountError.0?.localizedDescription ?? "")
}
.interactiveDismissDisabled(showProgressIndicator)
.dismissOnExternalContextLaunch()
.dismissOnAccountAdd()
}
}
func retreiveCredentials() {
if let account = account {
let credentials = try? account.retrieveCredentials(type: .newsBlurBasic)
if let credentials = credentials {
self.accountUserName = credentials.username
self.accountPassword = credentials.secret
}
}
}
var accountDetails: some View {
Section {
TextField("Email", text: $accountUserName, prompt: Text("textfield.placeholder.username-or-email", comment: "Username or Email"))
.autocorrectionDisabled()
.autocapitalization(.none)
.textContentType(.username)
SecureField("Password", text: $accountPassword, prompt: Text("textfield.placeholder.password", comment: "Password"))
.textContentType(.password)
}
}
var accountButton: some View {
Section {
Button {
Task {
do {
if account == nil {
// Create a new account
try await executeAccountCredentials()
} else {
// Updating account credentials
try await executeAccountCredentials()
dismiss()
}
} catch {
accountError = (error, true)
}
}
} label: {
HStack{
Spacer()
if account == nil {
Text("button.title.add-account", comment: "Add Account")
} else {
Text("button.title.update-credentials", comment: "Update Credentials")
}
Spacer()
}
}
.disabled(!validateCredentials())
}
}
var newsBlurAccountExplainer: some View {
if account == nil {
return Text("label.text.newsblur-explainer", comment: "Sign in to your NewsBlur account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDont have a NewsBlur account? [Sign Up Here](https://newsblur.com)")
.multilineTextAlignment(.center)
}
return Text("").multilineTextAlignment(.center)
}
private func validateCredentials() -> Bool {
if (accountUserName.trimmingWhitespace.count == 0) || (accountPassword.trimmingWhitespace.count == 0) {
return false
}
return true
}
private func executeAccountCredentials() async throws {
let trimmedUsername = accountUserName.trimmingWhitespace
guard (account != nil || !AccountManager.shared.duplicateServiceAccount(type: .newsBlur, username: trimmedUsername)) else {
throw LocalizedNetNewsWireError.duplicateAccount
}
showProgressIndicator = true
let basicCredentials = Credentials(type: .newsBlurBasic, username: trimmedUsername, secret: accountPassword)
return try await withCheckedThrowingContinuation { continuation in
Account.validateCredentials(type: .newsBlur, credentials: basicCredentials) { result in
switch result {
case .success(let credentials):
if let sessionsCredentials = credentials {
if self.account == nil {
self.account = AccountManager.shared.createAccount(type: .newsBlur)
}
do {
do {
try self.account?.removeCredentials(type: .newsBlurBasic)
try self.account?.removeCredentials(type: .newsBlurSessionID)
} catch {
NewsBlurAddAccountView.logger.error("\(error.localizedDescription)")
}
try self.account?.storeCredentials(basicCredentials)
try self.account?.storeCredentials(sessionsCredentials)
self.account?.refreshAll(completion: { result in
switch result {
case .success(_):
showProgressIndicator = false
continuation.resume()
return
case .failure(let failure):
showProgressIndicator = false
continuation.resume(throwing: failure)
return
}
})
} catch {
showProgressIndicator = false
continuation.resume(throwing: LocalizedNetNewsWireError.keychainError)
return
}
} else {
showProgressIndicator = false
continuation.resume(throwing: LocalizedNetNewsWireError.invalidUsernameOrPassword)
return
}
case .failure(let failure):
showProgressIndicator = false
continuation.resume(throwing: failure)
return
}
}
}
}
}
struct NewsBlurAddAccountView_Previews: PreviewProvider {
static var previews: some View {
NewsBlurAddAccountView()
}
}