mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Spit and polish on the Accounts views
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
"LOCAL_ACCOUNT_NAME_PHONE" = "On My iPhone";
|
||||
"LOCAL_ACCOUNT_NAME_PAD" = "On My iPad";
|
||||
"ACCOUNT_EMAIL_ADDRESS_PROMPT" = "Email Address";
|
||||
"ACCOUNT_USERNAME_PROMPT" = "Username";
|
||||
"ACCOUNT_USERNAME_OR_EMAIL_PROMPT" = "Username or Email";
|
||||
"ACCOUNT_PASSWORD_PROMPT" = "Password";
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import Account
|
||||
struct CloudKitAddAccountView: View {
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var addAccountError: (LocalizedError?, Bool) = (nil, false)
|
||||
@State private var accountError: (Error?, Bool) = (nil, false)
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
@@ -28,10 +28,10 @@ struct CloudKitAddAccountView: View {
|
||||
Button(action: { dismiss() }, label: { Text("CANCEL_BUTTON_TITLE", tableName: "Buttons") })
|
||||
}
|
||||
}
|
||||
.alert(Text("ERROR_TITLE", tableName: "Errors"), isPresented: $addAccountError.1) {
|
||||
.alert(Text("ERROR_TITLE", tableName: "Errors"), isPresented: $accountError.1) {
|
||||
Button(action: {}, label: { Text("DISMISS_BUTTON_TITLE", tableName: "Buttons") })
|
||||
} message: {
|
||||
Text(addAccountError.0?.localizedDescription ?? "Unknown Error")
|
||||
Text(accountError.0?.localizedDescription ?? "Unknown Error")
|
||||
}
|
||||
.dismissOnExternalContextLaunch()
|
||||
.dismissOnAccountAdd()
|
||||
@@ -41,7 +41,7 @@ struct CloudKitAddAccountView: View {
|
||||
var createCloudKitAccount: some View {
|
||||
Button {
|
||||
guard FileManager.default.ubiquityIdentityToken != nil else {
|
||||
addAccountError = (LocalizedNetNewsWireError.iCloudDriveMissing, true)
|
||||
accountError = (LocalizedNetNewsWireError.iCloudDriveMissing, true)
|
||||
return
|
||||
}
|
||||
let _ = AccountManager.shared.createAccount(type: .cloudKit)
|
||||
|
||||
@@ -54,6 +54,7 @@ struct FeedbinAddAccountView: View {
|
||||
}
|
||||
.navigationTitle(Text(account?.type.localizedAccountName() ?? ""))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.interactiveDismissDisabled(showProgressIndicator)
|
||||
.dismissOnExternalContextLaunch()
|
||||
.dismissOnAccountAdd()
|
||||
}
|
||||
@@ -73,8 +74,14 @@ struct FeedbinAddAccountView: View {
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
try await executeAccountCredentials()
|
||||
dismiss()
|
||||
if account == nil {
|
||||
// Create a new account
|
||||
try await executeAccountCredentials()
|
||||
} else {
|
||||
// Updating account credentials
|
||||
try await executeAccountCredentials()
|
||||
dismiss()
|
||||
}
|
||||
} catch {
|
||||
accountError = (error, true)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import Secrets
|
||||
import RSWeb
|
||||
import RSCore
|
||||
|
||||
struct NewsBlurAddAccountView: View {
|
||||
struct NewsBlurAddAccountView: View, Logging {
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State var account: Account? = nil
|
||||
@@ -52,6 +52,7 @@ struct NewsBlurAddAccountView: View {
|
||||
} message: {
|
||||
Text(accountError.0?.localizedDescription ?? "")
|
||||
}
|
||||
.interactiveDismissDisabled(showProgressIndicator)
|
||||
.dismissOnExternalContextLaunch()
|
||||
.dismissOnAccountAdd()
|
||||
}
|
||||
@@ -78,7 +79,7 @@ struct NewsBlurAddAccountView: View {
|
||||
|
||||
var accountDetails: some View {
|
||||
Section {
|
||||
TextField("Email", text: $accountUserName, prompt: Text("ACCOUNT_USERNAME_PROMPT", tableName: "Account"))
|
||||
TextField("Email", text: $accountUserName, prompt: Text("ACCOUNT_USERNAME_OR_EMAIL_PROMPT", tableName: "Account"))
|
||||
.autocorrectionDisabled()
|
||||
.autocapitalization(.none)
|
||||
SecureField("Password", text: $accountPassword, prompt: Text("ACCOUNT_PASSWORD_PROMPT", tableName: "Account"))
|
||||
@@ -90,8 +91,14 @@ struct NewsBlurAddAccountView: View {
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
try await executeAccountCredentials()
|
||||
dismiss()
|
||||
if account == nil {
|
||||
// Create a new account
|
||||
try await executeAccountCredentials()
|
||||
} else {
|
||||
// Updating account credentials
|
||||
try await executeAccountCredentials()
|
||||
dismiss()
|
||||
}
|
||||
} catch {
|
||||
accountError = (error, true)
|
||||
}
|
||||
@@ -115,27 +122,33 @@ struct NewsBlurAddAccountView: View {
|
||||
}
|
||||
|
||||
private func executeAccountCredentials() async throws {
|
||||
let trimmedEmailAddress = accountUserName.trimmingWhitespace
|
||||
let trimmedUsername = accountUserName.trimmingWhitespace
|
||||
|
||||
guard (account != nil || !AccountManager.shared.duplicateServiceAccount(type: .newsBlur, username: trimmedEmailAddress)) else {
|
||||
guard (account != nil || !AccountManager.shared.duplicateServiceAccount(type: .newsBlur, username: trimmedUsername)) else {
|
||||
throw LocalizedNetNewsWireError.duplicateAccount
|
||||
}
|
||||
showProgressIndicator = true
|
||||
|
||||
let basicCredentials = Credentials(type: .newsBlurBasic, username: trimmedEmailAddress, secret: accountPassword)
|
||||
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 {
|
||||
try self.account?.removeCredentials(type: .newsBlurBasic)
|
||||
try self.account?.removeCredentials(type: .newsBlurSessionId)
|
||||
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)
|
||||
|
||||
@@ -144,22 +157,27 @@ struct NewsBlurAddAccountView: View {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ struct ReaderAPIAddAccountView: View {
|
||||
@State private var accountSecret: String = ""
|
||||
@State private var accountAPIUrl: String = ""
|
||||
@State private var showProgressIndicator: Bool = false
|
||||
@State private var accountSetupError: (Error?, Bool) = (nil, false)
|
||||
@State private var accountError: (Error?, Bool) = (nil, false)
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
@@ -32,7 +32,7 @@ struct ReaderAPIAddAccountView: View {
|
||||
if accountType != nil {
|
||||
AccountSectionHeader(accountType: accountType!)
|
||||
}
|
||||
accountDetailsSection
|
||||
accountDetails
|
||||
Section(footer: readerAccountExplainer) {}
|
||||
}
|
||||
.navigationTitle(Text(accountType?.localizedAccountName() ?? ""))
|
||||
@@ -43,20 +43,22 @@ struct ReaderAPIAddAccountView: View {
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: { dismiss() }, label: { Text("CANCEL_BUTTON_TITLE", tableName: "Buttons") })
|
||||
.disabled(showProgressIndicator)
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if showProgressIndicator { ProgressView() }
|
||||
}
|
||||
}
|
||||
.alert(Text("ERROR_TITLE", tableName: "Errors"), isPresented: $accountSetupError.1) {
|
||||
.alert(Text("ERROR_TITLE", tableName: "Errors"), isPresented: $accountError.1) {
|
||||
Button(role: .cancel) {
|
||||
//
|
||||
} label: {
|
||||
Text("DISMISS_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
} message: {
|
||||
Text(accountSetupError.0?.localizedDescription ?? "")
|
||||
Text(accountError.0?.localizedDescription ?? "")
|
||||
}
|
||||
.interactiveDismissDisabled(showProgressIndicator)
|
||||
.dismissOnExternalContextLaunch()
|
||||
.dismissOnAccountAdd()
|
||||
}
|
||||
@@ -80,7 +82,7 @@ struct ReaderAPIAddAccountView: View {
|
||||
|
||||
|
||||
|
||||
var accountDetailsSection: some View {
|
||||
var accountDetails: some View {
|
||||
Group {
|
||||
Section {
|
||||
TextField("Username", text: $accountUserName)
|
||||
@@ -93,32 +95,39 @@ struct ReaderAPIAddAccountView: View {
|
||||
.autocapitalization(.none)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
accountSetupError = (error, true)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Spacer()
|
||||
if accountCredentials == nil {
|
||||
Text("ADD_ACCOUNT_BUTTON_TITLE", tableName: "Buttons")
|
||||
} else {
|
||||
Text("UPDATE_CREDENTIALS_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
Spacer()
|
||||
} catch {
|
||||
accountError = (error, true)
|
||||
}
|
||||
}
|
||||
.disabled(!validateCredentials())
|
||||
} label: {
|
||||
HStack {
|
||||
Spacer()
|
||||
if accountCredentials == nil {
|
||||
Text("ADD_ACCOUNT_BUTTON_TITLE", tableName: "Buttons")
|
||||
} else {
|
||||
Text("UPDATE_CREDENTIALS_BUTTON_TITLE", tableName: "Buttons")
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.disabled(!validateCredentials())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - API
|
||||
@@ -132,7 +141,7 @@ struct ReaderAPIAddAccountView: View {
|
||||
accountSecret = creds.secret
|
||||
}
|
||||
} catch {
|
||||
accountSetupError = (error, true)
|
||||
accountError = (error, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,7 +161,6 @@ struct ReaderAPIAddAccountView: View {
|
||||
return true
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func executeAccountCredentials() async throws {
|
||||
|
||||
let trimmedAccountUserName = accountUserName.trimmingWhitespace
|
||||
@@ -185,19 +193,23 @@ struct ReaderAPIAddAccountView: View {
|
||||
case .success:
|
||||
showProgressIndicator = false
|
||||
continuation.resume()
|
||||
return
|
||||
case .failure(let error):
|
||||
showProgressIndicator = false
|
||||
continuation.resume(throwing: error)
|
||||
return
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
showProgressIndicator = false
|
||||
continuation.resume(throwing: error)
|
||||
continuation.resume(throwing: LocalizedNetNewsWireError.keychainError)
|
||||
return
|
||||
}
|
||||
}
|
||||
case .failure(let failure):
|
||||
showProgressIndicator = false
|
||||
continuation.resume(throwing: failure)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1169,15 +1169,6 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
func showAccountInspector(for account: Account) {
|
||||
let hosting = UIHostingController(rootView: InjectedNavigationView(injectedView: AccountInspectorView(account: account)))
|
||||
rootSplitViewController.present(hosting, animated: true, completion: nil)
|
||||
|
||||
// let accountInspectorNavController =
|
||||
// UIStoryboard.inspector.instantiateViewController(identifier: "AccountInspectorNavigationViewController") as! UINavigationController
|
||||
// let accountInspectorController = accountInspectorNavController.topViewController as! AccountInspectorViewController
|
||||
// accountInspectorNavController.modalPresentationStyle = .formSheet
|
||||
// accountInspectorNavController.preferredContentSize = AccountInspectorViewController.preferredContentSizeForFormSheetDisplay
|
||||
// accountInspectorController.isModal = true
|
||||
// accountInspectorController.account = account
|
||||
// rootSplitViewController.present(accountInspectorNavController, animated: true)
|
||||
}
|
||||
|
||||
func showFeedInspector() {
|
||||
|
||||
@@ -105,7 +105,9 @@ struct AccountsManagementView: View {
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25)
|
||||
Text(account.nameForDisplay)
|
||||
}.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
}
|
||||
.transition(.move(edge: .top))
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
if account != AccountManager.shared.defaultAccount {
|
||||
Button(role: .destructive) {
|
||||
accountToRemove.wrappedValue = account
|
||||
@@ -118,17 +120,19 @@ struct AccountsManagementView: View {
|
||||
}
|
||||
}.tint(.red)
|
||||
}
|
||||
Button {
|
||||
withAnimation {
|
||||
account.isActive.toggle()
|
||||
}
|
||||
} label: {
|
||||
if account.isActive {
|
||||
Image(systemName: "minus.circle")
|
||||
} else {
|
||||
Image(systemName: "togglepower")
|
||||
}
|
||||
}.tint(account.isActive ? .yellow : Color(uiColor: AppAssets.primaryAccentColor))
|
||||
}
|
||||
}
|
||||
|
||||
var inactiveFooterText: some View {
|
||||
if AccountManager.shared.sortedAccounts.filter({ $0.isActive == false }).count == 0 {
|
||||
return Text("NO_INACTIVE_ACCOUNT_FOOTER", tableName: "Settings")
|
||||
} else {
|
||||
return Text("")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct AddAccountView_Previews: PreviewProvider {
|
||||
|
||||
@@ -22,6 +22,9 @@ struct DismissOnAccountAdd: ViewModifier {
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
/// Convenience modifier to dismiss a view when an account has been added.
|
||||
/// - Returns: `View`
|
||||
func dismissOnAccountAdd() -> some View {
|
||||
modifier(DismissOnAccountAdd())
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ struct DismissOnExternalContext: ViewModifier {
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
/// This function dismisses a view when the user launches from
|
||||
/// an external action, for example, opening the app from the widget.
|
||||
/// - Returns: `View`
|
||||
func dismissOnExternalContextLaunch() -> some View {
|
||||
modifier(DismissOnExternalContext())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user