mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Remove Multiplatform targets
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
//
|
||||
// AccountsPreferencesModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import Combine
|
||||
|
||||
public enum AccountConfigurationSheets: Equatable {
|
||||
case addAccountPicker, addSelectedAccount(AccountType), credentials, none
|
||||
|
||||
public static func == (lhs: AccountConfigurationSheets, rhs: AccountConfigurationSheets) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (let .addSelectedAccount(lhsType), let .addSelectedAccount(rhsType)):
|
||||
return lhsType == rhsType
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class AccountsPreferencesModel: ObservableObject {
|
||||
|
||||
// Selected Account
|
||||
public private(set) var account: Account?
|
||||
|
||||
// All Accounts
|
||||
@Published var sortedAccounts: [Account] = []
|
||||
@Published var selectedConfiguredAccountID: String? = AccountManager.shared.defaultAccount.accountID {
|
||||
didSet {
|
||||
if let accountID = selectedConfiguredAccountID {
|
||||
account = sortedAccounts.first(where: { $0.accountID == accountID })
|
||||
accountIsActive = account?.isActive ?? false
|
||||
accountName = account?.name ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@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
|
||||
}
|
||||
|
||||
// Edit Account
|
||||
@Published var accountIsActive: Bool = false {
|
||||
didSet {
|
||||
account?.isActive = accountIsActive
|
||||
}
|
||||
}
|
||||
@Published var accountName: String = "" {
|
||||
didSet {
|
||||
account?.name = accountName
|
||||
}
|
||||
}
|
||||
|
||||
// Sheets
|
||||
@Published var showSheet: Bool = false
|
||||
@Published var sheetToShow: AccountConfigurationSheets = .none {
|
||||
didSet {
|
||||
if sheetToShow == .none { showSheet = false } else { showSheet = true }
|
||||
}
|
||||
}
|
||||
@Published var showDeleteConfirmation: Bool = false
|
||||
|
||||
// Subscriptions
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init() {
|
||||
sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
|
||||
NotificationCenter.default.publisher(for: .UserDidAddAccount).sink { [weak self] _ in
|
||||
self?.sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
}.store(in: &cancellables)
|
||||
|
||||
NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink { [weak self] _ in
|
||||
self?.selectedConfiguredAccountID = nil
|
||||
self?.sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
self?.selectedConfiguredAccountID = AccountManager.shared.defaultAccount.accountID
|
||||
}.store(in: &cancellables)
|
||||
|
||||
NotificationCenter.default.publisher(for: .AccountStateDidChange).sink { [weak self] notification in
|
||||
guard let account = notification.object as? Account else {
|
||||
return
|
||||
}
|
||||
if account.accountID == self?.account?.accountID {
|
||||
self?.account = account
|
||||
self?.accountIsActive = account.isActive
|
||||
self?.accountName = account.name ?? ""
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
//
|
||||
// AccountsPreferencesView.swift
|
||||
// macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 27/6/20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
|
||||
struct AccountsPreferencesView: View {
|
||||
|
||||
@StateObject var viewModel = AccountsPreferencesModel()
|
||||
@State private var hoverOnAdd: Bool = false
|
||||
@State private var hoverOnRemove: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack(alignment: .top, spacing: 10) {
|
||||
listOfAccounts
|
||||
|
||||
AccountDetailView(viewModel: viewModel)
|
||||
.frame(height: 300, alignment: .leading)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.sheet(isPresented: $viewModel.showSheet,
|
||||
onDismiss: { viewModel.sheetToShow = .none },
|
||||
content: {
|
||||
switch viewModel.sheetToShow {
|
||||
case .addAccountPicker:
|
||||
AddAccountView(accountToAdd: $viewModel.sheetToShow)
|
||||
case .credentials:
|
||||
EditAccountCredentialsView(viewModel: viewModel)
|
||||
case .none:
|
||||
EmptyView()
|
||||
case .addSelectedAccount(let type):
|
||||
switch type {
|
||||
case .onMyMac:
|
||||
AddLocalAccountView()
|
||||
case .feedbin:
|
||||
AddFeedbinAccountView()
|
||||
case .cloudKit:
|
||||
AddCloudKitAccountView()
|
||||
case .feedWrangler:
|
||||
AddFeedWranglerAccountView()
|
||||
case .newsBlur:
|
||||
AddNewsBlurAccountView()
|
||||
case .feedly:
|
||||
AddFeedlyAccountView()
|
||||
default:
|
||||
AddReaderAPIAccountView(accountType: type)
|
||||
}
|
||||
}
|
||||
})
|
||||
.alert(isPresented: $viewModel.showDeleteConfirmation, content: {
|
||||
Alert(title: Text("Delete \(viewModel.account!.nameForDisplay)?"),
|
||||
message: Text("Are you sure you want to delete the account \"\(viewModel.account!.nameForDisplay)\"? This can not be undone."),
|
||||
primaryButton: .destructive(Text("Delete"), action: {
|
||||
AccountManager.shared.deleteAccount(viewModel.account!)
|
||||
viewModel.showDeleteConfirmation = false
|
||||
}),
|
||||
secondaryButton: .cancel({
|
||||
viewModel.showDeleteConfirmation = false
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
var listOfAccounts: some View {
|
||||
VStack(alignment: .leading) {
|
||||
List(viewModel.sortedAccounts, id: \.accountID, selection: $viewModel.selectedConfiguredAccountID) {
|
||||
ConfiguredAccountRow(account: $0)
|
||||
.id($0.accountID)
|
||||
}.overlay(
|
||||
Group {
|
||||
bottomButtonStack
|
||||
}, alignment: .bottom)
|
||||
}
|
||||
.frame(width: 160, height: 300, alignment: .leading)
|
||||
.border(Color.gray, width: 1)
|
||||
}
|
||||
|
||||
var bottomButtonStack: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Divider()
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
Button(action: {
|
||||
viewModel.sheetToShow = .addAccountPicker
|
||||
}, 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: {
|
||||
viewModel.showDeleteConfirmation = true
|
||||
}, 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.init(.windowBackgroundColor))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
//
|
||||
// AddAccountView.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Stuart Breckenridge on 28/10/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
enum AddAccountSections: Int, CaseIterable {
|
||||
case local = 0
|
||||
case icloud
|
||||
case web
|
||||
case selfhosted
|
||||
case allOrdered
|
||||
|
||||
var sectionHeader: String {
|
||||
switch self {
|
||||
case .local:
|
||||
return NSLocalizedString("Local", comment: "Local Account")
|
||||
case .icloud:
|
||||
return NSLocalizedString("iCloud", comment: "iCloud Account")
|
||||
case .web:
|
||||
return NSLocalizedString("Web", comment: "Web Account")
|
||||
case .selfhosted:
|
||||
return NSLocalizedString("Self-hosted", comment: "Self hosted Account")
|
||||
case .allOrdered:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var sectionFooter: String {
|
||||
switch self {
|
||||
case .local:
|
||||
return NSLocalizedString("Local accounts do not sync feeds across devices.", comment: "Local Account")
|
||||
case .icloud:
|
||||
return NSLocalizedString("Your iCloud account syncs your feeds across your Mac and iOS devices.", comment: "iCloud Account")
|
||||
case .web:
|
||||
return NSLocalizedString("Web accounts sync your feeds across all your devices.", comment: "Web Account")
|
||||
case .selfhosted:
|
||||
return NSLocalizedString("Self-hosted accounts sync your feeds across all your devices.", comment: "Self hosted Account")
|
||||
case .allOrdered:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var sectionContent: [AccountType] {
|
||||
switch self {
|
||||
case .local:
|
||||
return [.onMyMac]
|
||||
case .icloud:
|
||||
return [.cloudKit]
|
||||
case .web:
|
||||
#if DEBUG
|
||||
return [.bazQux, .feedbin, .feedly, .feedWrangler, .inoreader, .newsBlur, .theOldReader]
|
||||
#else
|
||||
return [.bazQux, .feedbin, .feedly, .feedWrangler, .inoreader, .newsBlur, .theOldReader]
|
||||
#endif
|
||||
case .selfhosted:
|
||||
return [.freshRSS]
|
||||
case .allOrdered:
|
||||
return AddAccountSections.local.sectionContent +
|
||||
AddAccountSections.icloud.sectionContent +
|
||||
AddAccountSections.web.sectionContent +
|
||||
AddAccountSections.selfhosted.sectionContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddAccountView: View {
|
||||
|
||||
@State private var selectedAccount: AccountType = .onMyMac
|
||||
@Binding public var accountToAdd: AccountConfigurationSheets
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Choose an account type to add...")
|
||||
.font(.headline)
|
||||
.padding()
|
||||
|
||||
localAccount
|
||||
icloudAccount
|
||||
webAccounts
|
||||
selfhostedAccounts
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Spacer()
|
||||
if #available(OSX 11.0, *) {
|
||||
Button(action: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}, label: {
|
||||
Text("Cancel")
|
||||
.frame(width: 80)
|
||||
})
|
||||
.help("Cancel")
|
||||
.keyboardShortcut(.cancelAction)
|
||||
|
||||
} else {
|
||||
Button(action: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}, label: {
|
||||
Text("Cancel")
|
||||
.frame(width: 80)
|
||||
})
|
||||
.accessibility(label: Text("Add Account"))
|
||||
}
|
||||
if #available(OSX 11.0, *) {
|
||||
Button(action: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
|
||||
accountToAdd = AccountConfigurationSheets.addSelectedAccount(selectedAccount)
|
||||
})
|
||||
}, label: {
|
||||
Text("Continue")
|
||||
.frame(width: 80)
|
||||
})
|
||||
.help("Add Account")
|
||||
.keyboardShortcut(.defaultAction)
|
||||
|
||||
} else {
|
||||
Button(action: {
|
||||
accountToAdd = AccountConfigurationSheets.addSelectedAccount(selectedAccount)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
|
||||
}, label: {
|
||||
Text("Continue")
|
||||
.frame(width: 80)
|
||||
})
|
||||
}
|
||||
}
|
||||
.padding(.top, 12)
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
.pickerStyle(RadioGroupPickerStyle())
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(width: 420)
|
||||
.padding()
|
||||
}
|
||||
|
||||
var localAccount: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Local")
|
||||
.font(.headline)
|
||||
.padding(.horizontal)
|
||||
|
||||
Picker(selection: $selectedAccount, label: Text(""), content: {
|
||||
ForEach(AddAccountSections.local.sectionContent, id: \.self, content: { account in
|
||||
HStack(alignment: .center) {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
Text(account.localizedAccountName())
|
||||
}
|
||||
.tag(account)
|
||||
})
|
||||
})
|
||||
.pickerStyle(RadioGroupPickerStyle())
|
||||
.offset(x: 7.5, y: 0)
|
||||
|
||||
Text(AddAccountSections.local.sectionFooter).foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
.padding(.horizontal)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var icloudAccount: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("iCloud")
|
||||
.font(.headline)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
Picker(selection: $selectedAccount, label: Text(""), content: {
|
||||
ForEach(AddAccountSections.icloud.sectionContent, id: \.self, content: { account in
|
||||
HStack(alignment: .center) {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
|
||||
Text(account.localizedAccountName())
|
||||
}
|
||||
.tag(account)
|
||||
})
|
||||
})
|
||||
.offset(x: 7.5, y: 0)
|
||||
.disabled(isCloudInUse())
|
||||
|
||||
Text(AddAccountSections.icloud.sectionFooter).foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
var webAccounts: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Web")
|
||||
.font(.headline)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
Picker(selection: $selectedAccount, label: Text(""), content: {
|
||||
ForEach(AddAccountSections.web.sectionContent, id: \.self, content: { account in
|
||||
|
||||
HStack(alignment: .center) {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
|
||||
Text(account.localizedAccountName())
|
||||
}
|
||||
.tag(account)
|
||||
|
||||
})
|
||||
})
|
||||
.offset(x: 7.5, y: 0)
|
||||
|
||||
Text(AddAccountSections.web.sectionFooter).foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
var selfhostedAccounts: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Self-hosted")
|
||||
.font(.headline)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
Picker(selection: $selectedAccount, label: Text(""), content: {
|
||||
ForEach(AddAccountSections.selfhosted.sectionContent, id: \.self, content: { account in
|
||||
HStack(alignment: .center) {
|
||||
account.image()
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25, alignment: .center)
|
||||
.padding(.leading, 4)
|
||||
|
||||
Text(account.localizedAccountName())
|
||||
}.tag(account)
|
||||
})
|
||||
})
|
||||
.offset(x: 7.5, y: 0)
|
||||
|
||||
Text(AddAccountSections.selfhosted.sectionFooter).foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
private func isCloudInUse() -> Bool {
|
||||
AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit })
|
||||
}
|
||||
|
||||
private func isRestricted(_ accountType: AccountType) -> Bool {
|
||||
if AppDefaults.shared.isDeveloperBuild && (accountType == .feedly || accountType == .feedWrangler || accountType == .inoreader) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// ConfiguredAccountRow.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
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: 20, height: 20)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
Text(account.nameForDisplay)
|
||||
}.padding(.vertical, 4)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
//
|
||||
// AccountDetailView.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
import Combine
|
||||
|
||||
struct AccountDetailView: View {
|
||||
|
||||
@ObservedObject var viewModel: AccountsPreferencesModel
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 8, style: .circular)
|
||||
.foregroundColor(Color.secondary.opacity(0.1))
|
||||
.padding(.top, 8)
|
||||
|
||||
VStack {
|
||||
editAccountHeader
|
||||
if viewModel.account != nil {
|
||||
editAccountForm
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var editAccountHeader: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Account Information", action: {})
|
||||
Spacer()
|
||||
}
|
||||
.padding([.leading, .trailing, .bottom], 4)
|
||||
}
|
||||
|
||||
var editAccountForm: some View {
|
||||
Form(content: {
|
||||
HStack(alignment: .top) {
|
||||
Text("Type: ")
|
||||
.frame(width: 50)
|
||||
VStack(alignment: .leading) {
|
||||
Text(viewModel.account!.defaultName)
|
||||
Toggle("Active", isOn: $viewModel.accountIsActive)
|
||||
}
|
||||
}
|
||||
HStack(alignment: .top) {
|
||||
Text("Name: ")
|
||||
.frame(width: 50)
|
||||
VStack(alignment: .leading) {
|
||||
TextField(viewModel.account!.name ?? "", text: $viewModel.accountName)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
Text("The name appears in the sidebar. It can be anything you want. You can even use emoji. 🎸")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
if viewModel.account?.type != .onMyMac {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Credentials", action: {
|
||||
viewModel.sheetToShow = .credentials
|
||||
})
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
})
|
||||
.padding()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct AccountDetailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AccountDetailView(viewModel: AccountsPreferencesModel())
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
//
|
||||
// EditAccountCredentialsModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import Secrets
|
||||
import RSCore
|
||||
|
||||
class EditAccountCredentialsModel: ObservableObject {
|
||||
|
||||
@Published var userName: String = ""
|
||||
@Published var password: String = ""
|
||||
@Published var apiUrl: String = ""
|
||||
@Published var accountIsUpdatingCredentials: Bool = false
|
||||
@Published var accountCredentialsWereUpdated: Bool = false
|
||||
@Published var error: AccountUpdateErrors = .none {
|
||||
didSet {
|
||||
if error == .none {
|
||||
showError = false
|
||||
} else {
|
||||
showError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@Published var showError: Bool = false
|
||||
|
||||
func updateAccountCredentials(_ account: Account) {
|
||||
switch account.type {
|
||||
case .onMyMac:
|
||||
return
|
||||
case .feedbin:
|
||||
updateFeedbin(account)
|
||||
case .cloudKit:
|
||||
return
|
||||
case .feedWrangler:
|
||||
updateFeedWrangler(account)
|
||||
case .feedly:
|
||||
updateFeedly(account)
|
||||
case .freshRSS:
|
||||
updateReaderAccount(account)
|
||||
case .newsBlur:
|
||||
updateNewsblur(account)
|
||||
case .inoreader:
|
||||
updateReaderAccount(account)
|
||||
case .bazQux:
|
||||
updateReaderAccount(account)
|
||||
case .theOldReader:
|
||||
updateReaderAccount(account)
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveCredentials(_ account: Account) {
|
||||
switch account.type {
|
||||
case .feedbin:
|
||||
let credentials = try? account.retrieveCredentials(type: .basic)
|
||||
userName = credentials?.username ?? ""
|
||||
case .feedWrangler:
|
||||
let credentials = try? account.retrieveCredentials(type: .feedWranglerBasic)
|
||||
userName = credentials?.username ?? ""
|
||||
case .feedly:
|
||||
return
|
||||
case .freshRSS:
|
||||
let credentials = try? account.retrieveCredentials(type: .readerBasic)
|
||||
userName = credentials?.username ?? ""
|
||||
case .newsBlur:
|
||||
let credentials = try? account.retrieveCredentials(type: .newsBlurBasic)
|
||||
userName = credentials?.username ?? ""
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK:- Update API
|
||||
extension EditAccountCredentialsModel {
|
||||
|
||||
func updateFeedbin(_ account: Account) {
|
||||
accountIsUpdatingCredentials = 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.accountIsUpdatingCredentials = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.error = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .basic)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountCredentialsWereUpdated = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.error = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFeedWrangler(_ account: Account) {
|
||||
accountIsUpdatingCredentials = 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.accountIsUpdatingCredentials = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.error = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .feedWranglerBasic)
|
||||
try account.removeCredentials(type: .feedWranglerToken)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountCredentialsWereUpdated = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.error = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFeedly(_ account: Account) {
|
||||
accountIsUpdatingCredentials = true
|
||||
let updateAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
||||
updateAccount.delegate = self
|
||||
#if os(macOS)
|
||||
updateAccount.presentationAnchor = NSApplication.shared.windows.last
|
||||
#endif
|
||||
MainThreadOperationQueue.shared.add(updateAccount)
|
||||
}
|
||||
|
||||
func updateReaderAccount(_ account: Account) {
|
||||
accountIsUpdatingCredentials = true
|
||||
let credentials = Credentials(type: .readerBasic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: account.type, credentials: credentials) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsUpdatingCredentials = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.error = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .readerBasic)
|
||||
try account.removeCredentials(type: .readerAPIKey)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountCredentialsWereUpdated = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.error = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNewsblur(_ account: Account) {
|
||||
accountIsUpdatingCredentials = 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.accountIsUpdatingCredentials = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.error = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .newsBlurBasic)
|
||||
try account.removeCredentials(type: .newsBlurSessionId)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountCredentialsWereUpdated = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.error = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK:- OAuthAccountAuthorizationOperationDelegate
|
||||
extension EditAccountCredentialsModel: OAuthAccountAuthorizationOperationDelegate {
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) {
|
||||
accountIsUpdatingCredentials = false
|
||||
accountCredentialsWereUpdated = true
|
||||
account.refreshAll { [weak self] result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self?.error = .other(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) {
|
||||
accountIsUpdatingCredentials = false
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
//
|
||||
// EditAccountCredentialsView.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Secrets
|
||||
|
||||
struct EditAccountCredentialsView: View {
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@StateObject private var editModel = EditAccountCredentialsModel()
|
||||
@ObservedObject var viewModel: AccountsPreferencesModel
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(rsImage: viewModel.account!.smallIcon!.image)
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
Text(viewModel.account?.nameForDisplay ?? "")
|
||||
Spacer()
|
||||
}.padding()
|
||||
|
||||
HStack(alignment: .center) {
|
||||
VStack(alignment: .trailing, spacing: 12) {
|
||||
Text("Username: ")
|
||||
Text("Password: ")
|
||||
if viewModel.account?.type == .freshRSS {
|
||||
Text("API URL: ")
|
||||
}
|
||||
}.frame(width: 75)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
TextField("Username", text: $editModel.userName)
|
||||
SecureField("Password", text: $editModel.password)
|
||||
if viewModel.account?.type == .freshRSS {
|
||||
TextField("API URL", text: $editModel.apiUrl)
|
||||
}
|
||||
}
|
||||
}.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
|
||||
Spacer()
|
||||
HStack{
|
||||
if editModel.accountIsUpdatingCredentials {
|
||||
ProgressView("Updating")
|
||||
}
|
||||
Spacer()
|
||||
Button("Cancel", action: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
})
|
||||
if viewModel.account?.type != .freshRSS {
|
||||
Button("Update", action: {
|
||||
editModel.updateAccountCredentials(viewModel.account!)
|
||||
}).disabled(editModel.userName.count == 0 || editModel.password.count == 0)
|
||||
} else {
|
||||
Button("Update", action: {
|
||||
editModel.updateAccountCredentials(viewModel.account!)
|
||||
}).disabled(editModel.userName.count == 0 || editModel.password.count == 0 || editModel.apiUrl.count == 0)
|
||||
}
|
||||
|
||||
}
|
||||
}.onAppear {
|
||||
editModel.retrieveCredentials(viewModel.account!)
|
||||
}
|
||||
.onChange(of: editModel.accountCredentialsWereUpdated) { value in
|
||||
if value == true {
|
||||
viewModel.sheetToShow = .none
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $editModel.showError) {
|
||||
Alert(title: Text("Error Adding Account"),
|
||||
message: Text(editModel.error.description),
|
||||
dismissButton: .default(Text("Dismiss"),
|
||||
action: {
|
||||
editModel.error = .none
|
||||
}))
|
||||
}
|
||||
.frame(idealWidth: 300, idealHeight: 200, alignment: .top)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct EditAccountCredentials_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EditAccountCredentialsView(viewModel: AccountsPreferencesModel())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
//
|
||||
// AccountUpdateErrors.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum AccountUpdateErrors: 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: AccountUpdateErrors, rhs: AccountUpdateErrors) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.other(let lhsError), .other(let rhsError)):
|
||||
return lhsError.localizedDescription == rhsError.localizedDescription
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// AdvancedPreferencesModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 16/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AdvancedPreferencesModel: ObservableObject {
|
||||
|
||||
let releaseBuildsURL = Bundle.main.infoDictionary!["SUFeedURL"]! as! String
|
||||
let testBuildsURL = Bundle.main.infoDictionary!["FeedURLForTestBuilds"]! as! String
|
||||
let appcastDefaultsKey = "SUFeedURL"
|
||||
|
||||
init() {
|
||||
if AppDefaults.shared.downloadTestBuilds == false {
|
||||
AppDefaults.store.setValue(releaseBuildsURL, forKey: appcastDefaultsKey)
|
||||
} else {
|
||||
AppDefaults.store.setValue(testBuildsURL, forKey: appcastDefaultsKey)
|
||||
}
|
||||
}
|
||||
|
||||
func updateAppcast() {
|
||||
if AppDefaults.shared.downloadTestBuilds == false {
|
||||
AppDefaults.store.setValue(releaseBuildsURL, forKey: appcastDefaultsKey)
|
||||
} else {
|
||||
AppDefaults.store.setValue(testBuildsURL, forKey: appcastDefaultsKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
//
|
||||
// AdvancedPreferencesView.swift
|
||||
// macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 27/6/20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AdvancedPreferencesView: View {
|
||||
|
||||
@StateObject private var preferences = AppDefaults.shared
|
||||
@StateObject private var viewModel = AdvancedPreferencesModel()
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Toggle("Check for app updates automatically", isOn: $preferences.checkForUpdatesAutomatically)
|
||||
Toggle("Download Test Builds", isOn: $preferences.downloadTestBuilds)
|
||||
Text("If you’re not sure, don’t enable test builds. Test builds may have bugs, which may include crashing bugs and data loss.")
|
||||
.foregroundColor(.secondary)
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Check for Updates") {
|
||||
appDelegate.softwareUpdater.checkForUpdates()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Toggle("Send Crash Logs Automatically", isOn: $preferences.sendCrashLogs)
|
||||
Divider()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Privacy Policy", action: {
|
||||
NSWorkspace.shared.open(URL(string: "https://netnewswire.com/privacypolicy")!)
|
||||
})
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.onChange(of: preferences.downloadTestBuilds, perform: { _ in
|
||||
viewModel.updateAppcast()
|
||||
})
|
||||
.frame(width: 400, alignment: .center)
|
||||
.lineLimit(3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
//
|
||||
// GeneralPreferencesModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 12/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
|
||||
class GeneralPreferencesModel: ObservableObject {
|
||||
|
||||
@Published var rssReaders = [RSSReader]()
|
||||
@Published var readerSelection: Int = 0 {
|
||||
willSet {
|
||||
if newValue != readerSelection {
|
||||
registerAppWithBundleID(rssReaders[newValue].bundleID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let readerInfo = RSSReaderInfo()
|
||||
|
||||
init() {
|
||||
prepareRSSReaders()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK:- RSS Readers
|
||||
|
||||
private extension GeneralPreferencesModel {
|
||||
|
||||
func prepareRSSReaders() {
|
||||
|
||||
// Populate rssReaders
|
||||
var thisApp = RSSReader(bundleID: Bundle.main.bundleIdentifier!)
|
||||
thisApp?.nameMinusAppSuffix.append(" (this app—multiplatform)")
|
||||
|
||||
let otherRSSReaders = readerInfo.rssReaders.filter { $0.bundleID != Bundle.main.bundleIdentifier! }.sorted(by: { $0.nameMinusAppSuffix < $1.nameMinusAppSuffix })
|
||||
rssReaders.append(thisApp!)
|
||||
rssReaders.append(contentsOf: otherRSSReaders)
|
||||
|
||||
if readerInfo.defaultRSSReaderBundleID != nil {
|
||||
let defaultReader = rssReaders.filter({ $0.bundleID == readerInfo.defaultRSSReaderBundleID })
|
||||
if defaultReader.count == 1 {
|
||||
let reader = defaultReader[0]
|
||||
readerSelection = rssReaders.firstIndex(of: reader)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerAppWithBundleID(_ bundleID: String) {
|
||||
NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feed", to: bundleID)
|
||||
NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feeds", to: bundleID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - RSSReaderInfo
|
||||
|
||||
struct RSSReaderInfo {
|
||||
|
||||
var defaultRSSReaderBundleID: String? {
|
||||
NSWorkspace.shared.defaultAppBundleID(forURLScheme: RSSReaderInfo.feedURLScheme)
|
||||
}
|
||||
let rssReaders: Set<RSSReader>
|
||||
static let feedURLScheme = "feed:"
|
||||
|
||||
init() {
|
||||
self.rssReaders = RSSReaderInfo.fetchRSSReaders()
|
||||
}
|
||||
|
||||
static func fetchRSSReaders() -> Set<RSSReader> {
|
||||
let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: feedURLScheme)
|
||||
|
||||
var rssReaders = Set<RSSReader>()
|
||||
rssReaderBundleIDs.forEach { (bundleID) in
|
||||
if let reader = RSSReader(bundleID: bundleID) {
|
||||
rssReaders.insert(reader)
|
||||
}
|
||||
}
|
||||
return rssReaders
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - RSSReader
|
||||
|
||||
struct RSSReader: Hashable {
|
||||
|
||||
let bundleID: String
|
||||
let name: String
|
||||
var nameMinusAppSuffix: String
|
||||
let path: String
|
||||
|
||||
init?(bundleID: String) {
|
||||
guard let path = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.path = path.path
|
||||
self.bundleID = bundleID
|
||||
|
||||
let name = (self.path as NSString).lastPathComponent
|
||||
self.name = name
|
||||
if name.hasSuffix(".app") {
|
||||
self.nameMinusAppSuffix = name.stripping(suffix: ".app")
|
||||
}
|
||||
else {
|
||||
self.nameMinusAppSuffix = name
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(bundleID)
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
static func ==(lhs: RSSReader, rhs: RSSReader) -> Bool {
|
||||
return lhs.bundleID == rhs.bundleID
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// GeneralPreferencesView.swift
|
||||
// macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 27/6/20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct GeneralPreferencesView: View {
|
||||
|
||||
@StateObject private var defaults = AppDefaults.shared
|
||||
@StateObject private var preferences = GeneralPreferencesModel()
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Picker("Refresh feeds:",
|
||||
selection: $defaults.interval,
|
||||
content: {
|
||||
ForEach(RefreshInterval.allCases, content: { interval in
|
||||
Text(interval.description())
|
||||
.tag(interval.rawValue)
|
||||
})
|
||||
})
|
||||
|
||||
Picker("Default RSS reader:", selection: $preferences.readerSelection, content: {
|
||||
ForEach(0..<preferences.rssReaders.count, content: { index in
|
||||
if index > 0 && preferences.rssReaders[index].nameMinusAppSuffix.contains("NetNewsWire") {
|
||||
Text(preferences.rssReaders[index].nameMinusAppSuffix.appending(" (old version)"))
|
||||
|
||||
} else {
|
||||
Text(preferences.rssReaders[index].nameMinusAppSuffix)
|
||||
.tag(index)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Toggle("Confirm when deleting feeds and folders", isOn: $defaults.sidebarConfirmDelete)
|
||||
|
||||
Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground)
|
||||
Toggle("Hide Unread Count in Dock", isOn: $defaults.hideDockUnreadCount)
|
||||
|
||||
Picker("Safari Extension:",
|
||||
selection: $defaults.subscribeToFeedsInDefaultBrowser,
|
||||
content: {
|
||||
Text("Open feeds in NetNewsWire").tag(false)
|
||||
Text("Open feeds in default news reader").tag(true)
|
||||
}).pickerStyle(RadioGroupPickerStyle())
|
||||
}
|
||||
.frame(width: 400, alignment: .center)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
//
|
||||
// LayoutPreferencesView.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 17/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LayoutPreferencesView: View {
|
||||
|
||||
@StateObject private var defaults = AppDefaults.shared
|
||||
private let colorPalettes = UserInterfaceColorPalette.allCases
|
||||
private let sampleTitle = "Lorem dolor sed viverra ipsum. Gravida rutrum quisque non tellus. Rutrum tellus pellentesque eu tincidunt tortor. Sed blandit libero volutpat sed cras ornare. Et netus et malesuada fames ac. Ultrices eros in cursus turpis massa tincidunt dui ut ornare. Lacus sed viverra tellus in. Sollicitudin ac orci phasellus egestas. Purus in mollis nunc sed. Sollicitudin ac orci phasellus egestas tellus rutrum tellus pellentesque. Interdum consectetur libero id faucibus nisl tincidunt eget."
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
Form {
|
||||
Picker("Appearance", selection: $defaults.userInterfaceColorPalette, content: {
|
||||
ForEach(colorPalettes, id: \.self, content: {
|
||||
Text($0.description)
|
||||
})
|
||||
})
|
||||
|
||||
Divider()
|
||||
|
||||
Text("Timeline: ")
|
||||
Picker("Number of Lines", selection: $defaults.timelineNumberOfLines, content: {
|
||||
ForEach(1..<6, content: { i in
|
||||
Text(String(i))
|
||||
.tag(Double(i))
|
||||
})
|
||||
}).padding(.leading, 16)
|
||||
Slider(value: $defaults.timelineIconDimensions, in: 20...60, step: 10, minimumValueLabel: Text("Small"), maximumValueLabel: Text("Large"), label: {
|
||||
Text("Icon size")
|
||||
}).padding(.leading, 16)
|
||||
|
||||
}
|
||||
|
||||
timelineRowPreview
|
||||
.frame(width: 300)
|
||||
.padding()
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8, style: .continuous)
|
||||
.stroke(Color.gray, lineWidth: 1)
|
||||
)
|
||||
.animation(.default)
|
||||
|
||||
Text("PREVIEW")
|
||||
.font(.caption)
|
||||
.tracking(0.3)
|
||||
Spacer()
|
||||
|
||||
}.frame(width: 400, height: 300, alignment: .center)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
var timelineRowPreview: some View {
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "circle.fill")
|
||||
.resizable()
|
||||
.frame(width: 10, height: 10, alignment: .top)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
Image(systemName: "paperplane.circle")
|
||||
.resizable()
|
||||
.frame(width: CGFloat(defaults.timelineIconDimensions), height: CGFloat(defaults.timelineIconDimensions), alignment: .top)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(sampleTitle)
|
||||
.font(.headline)
|
||||
.lineLimit(Int(defaults.timelineNumberOfLines))
|
||||
HStack {
|
||||
Text("Feed Name")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.footnote)
|
||||
Spacer()
|
||||
Text("10:31")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
struct SwiftUIView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LayoutPreferencesView()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user