Adding and deleting accounts works
This commit is contained in:
parent
564f96ae47
commit
a53500f643
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// AccountsPreferenceModel.swift
|
||||||
|
// Multiplatform macOS
|
||||||
|
//
|
||||||
|
// Created by Stuart Breckenridge on 13/7/20.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Account
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
class AccountsPreferenceModel: ObservableObject {
|
||||||
|
|
||||||
|
|
||||||
|
@Published var sortedAccounts: [Account] = []
|
||||||
|
|
||||||
|
// Configured Accounts
|
||||||
|
@Published var selectedConfiguredAccountID: String? = nil
|
||||||
|
|
||||||
|
// Sheets
|
||||||
|
@Published var showAddAccountView: Bool = false
|
||||||
|
|
||||||
|
var selectedAccountIsDefault: Bool {
|
||||||
|
guard let selected = selectedConfiguredAccountID else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if selected == AccountManager.shared.defaultAccount.accountID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscriptions
|
||||||
|
var notifcationSubscriptions = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
sortedAccounts = AccountManager.shared.sortedAccounts
|
||||||
|
|
||||||
|
NotificationCenter.default.publisher(for: .UserDidAddAccount).sink(receiveValue: { _ in
|
||||||
|
self.sortedAccounts = AccountManager.shared.sortedAccounts
|
||||||
|
}).store(in: ¬ifcationSubscriptions)
|
||||||
|
|
||||||
|
NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink(receiveValue: { _ in
|
||||||
|
self.selectedConfiguredAccountID = nil
|
||||||
|
self.sortedAccounts = AccountManager.shared.sortedAccounts
|
||||||
|
}).store(in: ¬ifcationSubscriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
//
|
||||||
|
// AccountsPreferencesView.swift
|
||||||
|
// macOS
|
||||||
|
//
|
||||||
|
// Created by Stuart Breckenridge on 27/6/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Account
|
||||||
|
|
||||||
|
|
||||||
|
struct AccountsPreferencesView: View {
|
||||||
|
|
||||||
|
@StateObject var viewModel = AccountsPreferenceModel()
|
||||||
|
|
||||||
|
@State private var hoverOnAdd: Bool = false
|
||||||
|
@State private var hoverOnRemove: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack(alignment: .top, spacing: 10) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
List(viewModel.sortedAccounts, id: \.accountID, selection: $viewModel.selectedConfiguredAccountID) {
|
||||||
|
ConfiguredAccountRow(account: $0)
|
||||||
|
.id($0.accountID)
|
||||||
|
}.overlay(
|
||||||
|
Group {
|
||||||
|
bottomButtonStack
|
||||||
|
}, alignment: .bottom)
|
||||||
|
}
|
||||||
|
.frame(width: 225, height: 300, alignment: .leading)
|
||||||
|
.border(Color.gray, width: 1)
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
EmptyView()
|
||||||
|
Spacer()
|
||||||
|
}.frame(width: 225, height: 300, alignment: .leading)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}.sheet(isPresented: $viewModel.showAddAccountView,
|
||||||
|
onDismiss: { viewModel.showAddAccountView.toggle() },
|
||||||
|
content: {
|
||||||
|
AddAccountView(preferencesModel: viewModel)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var bottomButtonStack: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Divider()
|
||||||
|
HStack(alignment: .center, spacing: 4) {
|
||||||
|
Button(action: {
|
||||||
|
viewModel.showAddAccountView.toggle()
|
||||||
|
}, label: {
|
||||||
|
Image(systemName: "plus")
|
||||||
|
.font(.title)
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
.overlay(RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||||
|
.foregroundColor(hoverOnAdd ? Color.gray.opacity(0.1) : Color.clear))
|
||||||
|
.padding(4)
|
||||||
|
})
|
||||||
|
.buttonStyle(BorderlessButtonStyle())
|
||||||
|
.onHover { hovering in
|
||||||
|
hoverOnAdd = hovering
|
||||||
|
}
|
||||||
|
.help("Add Account")
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
if let account = viewModel.sortedAccounts.first(where: { $0.accountID == viewModel.selectedConfiguredAccountID }) {
|
||||||
|
AccountManager.shared.deleteAccount(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, label: {
|
||||||
|
Image(systemName: "minus")
|
||||||
|
.font(.title)
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
.overlay(RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||||
|
.foregroundColor(hoverOnRemove ? Color.gray.opacity(0.1) : Color.clear))
|
||||||
|
.padding(4)
|
||||||
|
})
|
||||||
|
.buttonStyle(BorderlessButtonStyle())
|
||||||
|
.onHover { hovering in
|
||||||
|
hoverOnRemove = hovering
|
||||||
|
}
|
||||||
|
.disabled(viewModel.selectedAccountIsDefault)
|
||||||
|
.help("Delete Account")
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color.white)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConfiguredAccountRow: View {
|
||||||
|
|
||||||
|
var account: Account
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
if let img = account.smallIcon?.image {
|
||||||
|
Image(rsImage: img)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
}
|
||||||
|
Text(account.nameForDisplay)
|
||||||
|
}.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AddAccountPickerRow: View {
|
||||||
|
|
||||||
|
var accountType: AccountType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
if let img = AppAssets.image(for: accountType) {
|
||||||
|
Image(rsImage: img)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 15, height: 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch accountType {
|
||||||
|
case .onMyMac:
|
||||||
|
Text(Account.defaultLocalAccountName)
|
||||||
|
case .cloudKit:
|
||||||
|
Text("iCloud")
|
||||||
|
case .feedbin:
|
||||||
|
Text("Feedbin")
|
||||||
|
case .feedWrangler:
|
||||||
|
Text("FeedWrangler")
|
||||||
|
case .freshRSS:
|
||||||
|
Text("FreshRSS")
|
||||||
|
case .feedly:
|
||||||
|
Text("Feedly")
|
||||||
|
case .newsBlur:
|
||||||
|
Text("NewsBlur")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,304 @@
|
||||||
|
//
|
||||||
|
// AddAccountModel.swift
|
||||||
|
// Multiplatform macOS
|
||||||
|
//
|
||||||
|
// Created by Stuart Breckenridge on 13/7/20.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Account
|
||||||
|
import RSWeb
|
||||||
|
import Secrets
|
||||||
|
|
||||||
|
class AddAccountModel: ObservableObject {
|
||||||
|
|
||||||
|
enum AddAccountErrors: CustomStringConvertible {
|
||||||
|
case invalidUsernamePassword, invalidUsernamePasswordAPI, networkError, keyChainError, other(error: Error) , none
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .invalidUsernamePassword:
|
||||||
|
return NSLocalizedString("Invalid email or password combination.", comment: "Invalid email/password combination.")
|
||||||
|
case .invalidUsernamePasswordAPI:
|
||||||
|
return NSLocalizedString("Invalid email, password, or API URL combination.", comment: "Invalid email/password/API combination.")
|
||||||
|
case .networkError:
|
||||||
|
return NSLocalizedString("Network Error. Please try later.", comment: "Network Error. Please try later.")
|
||||||
|
case .keyChainError:
|
||||||
|
return NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
|
||||||
|
case .other(let error):
|
||||||
|
return NSLocalizedString(error.localizedDescription, comment: "Other add account error")
|
||||||
|
default:
|
||||||
|
return NSLocalizedString("N/A", comment: "N/A")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: AddAccountErrors, rhs: AddAccountErrors) -> Bool {
|
||||||
|
switch (lhs, rhs) {
|
||||||
|
case (.other(let lhsError), .other(let rhsError)):
|
||||||
|
return lhsError.localizedDescription == rhsError.localizedDescription
|
||||||
|
default:
|
||||||
|
return lhs == rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS, .cloudKit, .newsBlur]
|
||||||
|
#else
|
||||||
|
let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly]
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Add Accounts
|
||||||
|
@Published var selectedAddAccount: AccountType = .onMyMac
|
||||||
|
@Published var userName: String = ""
|
||||||
|
@Published var password: String = ""
|
||||||
|
@Published var apiUrl: String = ""
|
||||||
|
@Published var newLocalAccountName: String = ""
|
||||||
|
@Published var accountIsAuthenticating: Bool = false
|
||||||
|
@Published var addAccountError: AddAccountErrors = .none {
|
||||||
|
didSet {
|
||||||
|
if addAccountError == .none {
|
||||||
|
showError = false
|
||||||
|
} else {
|
||||||
|
showError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Published var showError: Bool = false
|
||||||
|
@Published var accountAdded: Bool = false
|
||||||
|
|
||||||
|
func resetUserEntries() {
|
||||||
|
userName = ""
|
||||||
|
password = ""
|
||||||
|
newLocalAccountName = ""
|
||||||
|
apiUrl = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticateAccount() {
|
||||||
|
switch selectedAddAccount {
|
||||||
|
case .onMyMac:
|
||||||
|
addLocalAccount()
|
||||||
|
case .cloudKit:
|
||||||
|
authenticateCloudKit()
|
||||||
|
case .feedbin:
|
||||||
|
authenticateFeedbin()
|
||||||
|
case .feedWrangler:
|
||||||
|
authenticateFeedWrangler()
|
||||||
|
case .freshRSS:
|
||||||
|
authenticateFreshRSS()
|
||||||
|
case .feedly:
|
||||||
|
authenticateFeedly()
|
||||||
|
case .newsBlur:
|
||||||
|
authenticateNewsBlur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK:- Authentication API
|
||||||
|
|
||||||
|
extension AddAccountModel {
|
||||||
|
|
||||||
|
private func addLocalAccount() {
|
||||||
|
let account = AccountManager.shared.createAccount(type: .onMyMac)
|
||||||
|
account.name = newLocalAccountName
|
||||||
|
accountAdded.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func authenticateFeedbin() {
|
||||||
|
accountIsAuthenticating = true
|
||||||
|
let credentials = Credentials(type: .basic, username: userName, secret: password)
|
||||||
|
|
||||||
|
Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in
|
||||||
|
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.accountIsAuthenticating = false
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let validatedCredentials):
|
||||||
|
|
||||||
|
guard let validatedCredentials = validatedCredentials else {
|
||||||
|
self.addAccountError = .invalidUsernamePassword
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let account = AccountManager.shared.createAccount(type: .feedbin)
|
||||||
|
|
||||||
|
do {
|
||||||
|
try account.removeCredentials(type: .basic)
|
||||||
|
try account.storeCredentials(validatedCredentials)
|
||||||
|
|
||||||
|
account.refreshAll(completion: { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
self.accountAdded.toggle()
|
||||||
|
break
|
||||||
|
case .failure(let error):
|
||||||
|
self.addAccountError = .other(error: error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
self.addAccountError = .keyChainError
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
self.addAccountError = .networkError
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func authenticateFeedWrangler() {
|
||||||
|
|
||||||
|
accountIsAuthenticating = true
|
||||||
|
let credentials = Credentials(type: .feedWranglerBasic, username: userName, secret: password)
|
||||||
|
|
||||||
|
Account.validateCredentials(type: .feedWrangler, credentials: credentials) { [weak self] result in
|
||||||
|
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.accountIsAuthenticating = false
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let validatedCredentials):
|
||||||
|
|
||||||
|
guard let validatedCredentials = validatedCredentials else {
|
||||||
|
self.addAccountError = .invalidUsernamePassword
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let account = AccountManager.shared.createAccount(type: .feedWrangler)
|
||||||
|
|
||||||
|
do {
|
||||||
|
try account.removeCredentials(type: .feedWranglerBasic)
|
||||||
|
try account.removeCredentials(type: .feedWranglerToken)
|
||||||
|
try account.storeCredentials(credentials)
|
||||||
|
try account.storeCredentials(validatedCredentials)
|
||||||
|
|
||||||
|
account.refreshAll(completion: { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
self.accountAdded.toggle()
|
||||||
|
break
|
||||||
|
case .failure(let error):
|
||||||
|
self.addAccountError = .other(error: error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
self.addAccountError = .keyChainError
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
self.addAccountError = .networkError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func authenticateNewsBlur() {
|
||||||
|
accountIsAuthenticating = true
|
||||||
|
let credentials = Credentials(type: .newsBlurBasic, username: userName, secret: password)
|
||||||
|
|
||||||
|
Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in
|
||||||
|
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.accountIsAuthenticating = false
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let validatedCredentials):
|
||||||
|
|
||||||
|
guard let validatedCredentials = validatedCredentials else {
|
||||||
|
self.addAccountError = .invalidUsernamePassword
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let account = AccountManager.shared.createAccount(type: .newsBlur)
|
||||||
|
|
||||||
|
do {
|
||||||
|
try account.removeCredentials(type: .newsBlurBasic)
|
||||||
|
try account.removeCredentials(type: .newsBlurSessionId)
|
||||||
|
try account.storeCredentials(credentials)
|
||||||
|
try account.storeCredentials(validatedCredentials)
|
||||||
|
|
||||||
|
account.refreshAll(completion: { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
self.accountAdded.toggle()
|
||||||
|
break
|
||||||
|
case .failure(let error):
|
||||||
|
self.addAccountError = .other(error: error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
self.addAccountError = .keyChainError
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
self.addAccountError = .networkError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func authenticateFreshRSS() {
|
||||||
|
accountIsAuthenticating = true
|
||||||
|
let credentials = Credentials(type: .readerBasic, username: userName, secret: password)
|
||||||
|
|
||||||
|
Account.validateCredentials(type: .freshRSS, credentials: credentials, endpoint: URL(string: apiUrl)!) { [weak self] result in
|
||||||
|
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
self.accountIsAuthenticating = false
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let validatedCredentials):
|
||||||
|
|
||||||
|
guard let validatedCredentials = validatedCredentials else {
|
||||||
|
self.addAccountError = .invalidUsernamePassword
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let account = AccountManager.shared.createAccount(type: .newsBlur)
|
||||||
|
|
||||||
|
do {
|
||||||
|
try account.removeCredentials(type: .readerBasic)
|
||||||
|
try account.removeCredentials(type: .readerAPIKey)
|
||||||
|
try account.storeCredentials(credentials)
|
||||||
|
try account.storeCredentials(validatedCredentials)
|
||||||
|
|
||||||
|
account.refreshAll(completion: { result in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
self.accountAdded.toggle()
|
||||||
|
break
|
||||||
|
case .failure(let error):
|
||||||
|
self.addAccountError = .other(error: error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
self.addAccountError = .keyChainError
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
self.addAccountError = .networkError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func authenticateCloudKit() {
|
||||||
|
let _ = AccountManager.shared.createAccount(type: .cloudKit)
|
||||||
|
self.accountAdded.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func authenticateFeedly() {
|
||||||
|
// TBC
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
//
|
||||||
|
// AddAccountView.swift
|
||||||
|
// Multiplatform macOS
|
||||||
|
//
|
||||||
|
// Created by Stuart Breckenridge on 13/7/20.
|
||||||
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Account
|
||||||
|
|
||||||
|
struct AddAccountView: View {
|
||||||
|
|
||||||
|
@Environment(\.presentationMode) private var presentationMode
|
||||||
|
@ObservedObject var preferencesModel: AccountsPreferenceModel
|
||||||
|
@StateObject private var viewModel = AddAccountModel()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Add an Account").font(.headline)
|
||||||
|
Form {
|
||||||
|
Picker("Account Type",
|
||||||
|
selection: $viewModel.selectedAddAccount,
|
||||||
|
content: {
|
||||||
|
ForEach(0..<viewModel.addableAccountTypes.count, content: { i in
|
||||||
|
AddAccountPickerRow(accountType: viewModel.addableAccountTypes[i]).tag(viewModel.addableAccountTypes[i])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
switch viewModel.selectedAddAccount {
|
||||||
|
case .onMyMac:
|
||||||
|
addLocalAccountView
|
||||||
|
case .cloudKit:
|
||||||
|
Text("iCloud")
|
||||||
|
case .feedbin:
|
||||||
|
userNameAndPasswordView
|
||||||
|
case .feedWrangler:
|
||||||
|
userNameAndPasswordView
|
||||||
|
case .freshRSS:
|
||||||
|
userNamePasswordAndAPIUrlView
|
||||||
|
case .feedly:
|
||||||
|
Text("Feedly")
|
||||||
|
case .newsBlur:
|
||||||
|
userNameAndPasswordView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
if viewModel.accountIsAuthenticating {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Button(action: { presentationMode.wrappedValue.dismiss() }, label: {
|
||||||
|
Text("Cancel")
|
||||||
|
})
|
||||||
|
|
||||||
|
if viewModel.selectedAddAccount == .onMyMac {
|
||||||
|
Button("Add", action: {
|
||||||
|
viewModel.authenticateAccount()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.selectedAddAccount != .onMyMac && viewModel.selectedAddAccount != .freshRSS {
|
||||||
|
Button("Add Account", action: {
|
||||||
|
viewModel.authenticateAccount()
|
||||||
|
})
|
||||||
|
.disabled(viewModel.userName.count == 0 || viewModel.password.count == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.selectedAddAccount == .freshRSS {
|
||||||
|
Button("Add Account", action: {
|
||||||
|
viewModel.authenticateAccount()
|
||||||
|
})
|
||||||
|
.disabled(viewModel.userName.count == 0 || viewModel.password.count == 0 || viewModel.apiUrl.count == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(idealWidth: 300, idealHeight: 200, alignment: .top)
|
||||||
|
.padding()
|
||||||
|
.onChange(of: viewModel.selectedAddAccount) { _ in
|
||||||
|
viewModel.resetUserEntries()
|
||||||
|
}
|
||||||
|
.onChange(of: viewModel.accountAdded) { value in
|
||||||
|
if value == true {
|
||||||
|
preferencesModel.showAddAccountView = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert(isPresented: $viewModel.showError) {
|
||||||
|
Alert(title: Text("Error Adding Account"),
|
||||||
|
message: Text(viewModel.addAccountError.description),
|
||||||
|
dismissButton: .default(Text("Dismiss"),
|
||||||
|
action: {
|
||||||
|
viewModel.addAccountError = .none
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var addLocalAccountView: some View {
|
||||||
|
Group {
|
||||||
|
TextField("Account Name", text: $viewModel.newLocalAccountName)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
Text("This account stores all of its data on your device. It does not sync.")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userNameAndPasswordView: some View {
|
||||||
|
Group {
|
||||||
|
TextField("Email", text: $viewModel.userName)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
SecureField("Password", text: $viewModel.password)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userNamePasswordAndAPIUrlView: some View {
|
||||||
|
Group {
|
||||||
|
TextField("Email", text: $viewModel.userName)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
SecureField("Password", text: $viewModel.password)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
TextField("API URL", text: $viewModel.apiUrl)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
struct AddAccountView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
AddAccountView(preferencesModel: AccountsPreferenceModel())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,230 +0,0 @@
|
||||||
//
|
|
||||||
// AccountsPreferencesView.swift
|
|
||||||
// macOS
|
|
||||||
//
|
|
||||||
// Created by Stuart Breckenridge on 27/6/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Account
|
|
||||||
|
|
||||||
struct AccountPreferencesViewModel {
|
|
||||||
|
|
||||||
// Sorted Accounts
|
|
||||||
let sortedAccounts = AccountManager.shared.sortedAccounts
|
|
||||||
|
|
||||||
// Available Accounts
|
|
||||||
let accountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly]
|
|
||||||
|
|
||||||
var selectedAccount: Int? = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AccountsPreferencesView: View {
|
|
||||||
|
|
||||||
@State private var viewModel = AccountPreferencesViewModel()
|
|
||||||
@State private var addAccountViewModel = AccountPreferencesViewModel()
|
|
||||||
@State private var showAddAccountView: Bool = false
|
|
||||||
@State private var hoverOnAdd: Bool = false
|
|
||||||
@State private var hoverOnRemove: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
HStack(alignment: .top, spacing: 10) {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
List(selection: $viewModel.selectedAccount) {
|
|
||||||
ForEach(0..<viewModel.sortedAccounts.count, content: { i in
|
|
||||||
ConfiguredAccountRow(account: viewModel.sortedAccounts[i])
|
|
||||||
.tag(i)
|
|
||||||
})
|
|
||||||
}.overlay(
|
|
||||||
Group {
|
|
||||||
bottomButtonStack
|
|
||||||
}, alignment: .bottom)
|
|
||||||
|
|
||||||
}
|
|
||||||
.frame(width: 225, height: 300, alignment: .leading)
|
|
||||||
.border(Color.gray, width: 1)
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
EmptyView()
|
|
||||||
Spacer()
|
|
||||||
}.frame(width: 225, height: 300, alignment: .leading)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}.sheet(isPresented: $showAddAccountView,
|
|
||||||
onDismiss: { showAddAccountView.toggle() },
|
|
||||||
content: {
|
|
||||||
AddAccountView()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var bottomButtonStack: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
|
||||||
Divider()
|
|
||||||
HStack(alignment: .center, spacing: 4) {
|
|
||||||
Button(action: {
|
|
||||||
showAddAccountView.toggle()
|
|
||||||
}, label: {
|
|
||||||
Image(systemName: "plus")
|
|
||||||
.font(.title)
|
|
||||||
.frame(width: 30, height: 30)
|
|
||||||
.overlay(RoundedRectangle(cornerRadius: 4, style: .continuous)
|
|
||||||
.foregroundColor(hoverOnAdd ? Color.gray.opacity(0.1) : Color.clear))
|
|
||||||
.padding(4)
|
|
||||||
})
|
|
||||||
.buttonStyle(BorderlessButtonStyle())
|
|
||||||
.onHover { hovering in
|
|
||||||
hoverOnAdd = hovering
|
|
||||||
}
|
|
||||||
.help("Add Account")
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
//
|
|
||||||
}, label: {
|
|
||||||
Image(systemName: "minus")
|
|
||||||
.font(.title)
|
|
||||||
.frame(width: 30, height: 30)
|
|
||||||
.overlay(RoundedRectangle(cornerRadius: 4, style: .continuous)
|
|
||||||
.foregroundColor(hoverOnRemove ? Color.gray.opacity(0.1) : Color.clear))
|
|
||||||
.padding(4)
|
|
||||||
})
|
|
||||||
.buttonStyle(BorderlessButtonStyle())
|
|
||||||
.onHover { hovering in
|
|
||||||
hoverOnRemove = hovering
|
|
||||||
}
|
|
||||||
.help("Remove Account")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}.background(Color.white)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ConfiguredAccountRow: View {
|
|
||||||
|
|
||||||
var account: Account
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
if let img = account.smallIcon?.image {
|
|
||||||
Image(rsImage: img)
|
|
||||||
}
|
|
||||||
Text(account.nameForDisplay)
|
|
||||||
}.padding(.vertical, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AddAccountPickerRow: View {
|
|
||||||
|
|
||||||
var accountType: AccountType
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
if let img = AppAssets.image(for: accountType) {
|
|
||||||
Image(rsImage: img)
|
|
||||||
.resizable()
|
|
||||||
.frame(width: 15, height: 15)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch accountType {
|
|
||||||
case .onMyMac:
|
|
||||||
Text(Account.defaultLocalAccountName)
|
|
||||||
case .cloudKit:
|
|
||||||
Text("iCloud")
|
|
||||||
case .feedbin:
|
|
||||||
Text("Feedbin")
|
|
||||||
case .feedWrangler:
|
|
||||||
Text("FeedWrangler")
|
|
||||||
case .freshRSS:
|
|
||||||
Text("FreshRSS")
|
|
||||||
case .feedly:
|
|
||||||
Text("Feedly")
|
|
||||||
case .newsBlur:
|
|
||||||
Text("NewsBlur")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct AddAccountView: View {
|
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
|
||||||
let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly]
|
|
||||||
@State private var selectedAccount: AccountType = .onMyMac
|
|
||||||
@State private var userName: String = ""
|
|
||||||
@State private var password: String = ""
|
|
||||||
@State private var newLocalAccountName = ""
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("Add an Account").font(.headline)
|
|
||||||
Form {
|
|
||||||
Picker("Account Type",
|
|
||||||
selection: $selectedAccount,
|
|
||||||
content: {
|
|
||||||
ForEach(0..<addableAccountTypes.count, content: { i in
|
|
||||||
AddAccountPickerRow(accountType: addableAccountTypes[i]).tag(addableAccountTypes[i])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if selectedAccount != .onMyMac {
|
|
||||||
TextField("Email", text: $userName)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
SecureField("Password", text: $password)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
}
|
|
||||||
|
|
||||||
if selectedAccount == .onMyMac {
|
|
||||||
TextField("Account Name", text: $newLocalAccountName)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
Text("This account stores all of its data on your device. It does not sync.")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
|
|
||||||
Button(action: { presentationMode.wrappedValue.dismiss() }, label: {
|
|
||||||
Text("Cancel")
|
|
||||||
})
|
|
||||||
|
|
||||||
if selectedAccount == .onMyMac {
|
|
||||||
Button("Add", action: {})
|
|
||||||
}
|
|
||||||
|
|
||||||
if selectedAccount != .onMyMac {
|
|
||||||
Button("Add Account", action: {})
|
|
||||||
.disabled(userName.count == 0 || password.count == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(idealWidth: 300, idealHeight: 200, alignment: .top)
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AddAccountModel: ObservableObject {
|
|
||||||
let accountTypes = ["On My Mac", "FeedBin"]
|
|
||||||
@Published var selectedAccount = Int?.none
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529A24AA1FD200D65E66 /* MacSearchField.swift */; };
|
1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529A24AA1FD200D65E66 /* MacSearchField.swift */; };
|
||||||
175942AA24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
|
175942AA24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
|
||||||
175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
|
175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
|
||||||
|
1769E32224BC5925000E1E8E /* AccountsPreferenceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32124BC5925000E1E8E /* AccountsPreferenceModel.swift */; };
|
||||||
|
1769E32524BC5A65000E1E8E /* AddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32424BC5A65000E1E8E /* AddAccountView.swift */; };
|
||||||
|
1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */; };
|
||||||
1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; };
|
1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; };
|
||||||
1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; };
|
1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; };
|
||||||
17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; };
|
17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; };
|
||||||
|
@ -1788,6 +1791,9 @@
|
||||||
1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesView.swift; sourceTree = "<group>"; };
|
1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesView.swift; sourceTree = "<group>"; };
|
||||||
1729529624AA1CD000D65E66 /* MacPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacPreferencesView.swift; sourceTree = "<group>"; };
|
1729529624AA1CD000D65E66 /* MacPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacPreferencesView.swift; sourceTree = "<group>"; };
|
||||||
1729529A24AA1FD200D65E66 /* MacSearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacSearchField.swift; sourceTree = "<group>"; };
|
1729529A24AA1FD200D65E66 /* MacSearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacSearchField.swift; sourceTree = "<group>"; };
|
||||||
|
1769E32124BC5925000E1E8E /* AccountsPreferenceModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsPreferenceModel.swift; sourceTree = "<group>"; };
|
||||||
|
1769E32424BC5A65000E1E8E /* AddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountView.swift; sourceTree = "<group>"; };
|
||||||
|
1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountModel.swift; sourceTree = "<group>"; };
|
||||||
1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = "<group>"; };
|
1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = "<group>"; };
|
||||||
17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = "<group>"; };
|
17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = "<group>"; };
|
||||||
179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = "<group>"; };
|
179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2515,13 +2521,48 @@
|
||||||
1729529924AA1CE100D65E66 /* Preference Panes */ = {
|
1729529924AA1CE100D65E66 /* Preference Panes */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */,
|
1769E2FD24BC589E000E1E8E /* General */,
|
||||||
1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */,
|
1769E31F24BC58A4000E1E8E /* Accounts */,
|
||||||
1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */,
|
1769E32024BC58AD000E1E8E /* Advanced */,
|
||||||
);
|
);
|
||||||
path = "Preference Panes";
|
path = "Preference Panes";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
1769E2FD24BC589E000E1E8E /* General */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */,
|
||||||
|
);
|
||||||
|
path = General;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
1769E31F24BC58A4000E1E8E /* Accounts */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1769E32124BC5925000E1E8E /* AccountsPreferenceModel.swift */,
|
||||||
|
1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */,
|
||||||
|
1769E32324BC5A50000E1E8E /* Add Account */,
|
||||||
|
);
|
||||||
|
path = Accounts;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
1769E32024BC58AD000E1E8E /* Advanced */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */,
|
||||||
|
);
|
||||||
|
path = Advanced;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
1769E32324BC5A50000E1E8E /* Add Account */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */,
|
||||||
|
1769E32424BC5A65000E1E8E /* AddAccountView.swift */,
|
||||||
|
);
|
||||||
|
path = "Add Account";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
17930ED224AF10CD00A9BA52 /* Add */ = {
|
17930ED224AF10CD00A9BA52 /* Add */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -5158,6 +5199,7 @@
|
||||||
5177470A24B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */,
|
5177470A24B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */,
|
||||||
51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */,
|
51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */,
|
||||||
5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */,
|
5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */,
|
||||||
|
1769E32224BC5925000E1E8E /* AccountsPreferenceModel.swift in Sources */,
|
||||||
51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */,
|
51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */,
|
||||||
51919FF224AB864A00541E64 /* TimelineModel.swift in Sources */,
|
51919FF224AB864A00541E64 /* TimelineModel.swift in Sources */,
|
||||||
51E4991A24A8090F00B667CB /* IconImage.swift in Sources */,
|
51E4991A24A8090F00B667CB /* IconImage.swift in Sources */,
|
||||||
|
@ -5177,6 +5219,7 @@
|
||||||
FF64D0E824AF53EE0084080A /* RefreshProgressModel.swift in Sources */,
|
FF64D0E824AF53EE0084080A /* RefreshProgressModel.swift in Sources */,
|
||||||
51E499D924A912C200B667CB /* SceneModel.swift in Sources */,
|
51E499D924A912C200B667CB /* SceneModel.swift in Sources */,
|
||||||
51919FB424AAB97900541E64 /* FeedIconImageLoader.swift in Sources */,
|
51919FB424AAB97900541E64 /* FeedIconImageLoader.swift in Sources */,
|
||||||
|
1769E32524BC5A65000E1E8E /* AddAccountView.swift in Sources */,
|
||||||
51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */,
|
51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */,
|
||||||
514E6C0324AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */,
|
514E6C0324AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */,
|
||||||
51B54A6524B549B20014348B /* WrapperScriptMessageHandler.swift in Sources */,
|
51B54A6524B549B20014348B /* WrapperScriptMessageHandler.swift in Sources */,
|
||||||
|
@ -5223,6 +5266,7 @@
|
||||||
51B54A4324B5499B0014348B /* WebViewProvider.swift in Sources */,
|
51B54A4324B5499B0014348B /* WebViewProvider.swift in Sources */,
|
||||||
514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */,
|
514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */,
|
||||||
1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */,
|
1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */,
|
||||||
|
1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */,
|
||||||
1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */,
|
1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */,
|
||||||
FFA2BBD724AF751100B3149D /* PreviewProvider+RefreshProgressModel.swift in Sources */,
|
FFA2BBD724AF751100B3149D /* PreviewProvider+RefreshProgressModel.swift in Sources */,
|
||||||
5177470424B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */,
|
5177470424B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */,
|
||||||
|
|
Loading…
Reference in New Issue