diff --git a/Multiplatform/Shared/Account Management/FixAccountCredentialView.swift b/Multiplatform/Shared/Account Management/FixAccountCredentialView.swift deleted file mode 100644 index ecb585d70..000000000 --- a/Multiplatform/Shared/Account Management/FixAccountCredentialView.swift +++ /dev/null @@ -1,167 +0,0 @@ -// -// FixAccountCredentialView.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 24/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct FixAccountCredentialView: View { - - let accountSyncError: AccountSyncError - @Environment(\.presentationMode) var presentationMode - @StateObject private var editModel = EditAccountCredentialsModel() - - - var body: some View { - #if os(macOS) - MacForm - .onAppear { - editModel.retrieveCredentials(accountSyncError.account) - } - .onChange(of: editModel.accountCredentialsWereUpdated) { value in - if value == true { - 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() - #else - iOSForm - .onAppear { - editModel.retrieveCredentials(accountSyncError.account) - } - .onChange(of: editModel.accountCredentialsWereUpdated) { value in - if value == true { - 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 - })) - } - #endif - - - } - - var MacForm: some View { - Form { - header - HStack(alignment: .center) { - VStack(alignment: .trailing, spacing: 12) { - Text("Username: ") - Text("Password: ") - if accountSyncError.account.type == .freshRSS { - Text("API URL: ") - } - }.frame(width: 75) - - VStack(alignment: .leading, spacing: 12) { - accountFields - } - } - .textFieldStyle(RoundedBorderTextFieldStyle()) - - Spacer() - HStack{ - if editModel.accountIsUpdatingCredentials { - ProgressView("Updating") - } - Spacer() - cancelButton - updateButton - } - }.frame(height: 220) - } - - #if os(iOS) - var iOSForm: some View { - - NavigationView { - List { - Section(header: header, content: { - accountFields - }) - } - .listStyle(InsetGroupedListStyle()) - .navigationBarItems( - leading: - cancelButton - , trailing: - HStack { - if editModel.accountIsUpdatingCredentials { - ProgressView() - .frame(width: 20 , height: 20) - .padding(.horizontal, 4) - } - updateButton - } - - ) - } - } - #endif - - var header: some View { - HStack { - Spacer() - VStack { - Image(rsImage: accountSyncError.account.smallIcon!.image) - .resizable() - .frame(width: 30, height: 30) - Text(accountSyncError.account.nameForDisplay) - Text(accountSyncError.error.localizedDescription) - .multilineTextAlignment(.center) - .lineLimit(3) - .padding(.top, 4) - } - Spacer() - }.padding() - } - - @ViewBuilder - var accountFields: some View { - TextField("Username", text: $editModel.userName) - SecureField("Password", text: $editModel.password) - if accountSyncError.account.type == .freshRSS { - TextField("API URL", text: $editModel.apiUrl) - } - } - - @ViewBuilder - var updateButton: some View { - if accountSyncError.account.type != .freshRSS { - Button("Update", action: { - editModel.updateAccountCredentials(accountSyncError.account) - }).disabled(editModel.userName.count == 0 || editModel.password.count == 0) - } else { - Button("Update", action: { - editModel.updateAccountCredentials(accountSyncError.account) - }).disabled(editModel.userName.count == 0 || editModel.password.count == 0 || editModel.apiUrl.count == 0) - } - } - - var cancelButton: some View { - Button("Cancel", action: { - presentationMode.wrappedValue.dismiss() - }) - } - -} - diff --git a/Multiplatform/Shared/Add/Add Account Models/AddAccountSignUp.swift b/Multiplatform/Shared/Add/Add Account Models/AddAccountSignUp.swift deleted file mode 100644 index d2c11d5cd..000000000 --- a/Multiplatform/Shared/Add/Add Account Models/AddAccountSignUp.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// AddAccountSignUp.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 06/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import Account -#if os(iOS) -import UIKit -#endif - - -/// Helper functions common to most account services. -protocol AddAccountSignUp { - func presentSignUpOption(_ accountType: AccountType) -} - - -extension AddAccountSignUp { - func presentSignUpOption(_ accountType: AccountType) { - #if os(macOS) - switch accountType { - case .bazQux: - NSWorkspace.shared.open(URL(string: "https://bazqux.com")!) - case .feedbin: - NSWorkspace.shared.open(URL(string: "https://feedbin.com/signup")!) - case .feedly: - NSWorkspace.shared.open(URL(string: "https://feedly.com")!) - case .feedWrangler: - NSWorkspace.shared.open(URL(string: "https://feedwrangler.net/users/new")!) - case .freshRSS: - NSWorkspace.shared.open(URL(string: "https://freshrss.org")!) - case .inoreader: - NSWorkspace.shared.open(URL(string: "https://www.inoreader.com")!) - case .newsBlur: - NSWorkspace.shared.open(URL(string: "https://newsblur.com")!) - case .theOldReader: - NSWorkspace.shared.open(URL(string: "https://theoldreader.com")!) - default: - return - } - #else - switch accountType { - case .bazQux: - UIApplication.shared.open(URL(string: "https://bazqux.com")!, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : false], completionHandler: nil) - case .feedbin: - UIApplication.shared.open(URL(string: "https://feedbin.com/signup")!, options: [:], completionHandler: nil) - case .feedly: - UIApplication.shared.open(URL(string: "https://feedly.com")!, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : false], completionHandler: nil) - case .feedWrangler: - UIApplication.shared.open(URL(string: "https://feedwrangler.net/users/new")!, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : false], completionHandler: nil) - case .freshRSS: - UIApplication.shared.open(URL(string: "https://freshrss.org")!, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : false], completionHandler: nil) - case .inoreader: - UIApplication.shared.open(URL(string: "https://www.inoreader.com")!, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : false], completionHandler: nil) - case .newsBlur: - UIApplication.shared.open(URL(string: "https://newsblur.com")!, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : false], completionHandler: nil) - case .theOldReader: - UIApplication.shared.open(URL(string: "https://theoldreader.com")!, options: [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly : false], completionHandler: nil) - default: - return - } - #endif - } -} diff --git a/Multiplatform/Shared/Add/Add Account Models/AddFeedWranglerViewModel.swift b/Multiplatform/Shared/Add/Add Account Models/AddFeedWranglerViewModel.swift deleted file mode 100644 index f05ac68d1..000000000 --- a/Multiplatform/Shared/Add/Add Account Models/AddFeedWranglerViewModel.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// AddFeedWranglerViewModel.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 05/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -class AddFeedWranglerViewModel: ObservableObject, AddAccountSignUp { - @Published var isAuthenticating: Bool = false - @Published var accountUpdateError: AccountUpdateErrors = .none - @Published var showError: Bool = false - @Published var username: String = "" - @Published var password: String = "" - @Published var canDismiss: Bool = false - @Published var showPassword: Bool = false - - func authenticateFeedWrangler() { - - isAuthenticating = true - let credentials = Credentials(type: .feedWranglerBasic, username: username, secret: password) - - Account.validateCredentials(type: .feedWrangler, credentials: credentials) { result in - - - self.isAuthenticating = false - - switch result { - case .success(let validatedCredentials): - - guard let validatedCredentials = validatedCredentials else { - self.accountUpdateError = .invalidUsernamePassword - self.showError = true - 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) - self.canDismiss = true - account.refreshAll(completion: { result in - switch result { - case .success: - break - case .failure(let error): - self.accountUpdateError = .other(error: error) - self.showError = true - } - }) - } catch { - self.accountUpdateError = .keyChainError - self.showError = true - } - case .failure: - self.accountUpdateError = .networkError - self.showError = true - } - } - } -} diff --git a/Multiplatform/Shared/Add/Add Account Models/AddFeedbinViewModel.swift b/Multiplatform/Shared/Add/Add Account Models/AddFeedbinViewModel.swift deleted file mode 100644 index a043fec28..000000000 --- a/Multiplatform/Shared/Add/Add Account Models/AddFeedbinViewModel.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// AddFeedbinViewModel.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 05/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -class AddFeedbinViewModel: ObservableObject, AddAccountSignUp { - @Published var isAuthenticating: Bool = false - @Published var accountUpdateError: AccountUpdateErrors = .none - @Published var showError: Bool = false - @Published var username: String = "" - @Published var password: String = "" - @Published var canDismiss: Bool = false - @Published var showPassword: Bool = false - - func authenticateFeedbin() { - isAuthenticating = true - let credentials = Credentials(type: .basic, username: username, secret: password) - - Account.validateCredentials(type: .feedbin, credentials: credentials) { result in - self.isAuthenticating = false - - switch result { - case .success(let validatedCredentials): - - guard let validatedCredentials = validatedCredentials else { - self.accountUpdateError = .invalidUsernamePassword - self.showError = true - return - } - - let account = AccountManager.shared.createAccount(type: .feedbin) - - do { - try account.removeCredentials(type: .basic) - try account.storeCredentials(validatedCredentials) - self.isAuthenticating = false - self.canDismiss = true - account.refreshAll(completion: { result in - switch result { - case .success: - break - case .failure(let error): - self.accountUpdateError = .other(error: error) - self.showError = true - } - }) - - } catch { - self.accountUpdateError = .keyChainError - self.showError = true - } - - case .failure: - self.accountUpdateError = .networkError - self.showError = true - } - } - } -} diff --git a/Multiplatform/Shared/Add/Add Account Models/AddFeedlyViewModel.swift b/Multiplatform/Shared/Add/Add Account Models/AddFeedlyViewModel.swift deleted file mode 100644 index 7312b2f61..000000000 --- a/Multiplatform/Shared/Add/Add Account Models/AddFeedlyViewModel.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// AddFeedlyViewModel.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 05/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -class AddFeedlyViewModel: ObservableObject, OAuthAccountAuthorizationOperationDelegate, AddAccountSignUp { - @Published var isAuthenticating: Bool = false - @Published var accountUpdateError: AccountUpdateErrors = .none - @Published var showError: Bool = false - @Published var username: String = "" - @Published var password: String = "" - - func authenticateFeedly() { - isAuthenticating = true - let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly) - addAccount.delegate = self - #if os(macOS) - addAccount.presentationAnchor = NSApplication.shared.windows.last - #else - addAccount.presentationAnchor = UIApplication.shared.windows.last - #endif - MainThreadOperationQueue.shared.add(addAccount) - } - - func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { - - isAuthenticating = false - - // macOS only: `ASWebAuthenticationSession` leaves the browser in the foreground. - // Ensure the app is in the foreground so the user can see their Feedly account load. - #if os(macOS) - NSApplication.shared.activate(ignoringOtherApps: true) - #endif - - account.refreshAll { [weak self] result in - switch result { - case .success: - break - case .failure(let error): - self?.accountUpdateError = .other(error: error) - self?.showError = true - } - } - } - - func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { - isAuthenticating = false - - // macOS only: `ASWebAuthenticationSession` leaves the browser in the foreground. - // Ensure the app is in the foreground so the user can see the error. - #if os(macOS) - NSApplication.shared.activate(ignoringOtherApps: true) - #endif - - accountUpdateError = .other(error: error) - showError = true - } -} diff --git a/Multiplatform/Shared/Add/Add Account Models/AddNewsBlurViewModel.swift b/Multiplatform/Shared/Add/Add Account Models/AddNewsBlurViewModel.swift deleted file mode 100644 index 96f58b78f..000000000 --- a/Multiplatform/Shared/Add/Add Account Models/AddNewsBlurViewModel.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// AddNewsBlurViewModel.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 05/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -class AddNewsBlurViewModel: ObservableObject, AddAccountSignUp { - @Published var isAuthenticating: Bool = false - @Published var accountUpdateError: AccountUpdateErrors = .none - @Published var showError: Bool = false - @Published var username: String = "" - @Published var password: String = "" - @Published var canDismiss: Bool = false - @Published var showPassword: Bool = false - - func authenticateNewsBlur() { - isAuthenticating = true - let credentials = Credentials(type: .newsBlurBasic, username: username, secret: password) - - Account.validateCredentials(type: .newsBlur, credentials: credentials) { result in - - self.isAuthenticating = false - - switch result { - case .success(let validatedCredentials): - - guard let validatedCredentials = validatedCredentials else { - self.accountUpdateError = .invalidUsernamePassword - self.showError = true - 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) - self.canDismiss = true - account.refreshAll(completion: { result in - switch result { - case .success: - break - case .failure(let error): - self.accountUpdateError = .other(error: error) - self.showError = true - } - }) - - } catch { - self.accountUpdateError = .keyChainError - self.showError = true - } - - case .failure: - self.accountUpdateError = .networkError - self.showError = true - } - } - } - -} diff --git a/Multiplatform/Shared/Add/Add Account Models/AddReaderAPIViewModel.swift b/Multiplatform/Shared/Add/Add Account Models/AddReaderAPIViewModel.swift deleted file mode 100644 index c6e67897f..000000000 --- a/Multiplatform/Shared/Add/Add Account Models/AddReaderAPIViewModel.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// AddReaderAPIViewModel.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 05/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -class AddReaderAPIViewModel: ObservableObject, AddAccountSignUp { - @Published var isAuthenticating: Bool = false - @Published var accountUpdateError: AccountUpdateErrors = .none - @Published var showError: Bool = false - @Published var username: String = "" - @Published var password: String = "" - @Published var apiUrl: String = "" - @Published var canDismiss: Bool = false - @Published var showPassword: Bool = false - - func authenticateReaderAccount(_ accountType: AccountType) { - isAuthenticating = true - - let credentials = Credentials(type: .readerBasic, username: username, secret: password) - - if accountType == .freshRSS { - Account.validateCredentials(type: accountType, credentials: credentials, endpoint: URL(string: apiUrl)!) { result in - - self.isAuthenticating = false - - switch result { - case .success(let validatedCredentials): - - guard let validatedCredentials = validatedCredentials else { - self.accountUpdateError = .invalidUsernamePassword - self.showError = true - return - } - - let account = AccountManager.shared.createAccount(type: .freshRSS) - - do { - try account.removeCredentials(type: .readerBasic) - try account.removeCredentials(type: .readerAPIKey) - try account.storeCredentials(credentials) - try account.storeCredentials(validatedCredentials) - self.canDismiss = true - account.refreshAll(completion: { result in - switch result { - case .success: - break - case .failure(let error): - self.accountUpdateError = .other(error: error) - self.showError = true - } - }) - - } catch { - self.accountUpdateError = .keyChainError - self.showError = true - } - - case .failure: - self.accountUpdateError = .networkError - self.showError = true - } - } - } - - else { - - Account.validateCredentials(type: accountType, credentials: credentials) { result in - - self.isAuthenticating = false - - switch result { - case .success(let validatedCredentials): - - guard let validatedCredentials = validatedCredentials else { - self.accountUpdateError = .invalidUsernamePassword - self.showError = true - return - } - - let account = AccountManager.shared.createAccount(type: .freshRSS) - - do { - try account.removeCredentials(type: .readerBasic) - try account.removeCredentials(type: .readerAPIKey) - try account.storeCredentials(credentials) - try account.storeCredentials(validatedCredentials) - self.canDismiss = true - account.refreshAll(completion: { result in - switch result { - case .success: - break - case .failure(let error): - self.accountUpdateError = .other(error: error) - self.showError = true - } - }) - - } catch { - self.accountUpdateError = .keyChainError - self.showError = true - } - - case .failure: - self.accountUpdateError = .networkError - self.showError = true - } - } - - } - - } - -} diff --git a/Multiplatform/Shared/Add/Add Account Sheets/AddCloudKitAccountView.swift b/Multiplatform/Shared/Add/Add Account Sheets/AddCloudKitAccountView.swift deleted file mode 100644 index b7ee03318..000000000 --- a/Multiplatform/Shared/Add/Add Account Sheets/AddCloudKitAccountView.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// AddCloudKitAccountView.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 03/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct AddCloudKitAccountView: View { - - @Environment (\.presentationMode) var presentationMode - - var body: some View { - - #if os(macOS) - macBody - #else - NavigationView { - iosBody - } - #endif - - } - - #if os(iOS) - var iosBody: some View { - List { - Section(header: formHeader, footer: formFooter, content: { - Button(action: { - _ = AccountManager.shared.createAccount(type: .cloudKit) - presentationMode.wrappedValue.dismiss() - }, label: { - HStack { - Spacer() - Text("Add Account") - Spacer() - } - }).disabled(AccountManager.shared.activeAccounts.filter({ $0.type == .cloudKit }).count > 0) - }) - }.navigationBarItems(leading: - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - }) - ) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(Text(AccountType.cloudKit.localizedAccountName())) - .listStyle(InsetGroupedListStyle()) - } - #endif - - #if os(macOS) - var macBody: some View { - VStack { - HStack(spacing: 16) { - VStack(alignment: .leading) { - AccountType.cloudKit.image() - .resizable() - .frame(width: 50, height: 50) - Spacer() - } - VStack(alignment: .leading, spacing: 8) { - Text("Sign in to your iCloud account.") - .font(.headline) - - Text("This account syncs across your Mac and iOS devices using your iCloud account.") - .foregroundColor(.secondary) - .font(.callout) - .lineLimit(2) - .padding(.top, 4) - - Spacer() - HStack(spacing: 8) { - Spacer() - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - .frame(width: 60) - }).keyboardShortcut(.cancelAction) - - Button(action: { - _ = AccountManager.shared.createAccount(type: .cloudKit) - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Create") - .frame(width: 60) - }) - .keyboardShortcut(.defaultAction) - .disabled(AccountManager.shared.activeAccounts.filter({ $0.type == .cloudKit }).count > 0) - } - } - } - } - .padding() - .frame(minWidth: 400, maxWidth: 400, maxHeight: 150) - } - #endif - - var formHeader: some View { - HStack { - Spacer() - VStack(alignment: .center) { - AccountType.cloudKit.image() - .resizable() - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.vertical) - } - - var formFooter: some View { - HStack { - Spacer() - VStack(spacing: 8) { - Text("This account syncs across your Mac and iOS devices using your iCloud account.").foregroundColor(.secondary) - } - .multilineTextAlignment(.center) - .font(.caption) - Spacer() - - }.padding(.vertical) - } -} - -struct AddCloudKitAccountView_Previews: PreviewProvider { - static var previews: some View { - AddCloudKitAccountView() - } -} diff --git a/Multiplatform/Shared/Add/Add Account Sheets/AddFeedWranglerAccountView.swift b/Multiplatform/Shared/Add/Add Account Sheets/AddFeedWranglerAccountView.swift deleted file mode 100644 index 5fe5d27c7..000000000 --- a/Multiplatform/Shared/Add/Add Account Sheets/AddFeedWranglerAccountView.swift +++ /dev/null @@ -1,216 +0,0 @@ -// -// AddFeedWranglerAccountView.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 03/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - - -struct AddFeedWranglerAccountView: View { - - @Environment (\.presentationMode) var presentationMode - @StateObject private var model = AddFeedWranglerViewModel() - - var body: some View { - #if os(macOS) - macBody - #else - NavigationView { - iosBody - } - #endif - } - - - #if os(iOS) - var iosBody: some View { - List { - Section(header: formHeader, content: { - TextField("Email", text: $model.username) - if model.showPassword == false { - ZStack { - HStack { - SecureField("Password", text: $model.password) - Spacer() - Image(systemName: "eye.fill") - .foregroundColor(.accentColor) - .onTapGesture { - model.showPassword = true - } - } - } - } - else { - ZStack { - HStack { - TextField("Password", text: $model.password) - Spacer() - Image(systemName: "eye.slash.fill") - .foregroundColor(.accentColor) - .onTapGesture { - model.showPassword = false - } - } - } - } - - }) - - Section(footer: formFooter, content: { - Button(action: { - model.authenticateFeedWrangler() - }, label: { - HStack { - Spacer() - Text("Add Account") - Spacer() - } - }).disabled(model.username.isEmpty || model.password.isEmpty) - }) - - } - .navigationBarItems(leading: - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - })) - .listStyle(InsetGroupedListStyle()) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(Text("Feed Wrangler")) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel(Text("Dismiss"))) - }) - .onReceive(model.$canDismiss, perform: { value in - if value == true { - presentationMode.wrappedValue.dismiss() - } - }) - } - #endif - - #if os(macOS) - var macBody: some View { - VStack { - HStack(spacing: 16) { - VStack(alignment: .leading) { - AccountType.feedWrangler.image() - .resizable() - .frame(width: 50, height: 50) - Spacer() - } - VStack(alignment: .leading, spacing: 8) { - Text("Sign in to your Feed Wrangler account.") - .font(.headline) - HStack { - Text("Don’t have a Feed Wrangler account?") - .font(.callout) - Button(action: { - model.presentSignUpOption(.feedWrangler) - }, label: { - Text("Sign up here.").font(.callout) - }).buttonStyle(LinkButtonStyle()) - } - - HStack { - VStack(alignment: .trailing, spacing: 14) { - Text("Email") - Text("Password") - } - VStack(spacing: 8) { - TextField("me@email.com", text: $model.username) - SecureField("•••••••••••", text: $model.password) - } - } - - Text("Your username and password will be encrypted and stored in Keychain.") - .foregroundColor(.secondary) - .font(.callout) - .lineLimit(2) - .padding(.top, 4) - - Spacer() - HStack(spacing: 8) { - Spacer() - ProgressView() - .scaleEffect(CGSize(width: 0.5, height: 0.5)) - .hidden(!model.isAuthenticating) - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - .frame(width: 60) - }).keyboardShortcut(.cancelAction) - - Button(action: { - model.authenticateFeedWrangler() - }, label: { - Text("Sign In") - .frame(width: 60) - }) - .keyboardShortcut(.defaultAction) - .disabled(model.username.isEmpty || model.password.isEmpty) - } - } - } - } - .padding() - .frame(minWidth: 400, maxWidth: 400, minHeight: 230, maxHeight: 260) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel()) - }) - .onReceive(model.$canDismiss, perform: { value in - if value == true { - presentationMode.wrappedValue.dismiss() - } - }) - } - #endif - - var formHeader: some View { - HStack { - Spacer() - VStack(alignment: .center) { - AccountType.feedWrangler.image() - .resizable() - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.vertical) - } - - var formFooter: some View { - HStack { - Spacer() - VStack(spacing: 8) { - Text("Sign in to your Feed Wrangler account and sync your feeds across your devices. Your username and password and password will be encrypted and stored in Keychain.").foregroundColor(.secondary) - Text("Don’t have a Feed Wrangler account?").foregroundColor(.secondary) - Button(action: { - model.presentSignUpOption(.feedWrangler) - }, label: { - Text("Sign Up Here").foregroundColor(.blue).multilineTextAlignment(.center) - }) - ProgressView().hidden(!model.isAuthenticating) - } - .multilineTextAlignment(.center) - .font(.caption2) - Spacer() - - }.padding(.vertical) - } - -} - -struct AddFeedWranglerAccountView_Previews: PreviewProvider { - static var previews: some View { - AddFeedWranglerAccountView() - } -} diff --git a/Multiplatform/Shared/Add/Add Account Sheets/AddFeedbinAccountView.swift b/Multiplatform/Shared/Add/Add Account Sheets/AddFeedbinAccountView.swift deleted file mode 100644 index dd33b6434..000000000 --- a/Multiplatform/Shared/Add/Add Account Sheets/AddFeedbinAccountView.swift +++ /dev/null @@ -1,214 +0,0 @@ -// -// AddFeedbinAccountView.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 02/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -struct AddFeedbinAccountView: View { - - @Environment (\.presentationMode) var presentationMode - @StateObject private var model = AddFeedbinViewModel() - - var body: some View { - #if os(macOS) - macBody - #else - NavigationView { - iosBody - } - #endif - } - - #if os(iOS) - var iosBody: some View { - List { - Section(header: formHeader, content: { - TextField("Email", text: $model.username) - if model.showPassword == false { - ZStack { - HStack { - SecureField("Password", text: $model.password) - Spacer() - Image(systemName: "eye.fill") - .foregroundColor(.accentColor) - .onTapGesture { - model.showPassword = true - } - } - } - } - else { - ZStack { - HStack { - TextField("Password", text: $model.password) - Spacer() - Image(systemName: "eye.slash.fill") - .foregroundColor(.accentColor) - .onTapGesture { - model.showPassword = false - } - } - } - } - - }) - - Section(footer: formFooter, content: { - Button(action: { - model.authenticateFeedbin() - }, label: { - HStack { - Spacer() - Text("Add Account") - Spacer() - } - }).disabled(model.username.isEmpty || model.password.isEmpty) - }) - - } - .navigationBarItems(leading: - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - })) - .listStyle(InsetGroupedListStyle()) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(Text("Feedbin")) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel(Text("Dismiss"))) - }) - .onReceive(model.$canDismiss, perform: { value in - if value == true { - presentationMode.wrappedValue.dismiss() - } - }) - } - #endif - - #if os(macOS) - var macBody: some View { - VStack { - HStack(spacing: 16) { - VStack(alignment: .leading) { - AccountType.feedbin.image() - .frame(width: 50, height: 50) - Spacer() - } - VStack(alignment: .leading, spacing: 8) { - Text("Sign in to your Feedbin account.") - .font(.headline) - HStack { - Text("Don’t have a Feedbin account?") - .font(.callout) - Button(action: { - model.presentSignUpOption(.feedbin) - }, label: { - Text("Sign up here.").font(.callout) - }).buttonStyle(LinkButtonStyle()) - } - - HStack { - VStack(alignment: .trailing, spacing: 14) { - Text("Email") - Text("Password") - } - VStack(spacing: 8) { - TextField("me@email.com", text: $model.username) - SecureField("•••••••••••", text: $model.password) - } - } - - Text("Your username and password will be encrypted and stored in Keychain.") - .foregroundColor(.secondary) - .font(.callout) - .lineLimit(2) - .padding(.top, 4) - - Spacer() - HStack(spacing: 8) { - Spacer() - ProgressView() - .scaleEffect(CGSize(width: 0.5, height: 0.5)) - .hidden(!model.isAuthenticating) - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - .frame(width: 60) - }).keyboardShortcut(.cancelAction) - - Button(action: { - model.authenticateFeedbin() - }, label: { - Text("Sign In") - .frame(width: 60) - }) - .keyboardShortcut(.defaultAction) - .disabled(model.username.isEmpty || model.password.isEmpty) - } - } - } - } - .padding() - .frame(minWidth: 400, maxWidth: 400, minHeight: 230, maxHeight: 260) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel()) - }) - .onReceive(model.$canDismiss, perform: { value in - if value == true { - presentationMode.wrappedValue.dismiss() - } - }) - } - #endif - - var formHeader: some View { - HStack { - Spacer() - VStack(alignment: .center) { - AccountType.feedbin.image() - .resizable() - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.vertical) - } - - var formFooter: some View { - HStack { - Spacer() - VStack(spacing: 8) { - Text("Sign in to your Feedbin account and sync your feeds across your devices. Your username and password and password will be encrypted and stored in Keychain.").foregroundColor(.secondary) - Text("Don’t have a Feedbin account?").foregroundColor(.secondary) - Button(action: { - model.presentSignUpOption(.feedbin) - }, label: { - Text("Sign Up Here").foregroundColor(.blue).multilineTextAlignment(.center) - }) - ProgressView().hidden(!model.isAuthenticating) - } - .multilineTextAlignment(.center) - .font(.caption2) - Spacer() - - }.padding(.vertical) - } - - -} - -struct AddFeedbinAccountView_Previews: PreviewProvider { - static var previews: some View { - AddFeedbinAccountView() - } -} diff --git a/Multiplatform/Shared/Add/Add Account Sheets/AddFeedlyAccountView.swift b/Multiplatform/Shared/Add/Add Account Sheets/AddFeedlyAccountView.swift deleted file mode 100644 index 32f14553a..000000000 --- a/Multiplatform/Shared/Add/Add Account Sheets/AddFeedlyAccountView.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// AddFeedlyAccountView.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 05/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -struct AddFeedlyAccountView: View { - - @Environment (\.presentationMode) var presentationMode - @StateObject private var model = AddFeedlyViewModel() - - var body: some View { - #if os(macOS) - macBody - #else - NavigationView { - iosBody - } - #endif - } - - - #if os(iOS) - var iosBody: some View { - List { - Section(header: formHeader, footer: formFooter, content: { - Button(action: { - model.authenticateFeedly() - }, label: { - HStack { - Spacer() - Text("Add Account") - Spacer() - } - }) - }) - }.navigationBarItems(leading: - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - }) - ) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(Text(AccountType.feedly.localizedAccountName())) - .listStyle(InsetGroupedListStyle()) - - } - #endif - - #if os(macOS) - var macBody: some View { - VStack { - HStack(spacing: 16) { - VStack(alignment: .leading) { - AccountType.feedly.image() - .resizable() - .frame(width: 50, height: 50) - Spacer() - } - VStack(alignment: .leading, spacing: 8) { - Text("Sign in to your Feedly account.") - .font(.headline) - HStack { - Text("Don’t have a Feedly account?") - .font(.callout) - Button(action: { - model.presentSignUpOption(.feedly) - }, label: { - Text("Sign up here.").font(.callout) - }).buttonStyle(LinkButtonStyle()) - } - - Spacer() - HStack(spacing: 8) { - Spacer() - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - .frame(width: 60) - }).keyboardShortcut(.cancelAction) - - Button(action: { - model.authenticateFeedly() - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Sign In") - .frame(width: 60) - }) - .keyboardShortcut(.defaultAction) - .disabled(AccountManager.shared.activeAccounts.filter({ $0.type == .cloudKit }).count > 0) - } - } - } - } - .padding() - .frame(minWidth: 400, maxWidth: 400, maxHeight: 150) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel()) - }) - } - #endif - - var formHeader: some View { - HStack { - Spacer() - VStack(alignment: .center) { - AccountType.feedly.image() - .resizable() - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.vertical) - } - - var formFooter: some View { - HStack { - Spacer() - VStack(spacing: 8) { - Text("Sign in to your Feedly account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an Feedly account?").foregroundColor(.secondary) - Button(action: { - model.presentSignUpOption(.feedly) - }, label: { - Text("Sign Up Here").foregroundColor(.blue).multilineTextAlignment(.center) - }) - } - .multilineTextAlignment(.center) - .font(.caption) - Spacer() - - }.padding(.vertical) - } - -} - -struct AddFeedlyAccountView_Previews: PreviewProvider { - static var previews: some View { - AddFeedlyAccountView() - } -} diff --git a/Multiplatform/Shared/Add/Add Account Sheets/AddLocalAccountView.swift b/Multiplatform/Shared/Add/Add Account Sheets/AddLocalAccountView.swift deleted file mode 100644 index e24923273..000000000 --- a/Multiplatform/Shared/Add/Add Account Sheets/AddLocalAccountView.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// AddLocalAccountView.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 02/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore - -struct AddLocalAccountView: View { - - @State private var newAccountName: String = "" - @Environment (\.presentationMode) var presentationMode - - var body: some View { - #if os(macOS) - macBody - #else - NavigationView { - iosBody - } - #endif - } - - #if os(iOS) - var iosBody: some View { - List { - Section(header: formHeader, content: { - TextField("Account Name", text: $newAccountName) - }) - - Section(footer: formFooter, content: { - Button(action: { - let newAccount = AccountManager.shared.createAccount(type: .onMyMac) - newAccount.name = newAccountName - presentationMode.wrappedValue.dismiss() - }, label: { - HStack { - Spacer() - Text("Add Account") - Spacer() - } - }) - }) - }.navigationBarItems(leading: - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - }) - ) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(Text(AccountType.onMyMac.localizedAccountName())) - .listStyle(InsetGroupedListStyle()) - } - #endif - - #if os(macOS) - var macBody: some View { - VStack { - HStack(spacing: 16) { - VStack(alignment: .leading) { - AccountType.onMyMac.image() - .resizable() - .frame(width: 50, height: 50) - Spacer() - } - VStack(alignment: .leading, spacing: 8) { - Text("Create a local account on your Mac.") - .font(.headline) - Text("Local accounts store their data on your Mac. They do not sync across your devices.") - .font(.callout) - .foregroundColor(.secondary) - HStack { - Text("Name: ") - TextField("Account Name", text: $newAccountName) - }.padding(.top, 8) - Spacer() - HStack(spacing: 8) { - Spacer() - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - .frame(width: 60) - }).keyboardShortcut(.cancelAction) - - Button(action: { - let newAccount = AccountManager.shared.createAccount(type: .onMyMac) - newAccount.name = newAccountName - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Create") - .frame(width: 60) - }).keyboardShortcut(.defaultAction) - } - } - } - } - .padding() - .frame(minWidth: 400, maxWidth: 400, minHeight: 230, maxHeight: 260) - .textFieldStyle(RoundedBorderTextFieldStyle()) - } - #endif - - var formHeader: some View { - HStack { - Spacer() - VStack(alignment: .center) { - AccountType.onMyMac.image() - .resizable() - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.vertical) - } - - var formFooter: some View { - HStack { - Spacer() - VStack(spacing: 8) { - Text("Local accounts do not sync your feeds across devices.").foregroundColor(.secondary) - } - .multilineTextAlignment(.center) - .font(.caption) - Spacer() - - }.padding(.vertical) - } - -} - -struct AddLocalAccount_Previews: PreviewProvider { - static var previews: some View { - AddLocalAccountView() - } -} diff --git a/Multiplatform/Shared/Add/Add Account Sheets/AddNewsBlurAccountView.swift b/Multiplatform/Shared/Add/Add Account Sheets/AddNewsBlurAccountView.swift deleted file mode 100644 index 8f6c35b1c..000000000 --- a/Multiplatform/Shared/Add/Add Account Sheets/AddNewsBlurAccountView.swift +++ /dev/null @@ -1,212 +0,0 @@ -// -// AddNewsBlurAccountView.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 03/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -struct AddNewsBlurAccountView: View { - - @Environment (\.presentationMode) var presentationMode - @StateObject private var model = AddNewsBlurViewModel() - - var body: some View { - #if os(macOS) - macBody - #else - NavigationView { - iosBody - } - #endif - } - - #if os(iOS) - var iosBody: some View { - List { - Section(header: formHeader, content: { - TextField("Email", text: $model.username) - if model.showPassword == false { - ZStack { - HStack { - SecureField("Password", text: $model.password) - Spacer() - Image(systemName: "eye.fill") - .foregroundColor(.accentColor) - .onTapGesture { - model.showPassword = true - } - } - } - } - else { - ZStack { - HStack { - TextField("Password", text: $model.password) - Spacer() - Image(systemName: "eye.slash.fill") - .foregroundColor(.accentColor) - .onTapGesture { - model.showPassword = false - } - } - } - } - - }) - - Section(footer: formFooter, content: { - Button(action: { - model.authenticateNewsBlur() - }, label: { - HStack { - Spacer() - Text("Add Account") - Spacer() - } - }).disabled(model.username.isEmpty || model.password.isEmpty) - }) - - } - .navigationBarItems(leading: - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - })) - .listStyle(InsetGroupedListStyle()) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(Text("NewsBlur")) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel(Text("Dismiss"))) - }) - .onReceive(model.$canDismiss, perform: { value in - if value == true { - presentationMode.wrappedValue.dismiss() - } - }) - } - #endif - - #if os(macOS) - var macBody: some View { - VStack { - HStack(spacing: 16) { - VStack(alignment: .leading) { - AccountType.newsBlur.image() - .frame(width: 50, height: 50) - Spacer() - } - VStack(alignment: .leading, spacing: 8) { - Text("Sign in to your NewsBlur account.") - .font(.headline) - HStack { - Text("Don’t have a NewsBlur account?") - .font(.callout) - Button(action: { - model.presentSignUpOption(.newsBlur) - }, label: { - Text("Sign up here.").font(.callout) - }).buttonStyle(LinkButtonStyle()) - } - - HStack { - VStack(alignment: .trailing, spacing: 14) { - Text("Email") - Text("Password") - } - VStack(spacing: 8) { - TextField("me@email.com", text: $model.username) - SecureField("•••••••••••", text: $model.password) - } - } - - Text("Your username and password will be encrypted and stored in Keychain.") - .foregroundColor(.secondary) - .font(.callout) - .lineLimit(2) - .padding(.top, 4) - - Spacer() - HStack(spacing: 8) { - Spacer() - ProgressView() - .scaleEffect(CGSize(width: 0.5, height: 0.5)) - .hidden(!model.isAuthenticating) - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - .frame(width: 60) - }).keyboardShortcut(.cancelAction) - - Button(action: { - model.authenticateNewsBlur() - }, label: { - Text("Sign In") - .frame(width: 60) - }) - .keyboardShortcut(.defaultAction) - .disabled(model.username.isEmpty || model.password.isEmpty) - } - } - } - } - .padding() - .frame(minWidth: 400, maxWidth: 400, minHeight: 230, maxHeight: 260) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel()) - }) - .onReceive(model.$canDismiss, perform: { value in - if value == true { - presentationMode.wrappedValue.dismiss() - } - }) - } - #endif - - var formHeader: some View { - HStack { - Spacer() - VStack(alignment: .center) { - AccountType.newsBlur.image() - .resizable() - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.vertical) - } - - var formFooter: some View { - HStack { - Spacer() - VStack(spacing: 8) { - Text("Sign in to your NewsBlur account and sync your feeds across your devices. Your username and password and password will be encrypted and stored in Keychain.").foregroundColor(.secondary) - Text("Don’t have a NewsBlur account?").foregroundColor(.secondary) - Button(action: { - model.presentSignUpOption(.newsBlur) - }, label: { - Text("Sign Up Here").foregroundColor(.blue).multilineTextAlignment(.center) - }) - ProgressView().hidden(!model.isAuthenticating) - } - .multilineTextAlignment(.center) - .font(.caption2) - Spacer() - - }.padding(.vertical) - } -} - -struct AddNewsBlurAccountView_Previews: PreviewProvider { - static var previews: some View { - AddNewsBlurAccountView() - } -} diff --git a/Multiplatform/Shared/Add/Add Account Sheets/AddReaderAPIAccountView.swift b/Multiplatform/Shared/Add/Add Account Sheets/AddReaderAPIAccountView.swift deleted file mode 100644 index 0d2f70c75..000000000 --- a/Multiplatform/Shared/Add/Add Account Sheets/AddReaderAPIAccountView.swift +++ /dev/null @@ -1,247 +0,0 @@ -// -// AddReaderAPIAccountView.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 03/12/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore -import RSWeb -import Secrets - -struct AddReaderAPIAccountView: View { - - @Environment (\.presentationMode) var presentationMode - @StateObject private var model = AddReaderAPIViewModel() - public var accountType: AccountType - - var body: some View { - #if os(macOS) - macBody - #else - NavigationView { - iosBody - } - #endif - } - - #if os(iOS) - var iosBody: some View { - List { - Section(header: formHeader, content: { - TextField("Email", text: $model.username) - if model.showPassword == false { - ZStack { - HStack { - SecureField("Password", text: $model.password) - Spacer() - Image(systemName: "eye.fill") - .foregroundColor(.accentColor) - .onTapGesture { - model.showPassword = true - } - } - } - } - else { - ZStack { - HStack { - TextField("Password", text: $model.password) - Spacer() - Image(systemName: "eye.slash.fill") - .foregroundColor(.accentColor) - .onTapGesture { - model.showPassword = false - } - } - } - } - if accountType == .freshRSS { - TextField("API URL", text: $model.apiUrl) - } - - }) - - Section(footer: formFooter, content: { - Button(action: { - model.authenticateReaderAccount(accountType) - }, label: { - HStack { - Spacer() - Text("Add Account") - Spacer() - } - }).disabled(createDisabled()) - }) - - } - .navigationBarItems(leading: - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - })) - .listStyle(InsetGroupedListStyle()) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(Text(accountType.localizedAccountName())) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel(Text("Dismiss"))) - }) - .onReceive(model.$canDismiss, perform: { value in - if value == true { - presentationMode.wrappedValue.dismiss() - } - }) - } - #endif - - #if os(macOS) - var macBody: some View { - VStack { - HStack(spacing: 16) { - VStack(alignment: .leading) { - accountType.image() - .resizable() - .frame(width: 50, height: 50) - Spacer() - } - VStack(alignment: .leading, spacing: 8) { - Text("Sign in to your \(accountType.localizedAccountName()) account.") - .font(.headline) - HStack { - if accountType == .freshRSS { - Text("Don’t have a \(accountType.localizedAccountName()) instance?") - .font(.callout) - } else { - Text("Don’t have an \(accountType.localizedAccountName()) account?") - .font(.callout) - } - Button(action: { - model.presentSignUpOption(accountType) - }, label: { - Text(accountType == .freshRSS ? "Find out more." : "Sign up here.").font(.callout) - }).buttonStyle(LinkButtonStyle()) - } - - HStack { - VStack(alignment: .trailing, spacing: 14) { - Text("Email") - Text("Password") - if accountType == .freshRSS { - Text("API URL") - } - } - VStack(spacing: 8) { - TextField("me@email.com", text: $model.username) - SecureField("•••••••••••", text: $model.password) - if accountType == .freshRSS { - TextField("https://myfreshrss.rocks", text: $model.apiUrl) - } - } - } - - Text("Your username and password will be encrypted and stored in Keychain.") - .foregroundColor(.secondary) - .font(.callout) - .lineLimit(2) - .padding(.top, 4) - - Spacer() - HStack(spacing: 8) { - Spacer() - ProgressView() - .scaleEffect(CGSize(width: 0.5, height: 0.5)) - .hidden(!model.isAuthenticating) - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Text("Cancel") - .frame(width: 60) - }).keyboardShortcut(.cancelAction) - - Button(action: { - model.authenticateReaderAccount(accountType) - }, label: { - Text("Sign In") - .frame(width: 60) - }) - .keyboardShortcut(.defaultAction) - .disabled(createDisabled()) - } - } - } - } - .padding() - .frame(width: 400, height: height()) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .alert(isPresented: $model.showError, content: { - Alert(title: Text("Sign In Error"), message: Text(model.accountUpdateError.description), dismissButton: .cancel()) - }) - .onReceive(model.$canDismiss, perform: { value in - if value == true { - presentationMode.wrappedValue.dismiss() - } - }) - } - #endif - - - - - func createDisabled() -> Bool { - if accountType == .freshRSS { - return model.username.isEmpty || model.password.isEmpty || !model.apiUrl.mayBeURL - } - return model.username.isEmpty || model.password.isEmpty - } - - func height() -> CGFloat { - if accountType == .freshRSS { - return 260 - } - return 230 - } - - var formHeader: some View { - HStack { - Spacer() - VStack(alignment: .center) { - accountType.image() - .resizable() - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.vertical) - } - - var formFooter: some View { - HStack { - Spacer() - VStack(spacing: 8) { - Text("Sign in to your \(accountType.localizedAccountName()) account and sync your feeds across your devices. Your username and password and password will be encrypted and stored in Keychain.").foregroundColor(.secondary) - Text("Don’t have a \(accountType.localizedAccountName()) instance?").foregroundColor(.secondary) - Button(action: { - model.presentSignUpOption(accountType) - }, label: { - Text("Sign Up Here").foregroundColor(.blue).multilineTextAlignment(.center) - }) - ProgressView().hidden(!model.isAuthenticating) - } - .multilineTextAlignment(.center) - .font(.caption2) - Spacer() - - }.padding(.vertical) - } - -} - -struct AddReaderAPIAccountView_Previews: PreviewProvider { - static var previews: some View { - AddReaderAPIAccountView(accountType: .freshRSS) - //AddReaderAPIAccountView(accountType: .inoreader) - } -} diff --git a/Multiplatform/Shared/Add/AddFolderModel.swift b/Multiplatform/Shared/Add/AddFolderModel.swift deleted file mode 100644 index 57da82f7e..000000000 --- a/Multiplatform/Shared/Add/AddFolderModel.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// AddFolderModel.swift -// NetNewsWire -// -// Created by Alex Faber on 04/07/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import Account -import RSCore -import SwiftUI - - -class AddFolderModel: ObservableObject { - - @Published var shouldDismiss: Bool = false - @Published var folderName: String = "" - @Published var selectedAccountIndex: Int = 0 - @Published var accounts: [Account] = [] - - @Published var showError: Bool = false - @Published var showProgressIndicator: Bool = false - - init() { - for account in - AccountManager.shared.sortedActiveAccounts{ - accounts.append(account) - } - } - - func addFolder() { - let account = accounts[selectedAccountIndex] - - showProgressIndicator = true - - account.addFolder(folderName){ result in - self.showProgressIndicator = false - - switch result { - case .success(_): - self.shouldDismiss = true - - case .failure(let error): - print("Error") - print(error) - } - - } - } -} diff --git a/Multiplatform/Shared/Add/AddFolderView.swift b/Multiplatform/Shared/Add/AddFolderView.swift deleted file mode 100644 index 1167096c5..000000000 --- a/Multiplatform/Shared/Add/AddFolderView.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// AddFolderView.swift -// NetNewsWire -// -// Created by Alex Faber on 04/07/2020. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore - -struct AddFolderView: View { - - @ObservedObject private var viewModel = AddFolderModel() - @Binding var isPresented: Bool - - var body: some View { - #if os(iOS) - iosForm - .onReceive(viewModel.$shouldDismiss, perform: { - dismiss in - if dismiss == true { - isPresented = false - } - }) - #else - macForm - .onReceive(viewModel.$shouldDismiss, perform: { dismiss in - if dismiss == true { - isPresented = false - } - }) - #endif - } - #if os(iOS) - var iosForm: some View { - NavigationView { - Form { - Section { - TextField("Name", text: $viewModel.folderName) - } - Section { - accountPicker - } - } - .navigationTitle("Add Folder") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems( - leading:Button("Cancel", action: { - isPresented = false - } - ) - .help("Cancel Adding Folder"), - trailing:Button("Add", action: { - viewModel.addFolder() - } - ) - .disabled(viewModel.folderName.isEmpty) - .help("Save Adding Folder") - ) - - } - } - #endif - - #if os(macOS) - var macForm: some View { - Form { - HStack { - Spacer() - Image(rsImage: AppAssets.faviconTemplateImage) - .resizable() - .renderingMode(.template) - .frame(width: 30, height: 30) - Text("Add a Folder") - .font(.title) - Spacer() - } - - LazyVGrid(columns: [GridItem(.fixed(75), spacing: 10, alignment: .trailing),GridItem(.fixed(400), spacing: 0, alignment: .leading) ], alignment: .leading, spacing: 10, pinnedViews: [], content:{ - Text("Name:").bold() - TextField("Name", text: $viewModel.folderName) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .help("The name of the folder you want to create") - Text("Account:").bold() - accountPicker - .help("Pick the account you want to create a folder in.") - }) - buttonStack - } - .frame(maxWidth: 485) - .padding(12) - } - #endif - - var accountPicker: some View { - Picker("Account:", selection: $viewModel.selectedAccountIndex, content: { - ForEach(0.. AccountAndFolderSpecifier? { - if let account = container as? Account { - return AccountAndFolderSpecifier(account: account, folder: nil) - } - if let folder = container as? Folder, let account = folder.account { - return AccountAndFolderSpecifier(account: account, folder: folder) - } - return nil - } - - func addWebFeed() { - if let account = accountAndFolderFromContainer(containers[selectedFolderIndex])?.account { - - showProgressIndicator = true - - let normalizedURLString = providedURL.normalizedURL - - guard !normalizedURLString.isEmpty, let url = URL(string: normalizedURLString) else { - showProgressIndicator = false - return - } - - let container = containers[selectedFolderIndex] - - if account.hasWebFeed(withURL: normalizedURLString) { - addFeedError = .alreadySubscribed - showProgressIndicator = false - return - } - - account.createWebFeed(url: url.absoluteString, name: providedName, container: container, validateFeed: true, completion: { [weak self] result in - self?.showProgressIndicator = false - switch result { - case .success(let feed): - NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed]) - self?.shouldDismiss = true - case .failure(let error): - switch error { - case AccountError.createErrorAlreadySubscribed: - self?.addFeedError = .alreadySubscribed - return - case AccountError.createErrorNotFound: - self?.addFeedError = .noFeeds - return - default: - print("Error") - } - } - }) - } - } - - func smallIconImage(for container: Container) -> RSImage? { - if let smallIconProvider = container as? SmallIconProvider { - return smallIconProvider.smallIcon?.image - } - return nil - } - -} diff --git a/Multiplatform/Shared/Add/AddWebFeedView.swift b/Multiplatform/Shared/Add/AddWebFeedView.swift deleted file mode 100644 index e898312e0..000000000 --- a/Multiplatform/Shared/Add/AddWebFeedView.swift +++ /dev/null @@ -1,209 +0,0 @@ -// -// AddWebFeedView.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 3/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore - - -struct AddWebFeedView: View { - - @StateObject private var viewModel = AddWebFeedModel() - @Binding var isPresented: Bool - - var body: some View { - #if os(iOS) - iosForm - .onAppear { - viewModel.pasteUrlFromPasteboard() - } - .onReceive(viewModel.$shouldDismiss, perform: { dismiss in - if dismiss == true { - isPresented = false - } - }) - #else - macForm - .onAppear { - viewModel.pasteUrlFromPasteboard() - }.alert(isPresented: $viewModel.showError) { - Alert(title: Text("Oops"), - message: Text(viewModel.addFeedError!.localizedDescription), - dismissButton: Alert.Button.cancel({ - viewModel.addFeedError = AddWebFeedError.none - })) - } - .onChange(of: viewModel.shouldDismiss, perform: { dismiss in - if dismiss == true { - isPresented = false - } - }) - #endif - } - - #if os(macOS) - var macForm: some View { - Form { - HStack { - Spacer() - Image(rsImage: AppAssets.faviconTemplateImage) - .resizable() - .renderingMode(.template) - .frame(width: 30, height: 30) - Text("Add a Web Feed") - .font(.title) - Spacer() - }.padding() - - LazyVGrid(columns: [GridItem(.fixed(75), spacing: 10, alignment: .trailing),GridItem(.fixed(400), spacing: 0, alignment: .leading) ], alignment: .leading, spacing: 10, pinnedViews: [], content:{ - Text("URL:").bold() - urlTextField - .textFieldStyle(RoundedBorderTextFieldStyle()) - .help("The URL of the feed you want to add.") - Text("Name:").bold() - providedNameTextField - .textFieldStyle(RoundedBorderTextFieldStyle()) - .help("The name of the feed. (Optional.)") - Text("Folder:").bold() - folderPicker - .help("Pick the folder you want to add the feed to.") - }) - buttonStack - } - .frame(maxWidth: 485) - .padding(12) - } - #endif - - #if os(iOS) - var iosForm: some View { - NavigationView { - List { - urlTextField - providedNameTextField - folderPicker - } - .listStyle(InsetGroupedListStyle()) - .navigationBarTitle("Add Web Feed") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(leading: - Button("Cancel", action: { - isPresented = false - }) - .help("Cancel Add Feed") - , trailing: - HStack(spacing: 12) { - if viewModel.showProgressIndicator == true { - ProgressView() - } - Button("Add", action: { - viewModel.addWebFeed() - }) - .disabled(!viewModel.providedURL.mayBeURL) - .help("Add Feed") - } - ) - } - } - #endif - - @ViewBuilder var urlTextField: some View { - #if os(iOS) - TextField("URL", text: $viewModel.providedURL) - .disableAutocorrection(true) - .autocapitalization(UITextAutocapitalizationType.none) - #else - TextField("URL", text: $viewModel.providedURL) - .disableAutocorrection(true) - #endif - } - - var providedNameTextField: some View { - TextField("Title (Optional)", text: $viewModel.providedName) - } - - @ViewBuilder var folderPicker: some View { - #if os(iOS) - Picker("Folder", selection: $viewModel.selectedFolderIndex, content: { - ForEach(0.. RSImage? { - switch accountType { - case .onMyMac: - #if os(macOS) - return AppAssets.accountLocalMacImage - #endif - #if os(iOS) - if UIDevice.current.userInterfaceIdiom == .pad { - return AppAssets.accountLocalPadImage - } else { - return AppAssets.accountLocalPhoneImage - } - #endif - case .bazQux: - return AppAssets.accountBazQux - case .cloudKit: - return AppAssets.accountCloudKitImage - case .feedbin: - return AppAssets.accountFeedbinImage - case .feedly: - return AppAssets.accountFeedlyImage - case .feedWrangler: - return AppAssets.accountFeedWranglerImage - case .freshRSS: - return AppAssets.accountFreshRSSImage - case .newsBlur: - return AppAssets.accountNewsBlurImage - case .inoreader: - return AppAssets.accountInoreader - case .theOldReader: - return AppAssets.accountTheOldReader - - } - } - -} diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift deleted file mode 100644 index 51261efaa..000000000 --- a/Multiplatform/Shared/AppDefaults.swift +++ /dev/null @@ -1,359 +0,0 @@ -// -// AppDefaults.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 1/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import SwiftUI - -enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { - case automatic = 0 - case light = 1 - case dark = 2 - - var description: String { - switch self { - case .automatic: - return NSLocalizedString("Automatic", comment: "Automatic") - case .light: - return NSLocalizedString("Light", comment: "Light") - case .dark: - return NSLocalizedString("Dark", comment: "Dark") - } - } -} - -final class AppDefaults: ObservableObject { - - static let defaultThemeName = "Default" - - #if os(macOS) - static let store: UserDefaults = UserDefaults.standard - #endif - - #if os(iOS) - static let store: UserDefaults = { - let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String - let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" - return UserDefaults.init(suiteName: suiteName)! - }() - #endif - - public static let shared = AppDefaults() - private init() {} - - struct Key { - - // Shared Defaults - static let refreshInterval = "refreshInterval" - static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" - static let activeExtensionPointIDs = "activeExtensionPointIDs" - static let lastImageCacheFlushDate = "lastImageCacheFlushDate" - static let firstRunDate = "firstRunDate" - static let lastRefresh = "lastRefresh" - static let addWebFeedAccountID = "addWebFeedAccountID" - static let addWebFeedFolderName = "addWebFeedFolderName" - static let addFolderAccountID = "addFolderAccountID" - - static let userInterfaceColorPalette = "userInterfaceColorPalette" - static let timelineSortDirection = "timelineSortDirection" - static let timelineGroupByFeed = "timelineGroupByFeed" - static let timelineIconDimensions = "timelineIconDimensions" - static let timelineNumberOfLines = "timelineNumberOfLines" - static let currentThemeName = "currentThemeName" - - // Sidebar Defaults - static let sidebarConfirmDelete = "sidebarConfirmDelete" - - // iOS Defaults - static let refreshClearsReadArticles = "refreshClearsReadArticles" - static let articleFullscreenAvailable = "articleFullscreenAvailable" - static let articleFullscreenEnabled = "articleFullscreenEnabled" - static let confirmMarkAllAsRead = "confirmMarkAllAsRead" - - // macOS Defaults - static let articleTextSize = "articleTextSize" - static let openInBrowserInBackground = "openInBrowserInBackground" - static let defaultBrowserID = "defaultBrowserID" - static let subscribeToFeedsInDefaultBrowser = "subscribeToFeedsInDefaultBrowser" - static let checkForUpdatesAutomatically = "checkForUpdatesAutomatically" - static let downloadTestBuilds = "downloadTestBuild" - static let sendCrashLogs = "sendCrashLogs" - - // Hidden macOS Defaults - static let showDebugMenu = "ShowDebugMenu" - static let timelineShowsSeparators = "CorreiaSeparators" - static let showTitleOnMainWindow = "KafasisTitleMode" - - #if !MAC_APP_STORE - static let webInspectorEnabled = "WebInspectorEnabled" - static let webInspectorStartsAttached = "__WebInspectorPageGroupLevel1__.WebKit2InspectorStartsAttached" - #endif - - } - - // MARK: Development Builds - let isDeveloperBuild: Bool = { - if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { - return true - } - return false - }() - - // MARK: First Run Details - var firstRunDate: Date? { - set { - AppDefaults.store.setValue(newValue, forKey: Key.firstRunDate) - objectWillChange.send() - } - get { - AppDefaults.store.object(forKey: Key.firstRunDate) as? Date - } - } - - // MARK: Refresh Interval - @AppStorage(wrappedValue: 4, Key.refreshInterval, store: store) var interval: Int { - didSet { - objectWillChange.send() - } - } - - var refreshInterval: RefreshInterval { - RefreshInterval(rawValue: interval) ?? RefreshInterval.everyHour - } - - // MARK: Dock Badge - @AppStorage(wrappedValue: false, Key.hideDockUnreadCount, store: store) var hideDockUnreadCount { - didSet { - objectWillChange.send() - } - } - - // MARK: Color Palette - var userInterfaceColorPalette: UserInterfaceColorPalette { - get { - if let palette = UserInterfaceColorPalette(rawValue: AppDefaults.store.integer(forKey: Key.userInterfaceColorPalette)) { - return palette - } - return .automatic - } - set { - AppDefaults.store.set(newValue.rawValue, forKey: Key.userInterfaceColorPalette) - #if os(macOS) - self.objectWillChange.send() - #else - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { - self.objectWillChange.send() - }) - #endif - } - } - - static var userInterfaceColorScheme: ColorScheme? { - switch AppDefaults.shared.userInterfaceColorPalette { - case .light: - return ColorScheme.light - case .dark: - return ColorScheme.dark - default: - return nil - } - } - - // MARK: Feeds & Folders - @AppStorage(Key.addWebFeedAccountID, store: store) var addWebFeedAccountID: String? - - @AppStorage(Key.addWebFeedFolderName, store: store) var addWebFeedFolderName: String? - - @AppStorage(Key.addFolderAccountID, store: store) var addFolderAccountID: String? - - @AppStorage(wrappedValue: false, Key.confirmMarkAllAsRead, store: store) var confirmMarkAllAsRead: Bool - - // MARK: Extension Points - var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { - get { - return AppDefaults.store.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] - } - set { - UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) - objectWillChange.send() - } - } - - // MARK: Image Cache - var lastImageCacheFlushDate: Date? { - set { - AppDefaults.store.setValue(newValue, forKey: Key.lastImageCacheFlushDate) - objectWillChange.send() - } - get { - AppDefaults.store.object(forKey: Key.lastImageCacheFlushDate) as? Date - } - } - - // MARK: Timeline - @AppStorage(wrappedValue: false, Key.timelineGroupByFeed, store: store) var timelineGroupByFeed: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: 2.0, Key.timelineNumberOfLines, store: store) var timelineNumberOfLines: Double { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: 40.0, Key.timelineIconDimensions, store: store) var timelineIconDimensions: Double { - didSet { - objectWillChange.send() - } - } - - /// Set to `true` to sort oldest to newest, `false` for newest to oldest. Default is `false`. - @AppStorage(wrappedValue: false, Key.timelineSortDirection, store: store) var timelineSortDirection: Bool { - didSet { - objectWillChange.send() - } - } - - // MARK: Sidebar - @AppStorage(wrappedValue: true, Key.sidebarConfirmDelete, store: store) var sidebarConfirmDelete: Bool { - didSet { - objectWillChange.send() - } - } - - - // MARK: Refresh - @AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool - - // MARK: Articles - @AppStorage(wrappedValue: false, Key.articleFullscreenAvailable, store: store) var articleFullscreenAvailable: Bool - - @AppStorage(wrappedValue: false, Key.articleFullscreenEnabled, store: store) var articleFullscreenEnabled: Bool - - @AppStorage(wrappedValue: 3, Key.articleTextSize, store: store) var articleTextSizeTag: Int { - didSet { - objectWillChange.send() - } - } - - var articleTextSize: ArticleTextSize { - ArticleTextSize(rawValue: articleTextSizeTag) ?? ArticleTextSize.large - } - - @AppStorage(Key.currentThemeName, store: store) var currentThemeName: String? - - // MARK: Refresh - var lastRefresh: Date? { - set { - AppDefaults.store.setValue(newValue, forKey: Key.lastRefresh) - objectWillChange.send() - } - get { - AppDefaults.store.object(forKey: Key.lastRefresh) as? Date - } - } - - // MARK: Window State - @AppStorage(wrappedValue: false, Key.openInBrowserInBackground, store: store) var openInBrowserInBackground: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(Key.defaultBrowserID, store: store) var defaultBrowserID: String? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.subscribeToFeedsInDefaultBrowser, store: store) var subscribeToFeedsInDefaultBrowser: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(Key.showTitleOnMainWindow, store: store) var showTitleOnMainWindow: Bool? { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.showDebugMenu, store: store) var showDebugMenu: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.timelineShowsSeparators, store: store) var timelineShowsSeparators: Bool { - didSet { - objectWillChange.send() - } - } - - #if !MAC_APP_STORE - @AppStorage(wrappedValue: false, Key.webInspectorEnabled, store: store) var webInspectorEnabled: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.webInspectorStartsAttached, store: store) var webInspectorStartsAttached: Bool { - didSet { - objectWillChange.send() - } - } - #endif - - @AppStorage(wrappedValue: true, Key.checkForUpdatesAutomatically, store: store) var checkForUpdatesAutomatically: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: false, Key.downloadTestBuilds, store: store) var downloadTestBuilds: Bool { - didSet { - objectWillChange.send() - } - } - - @AppStorage(wrappedValue: true, Key.sendCrashLogs, store: store) var sendCrashLogs: Bool { - didSet { - objectWillChange.send() - } - } - - static func registerDefaults() { - let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue, - Key.timelineGroupByFeed: false, - Key.refreshClearsReadArticles: false, - Key.timelineNumberOfLines: 2, - Key.timelineIconDimensions: 40, - Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, - Key.articleFullscreenAvailable: false, - Key.articleFullscreenEnabled: false, - Key.confirmMarkAllAsRead: true, - "NSScrollViewShouldScrollUnderTitlebar": false, - Key.refreshInterval: RefreshInterval.everyHour.rawValue, - Key.currentThemeName: Self.defaultThemeName] - AppDefaults.store.register(defaults: defaults) - } - -} - -extension AppDefaults { - - func isFirstRun() -> Bool { - if let _ = AppDefaults.store.object(forKey: Key.firstRunDate) as? Date { - return false - } - firstRunDate = Date() - return true - } - -} diff --git a/Multiplatform/Shared/Article/ArticleContainerView.swift b/Multiplatform/Shared/Article/ArticleContainerView.swift deleted file mode 100644 index 07fa53b72..000000000 --- a/Multiplatform/Shared/Article/ArticleContainerView.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ArticleContainerView.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/2/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Articles - -struct ArticleContainerView: View { - - var body: some View { - ArticleView() - .modifier(ArticleToolbarModifier()) - } - -} diff --git a/Multiplatform/Shared/Article/ArticleExtractorButtonState.swift b/Multiplatform/Shared/Article/ArticleExtractorButtonState.swift deleted file mode 100644 index 6c9f6d04a..000000000 --- a/Multiplatform/Shared/Article/ArticleExtractorButtonState.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// ArticleExtractorButtonState.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation - -enum ArticleExtractorButtonState { - case error - case animated - case on - case off -} diff --git a/Multiplatform/Shared/Article/ArticleIconSchemeHandler.swift b/Multiplatform/Shared/Article/ArticleIconSchemeHandler.swift deleted file mode 100644 index 4e18cccb7..000000000 --- a/Multiplatform/Shared/Article/ArticleIconSchemeHandler.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// ArticleIconSchemeHandler.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import WebKit -import Articles - -class ArticleIconSchemeHandler: NSObject, WKURLSchemeHandler { - - weak var sceneModel: SceneModel? - - init(sceneModel: SceneModel) { - self.sceneModel = sceneModel - } - - func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { - - guard let url = urlSchemeTask.request.url, let sceneModel = sceneModel else { - urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist)) - return - } - - guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - return - } - let articleID = components.path - guard let iconImage = sceneModel.articleFor(articleID)?.iconImage() else { - urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist)) - return - } - - let iconView = IconView(frame: CGRect(x: 0, y: 0, width: 48, height: 48)) - iconView.iconImage = iconImage - let renderedImage = iconView.asImage() - - guard let data = renderedImage.dataRepresentation() else { - urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist)) - return - } - - let headerFields = ["Cache-Control": "no-cache"] - if let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headerFields) { - urlSchemeTask.didReceive(response) - urlSchemeTask.didReceive(data) - urlSchemeTask.didFinish() - } - - } - - func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { - urlSchemeTask.didFailWithError(URLError(.unknown)) - } - -} - diff --git a/Multiplatform/Shared/Article/ArticleToolbarModifier.swift b/Multiplatform/Shared/Article/ArticleToolbarModifier.swift deleted file mode 100644 index 380cba519..000000000 --- a/Multiplatform/Shared/Article/ArticleToolbarModifier.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// ArticleToolbarModifier.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/5/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct ArticleToolbarModifier: ViewModifier { - - @EnvironmentObject private var sceneModel: SceneModel - @State private var showActivityView = false - - func body(content: Content) -> some View { - content - .toolbar { - #if os(iOS) - - ToolbarItem(placement: .primaryAction) { - HStack(spacing: 20) { - Button { - } label: { - AppAssets.prevArticleImage - .font(.title3) - } - .help("Previouse Unread") - Button { - } label: { - AppAssets.nextArticleImage.font(.title3) - } - .help("Next Unread") - } - } - - ToolbarItem(placement: .bottomBar) { - Button { - sceneModel.toggleReadStatusForSelectedArticles() - } label: { - if sceneModel.readButtonState == true { - AppAssets.readClosedImage - } else { - AppAssets.readOpenImage - } - } - .disabled(sceneModel.readButtonState == nil) - .help(sceneModel.readButtonState ?? false ? "Mark as Unread" : "Mark as Read") - } - - ToolbarItem(placement: .bottomBar) { - Spacer() - } - - ToolbarItem(placement: .bottomBar) { - Button { - sceneModel.toggleStarredStatusForSelectedArticles() - } label: { - if sceneModel.starButtonState ?? false { - AppAssets.starClosedImage - } else { - AppAssets.starOpenImage - } - } - .disabled(sceneModel.starButtonState == nil) - .help(sceneModel.starButtonState ?? false ? "Mark as Unstarred" : "Mark as Starred") - } - - ToolbarItem(placement: .bottomBar) { - Spacer() - } - - ToolbarItem(placement: .bottomBar) { - Button { - sceneModel.goToNextUnread() - } label: { - AppAssets.nextUnreadArticleImage.font(.title3) - } - .disabled(sceneModel.nextUnreadButtonState == nil) - .help("Next Unread") - } - - ToolbarItem(placement: .bottomBar) { - Spacer() - } - - ToolbarItem(placement: .bottomBar) { - Button { - } label: { - AppAssets.articleExtractorOff - .font(.title3) - } - .disabled(sceneModel.extractorButtonState == nil) - .help("Reader View") - } - - ToolbarItem(placement: .bottomBar) { - Spacer() - } - - ToolbarItem(placement: .bottomBar) { - Button { - showActivityView.toggle() - } label: { - AppAssets.shareImage.font(.title3) - } - .disabled(sceneModel.shareButtonState == nil) - .help("Share") - .sheet(isPresented: $showActivityView) { - if let article = sceneModel.selectedArticles.first, let url = article.preferredURL { - ActivityViewController(title: article.title, url: url) - } - } - } - - #endif - } - } - -} diff --git a/Multiplatform/Shared/Article/PreloadedWebView.swift b/Multiplatform/Shared/Article/PreloadedWebView.swift deleted file mode 100644 index 2baca9e73..000000000 --- a/Multiplatform/Shared/Article/PreloadedWebView.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// PreloadedWebView.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import WebKit -import RSWeb - -class PreloadedWebView: WKWebView { - - private var isReady: Bool = false - private var readyCompletion: (() -> Void)? - - init(articleIconSchemeHandler: ArticleIconSchemeHandler) { - let preferences = WKPreferences() - preferences.javaScriptCanOpenWindowsAutomatically = false - - let configuration = WKWebViewConfiguration() - configuration.preferences = preferences - configuration.setValue(true, forKey: "allowUniversalAccessFromFileURLs") - #if os(iOS) - configuration.allowsInlineMediaPlayback = true - #endif - configuration.mediaTypesRequiringUserActionForPlayback = .audio - configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme) - - super.init(frame: .zero, configuration: configuration) - - if let userAgent = UserAgent.fromInfoPlist() { - customUserAgent = userAgent - } - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - } - - func preload() { - navigationDelegate = self - loadFileURL(ArticleRenderer.blank.url, allowingReadAccessTo: ArticleRenderer.blank.baseURL) - } - - func ready(completion: @escaping () -> Void) { - if isReady { - completeRequest(completion: completion) - } else { - readyCompletion = completion - } - } - - #if os(macOS) - override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) { - // There’s no API for affecting a WKWebView’s contextual menu. - // (WebView had API for this.) - // - // This a minor hack. It hides unwanted menu items. - // The menu item identifiers are not documented anywhere; - // they could change, and this code would need updating. - for menuItem in menu.items { - if shouldHideMenuItem(menuItem) { - menuItem.isHidden = true - } - } - - super.willOpenMenu(menu, with: event) - } - #endif -} - -// MARK: WKScriptMessageHandler - -extension PreloadedWebView: WKNavigationDelegate { - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - isReady = true - if let completion = readyCompletion { - completeRequest(completion: completion) - readyCompletion = nil - } - } - -} - -// MARK: Private - -private extension PreloadedWebView { - - func completeRequest(completion: @escaping () -> Void) { - isReady = false - navigationDelegate = nil - completion() - } - -} - -#if os(macOS) -private extension NSUserInterfaceItemIdentifier { - - static let DetailMenuItemIdentifierReload = NSUserInterfaceItemIdentifier(rawValue: "WKMenuItemIdentifierReload") - static let DetailMenuItemIdentifierOpenLink = NSUserInterfaceItemIdentifier(rawValue: "WKMenuItemIdentifierOpenLink") -} - -private extension PreloadedWebView { - - static let menuItemIdentifiersToHide: [NSUserInterfaceItemIdentifier] = [.DetailMenuItemIdentifierReload, .DetailMenuItemIdentifierOpenLink] - static let menuItemIdentifierMatchStrings = ["newwindow", "download"] - - func shouldHideMenuItem(_ menuItem: NSMenuItem) -> Bool { - - guard let identifier = menuItem.identifier else { - return false - } - - if PreloadedWebView.menuItemIdentifiersToHide.contains(identifier) { - return true - } - - let lowerIdentifier = identifier.rawValue.lowercased() - for matchString in PreloadedWebView.menuItemIdentifierMatchStrings { - if lowerIdentifier.contains(matchString) { - return true - } - } - - return false - } -} -#endif diff --git a/Multiplatform/Shared/Article/WebViewProvider.swift b/Multiplatform/Shared/Article/WebViewProvider.swift deleted file mode 100644 index 64d834c0c..000000000 --- a/Multiplatform/Shared/Article/WebViewProvider.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// WebViewProvider.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import RSCore -import WebKit - -/// WKWebView has an awful behavior of a flash to white on first load when in dark mode. -/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle. -class WebViewProvider: NSObject { - - private let articleIconSchemeHandler: ArticleIconSchemeHandler - private let operationQueue = MainThreadOperationQueue() - private var queue = NSMutableArray() - - init(articleIconSchemeHandler: ArticleIconSchemeHandler) { - self.articleIconSchemeHandler = articleIconSchemeHandler - super.init() - replenishQueueIfNeeded() - } - - func replenishQueueIfNeeded() { - operationQueue.add(WebViewProviderReplenishQueueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler)) - } - - func dequeueWebView(completion: @escaping (PreloadedWebView) -> ()) { - operationQueue.add(WebViewProviderDequeueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler, completion: completion)) - operationQueue.add(WebViewProviderReplenishQueueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler)) - } - -} - -class WebViewProviderReplenishQueueOperation: MainThreadOperation { - - // MainThreadOperation - public var isCanceled = false - public var id: Int? - public weak var operationDelegate: MainThreadOperationDelegate? - public var name: String? = "WebViewProviderReplenishQueueOperation" - public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? - - private let minimumQueueDepth = 3 - - private var queue: NSMutableArray - private var articleIconSchemeHandler: ArticleIconSchemeHandler - - init(queue: NSMutableArray, articleIconSchemeHandler: ArticleIconSchemeHandler) { - self.queue = queue - self.articleIconSchemeHandler = articleIconSchemeHandler - } - - func run() { - while queue.count < minimumQueueDepth { - let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler) - webView.preload() - queue.insert(webView, at: 0) - } - self.operationDelegate?.operationDidComplete(self) - } - -} - -class WebViewProviderDequeueOperation: MainThreadOperation { - - // MainThreadOperation - public var isCanceled = false - public var id: Int? - public weak var operationDelegate: MainThreadOperationDelegate? - public var name: String? = "WebViewProviderFlushQueueOperation" - public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? - - private var queue: NSMutableArray - private var articleIconSchemeHandler: ArticleIconSchemeHandler - private var completion: (PreloadedWebView) -> () - - init(queue: NSMutableArray, articleIconSchemeHandler: ArticleIconSchemeHandler, completion: @escaping (PreloadedWebView) -> ()) { - self.queue = queue - self.articleIconSchemeHandler = articleIconSchemeHandler - self.completion = completion - } - - func run() { - if let webView = queue.lastObject as? PreloadedWebView { - self.completion(webView) - self.queue.remove(webView) - self.operationDelegate?.operationDidComplete(self) - return - } - - assertionFailure("Creating PreloadedWebView in \(#function); queue has run dry.") - - let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler) - webView.preload() - self.completion(webView) - self.operationDelegate?.operationDidComplete(self) - } - -} diff --git a/Multiplatform/Shared/Article/WrapperScriptMessageHandler.swift b/Multiplatform/Shared/Article/WrapperScriptMessageHandler.swift deleted file mode 100644 index e3a58e19d..000000000 --- a/Multiplatform/Shared/Article/WrapperScriptMessageHandler.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// WrapperScriptMessageHandler.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import WebKit - -class WrapperScriptMessageHandler: NSObject, WKScriptMessageHandler { - - // We need to wrap a message handler to prevent a circlular reference - private weak var handler: WKScriptMessageHandler? - - init(_ handler: WKScriptMessageHandler) { - self.handler = handler - } - - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - handler?.userContentController(userContentController, didReceive: message) - } - -} diff --git a/Multiplatform/Shared/Article/blank.html b/Multiplatform/Shared/Article/blank.html deleted file mode 100644 index 6e02cf3a6..000000000 --- a/Multiplatform/Shared/Article/blank.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/Multiplatform/Shared/Article/main_multiplatform.js b/Multiplatform/Shared/Article/main_multiplatform.js deleted file mode 100644 index e1f0f2d0a..000000000 --- a/Multiplatform/Shared/Article/main_multiplatform.js +++ /dev/null @@ -1,498 +0,0 @@ -var activeImageViewer = null; - -class ImageViewer { - constructor(img) { - this.img = img; - this.loadingInterval = null; - this.activityIndicator = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZpZXdCb3g9IjAgMCAxMjggMTI4IiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMwMDAwMDAiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDMwIDY0IDY0KSIvPjxwYXRoIGQ9Ik01OS42IDBoOHY0MGgtOFYweiIgZmlsbD0iI2NjY2NjYyIgdHJhbnNmb3JtPSJyb3RhdGUoNjAgNjQgNjQpIi8+PHBhdGggZD0iTTU5LjYgMGg4djQwaC04VjB6IiBmaWxsPSIjY2NjY2NjIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNjY2NjY2MiIHRyYW5zZm9ybT0icm90YXRlKDEyMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiNiMmIyYjIiIHRyYW5zZm9ybT0icm90YXRlKDE1MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM5OTk5OTkiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM3ZjdmN2YiIHRyYW5zZm9ybT0icm90YXRlKDIxMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM2NjY2NjYiIHRyYW5zZm9ybT0icm90YXRlKDI0MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiM0YzRjNGMiIHRyYW5zZm9ybT0icm90YXRlKDI3MCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMzMzMzMzMiIHRyYW5zZm9ybT0icm90YXRlKDMwMCA2NCA2NCkiLz48cGF0aCBkPSJNNTkuNiAwaDh2NDBoLThWMHoiIGZpbGw9IiMxOTE5MTkiIHRyYW5zZm9ybT0icm90YXRlKDMzMCA2NCA2NCkiLz48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgdmFsdWVzPSIwIDY0IDY0OzMwIDY0IDY0OzYwIDY0IDY0OzkwIDY0IDY0OzEyMCA2NCA2NDsxNTAgNjQgNjQ7MTgwIDY0IDY0OzIxMCA2NCA2NDsyNDAgNjQgNjQ7MjcwIDY0IDY0OzMwMCA2NCA2NDszMzAgNjQgNjQiIGNhbGNNb2RlPSJkaXNjcmV0ZSIgZHVyPSIxMDgwbXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIj48L2FuaW1hdGVUcmFuc2Zvcm0+PC9nPjwvc3ZnPg=="; - } - - isLoaded() { - return this.img.classList.contains("nnwLoaded"); - } - - clicked() { - this.showLoadingIndicator(); - if (this.isLoaded()) { - this.showViewer(); - } else { - var callback = () => { - if (this.isLoaded()) { - clearInterval(this.loadingInterval); - this.showViewer(); - } - } - this.loadingInterval = setInterval(callback, 100); - } - } - cancel() { - clearInterval(this.loadingInterval); - this.hideLoadingIndicator(); - } - - showViewer() { - this.hideLoadingIndicator(); - - var canvas = document.createElement("canvas"); - var pixelRatio = window.devicePixelRatio; - do { - canvas.width = this.img.naturalWidth * pixelRatio; - canvas.height = this.img.naturalHeight * pixelRatio; - pixelRatio--; - } while (pixelRatio > 0 && canvas.width * canvas.height > 16777216) - canvas.getContext("2d").drawImage(this.img, 0, 0, canvas.width, canvas.height); - - const rect = this.img.getBoundingClientRect(); - const message = { - x: rect.x, - y: rect.y, - width: rect.width, - height: rect.height, - imageTitle: this.img.title, - imageURL: canvas.toDataURL(), - }; - - var jsonMessage = JSON.stringify(message); - window.webkit.messageHandlers.imageWasClicked.postMessage(jsonMessage); - } - - hideImage() { - this.img.style.opacity = 0; - } - - showImage() { - this.img.style.opacity = 1 - } - - showLoadingIndicator() { - var wrapper = document.createElement("div"); - wrapper.classList.add("activityIndicatorWrap"); - this.img.parentNode.insertBefore(wrapper, this.img); - wrapper.appendChild(this.img); - - var activityIndicatorImg = document.createElement("img"); - activityIndicatorImg.classList.add("activityIndicator"); - activityIndicatorImg.style.opacity = 0; - activityIndicatorImg.src = this.activityIndicator; - wrapper.appendChild(activityIndicatorImg); - - activityIndicatorImg.style.opacity = 1; - } - - hideLoadingIndicator() { - var wrapper = this.img.parentNode; - if (wrapper.classList.contains("activityIndicatorWrap")) { - var wrapperParent = wrapper.parentNode; - wrapperParent.insertBefore(this.img, wrapper); - wrapperParent.removeChild(wrapper); - } - } - - static init() { - cancelImageLoad(); - - // keep track of when an image has finished downloading for ImageViewer - document.querySelectorAll("img").forEach(element => { - element.onload = function() { - this.classList.add("nnwLoaded"); - } - }); - - // Add the click listener for images - window.onclick = function(event) { - if (event.target.matches("img") && !event.target.classList.contains("nnw-nozoom")) { - if (activeImageViewer && activeImageViewer.img === event.target) { - cancelImageLoad(); - } else { - cancelImageLoad(); - activeImageViewer = new ImageViewer(event.target); - activeImageViewer.clicked(); - } - } - } - } -} - -function cancelImageLoad() { - if (activeImageViewer) { - activeImageViewer.cancel(); - activeImageViewer = null; - } -} - -function hideClickedImage() { - if (activeImageViewer) { - activeImageViewer.hideImage(); - } -} - -// Used to animate the transition from a fullscreen image -function showClickedImage() { - if (activeImageViewer) { - activeImageViewer.showImage(); - } - window.webkit.messageHandlers.imageWasShown.postMessage(""); -} - -function showFeedInspectorSetup() { - document.getElementById("nnwImageIcon").onclick = function(event) { - window.webkit.messageHandlers.showFeedInspector.postMessage(""); - } -} - -function linkHover() { - window.onmouseover = function(event) { - var closestAnchor = event.target.closest('a') - if (closestAnchor) { - window.webkit.messageHandlers.mouseDidEnter.postMessage(closestAnchor.href); - } - } - window.onmouseout = function(event) { - var closestAnchor = event.target.closest('a') - if (closestAnchor) { - window.webkit.messageHandlers.mouseDidExit.postMessage(closestAnchor.href); - } - } -} - - -function postRenderProcessing() { - ImageViewer.init(); - showFeedInspectorSetup(); - linkHover(); -} - - -function makeHighlightRect({left, top, width, height}, offsetTop=0, offsetLeft=0) { - const overlay = document.createElement('a'); - - Object.assign(overlay.style, { - position: 'absolute', - left: `${Math.floor(left + offsetLeft)}px`, - top: `${Math.floor(top + offsetTop)}px`, - width: `${Math.ceil(width)}px`, - height: `${Math.ceil(height)}px`, - backgroundColor: 'rgba(200, 220, 10, 0.4)', - pointerEvents: 'none' - }); - - return overlay; -} - -function clearHighlightRects() { - let container = document.getElementById('nnw:highlightContainer') - if (container) container.remove(); -} - -function highlightRects(rects, clearOldRects=true, makeHighlightRect=makeHighlightRect) { - const article = document.querySelector('article'); - let container = document.getElementById('nnw:highlightContainer'); - - article.style.position = 'relative'; - - if (container && clearOldRects) - container.remove(); - - container = document.createElement('div'); - container.id = 'nnw:highlightContainer'; - article.appendChild(container); - - const {top, left} = article.getBoundingClientRect(); - return Array.from(rects, rect => - container.appendChild(makeHighlightRect(rect, -top, -left)) - ); -} - -FinderResult = class { - constructor(result) { - Object.assign(this, result); - } - - range() { - const range = document.createRange(); - range.setStart(this.node, this.offset); - range.setEnd(this.node, this.offsetEnd); - return range; - } - - bounds() { - return this.range().getBoundingClientRect(); - } - - rects() { - return this.range().getClientRects(); - } - - highlight({clearOldRects=true, fn=makeHighlightRect} = {}) { - highlightRects(this.rects(), clearOldRects, fn); - } - - scrollTo() { - scrollToRect(this.bounds(), this.node); - } - - toJSON() { - return { - rects: Array.from(this.rects()), - bounds: this.bounds(), - index: this.index, - matchGroups: this.match - }; - } - - toJSONString() { - return JSON.stringify(this.toJSON()); - } -} - -Finder = class { - constructor(pattern, options) { - if (!pattern.global) { - pattern = new RegExp(pattern, 'g'); - } - - this.pattern = pattern; - this.lastResult = null; - this._nodeMatches = []; - this.options = { - rootSelector: '.articleBody', - startNode: null, - startOffset: null, - } - - this.resultIndex = -1 - - Object.assign(this.options, options); - - this.walker = document.createTreeWalker(this.root, NodeFilter.SHOW_TEXT); - } - - get root() { - return document.querySelector(this.options.rootSelector) - } - - get count() { - const node = this.walker.currentNode; - const index = this.resultIndex; - this.reset(); - - let result, count = 0; - while ((result = this.next())) ++count; - - this.resultIndex = index; - this.walker.currentNode = node; - - return count; - } - - reset() { - this.walker.currentNode = this.options.startNode || this.root; - this.resultIndex = -1; - } - - [Symbol.iterator]() { - return this; - } - - next({wrap = false} = {}) { - const { startNode } = this.options; - const { pattern, walker } = this; - - let { node, matchIndex = -1 } = this.lastResult || { node: startNode }; - - while (true) { - if (!node) - node = walker.nextNode(); - - if (!node) { - if (!wrap || this.resultIndex < 0) break; - - this.reset(); - - continue; - } - - let nextIndex = matchIndex + 1; - let matches = this._nodeMatches; - - if (!matches.length) { - matches = Array.from(node.textContent.matchAll(pattern)); - nextIndex = 0; - } - - if (matches[nextIndex]) { - this._nodeMatches = matches; - const m = matches[nextIndex]; - - this.lastResult = new FinderResult({ - node, - offset: m.index, - offsetEnd: m.index + m[0].length, - text: m[0], - match: m, - matchIndex: nextIndex, - index: ++this.resultIndex, - }); - - return { value: this.lastResult, done: false }; - } - - this._nodeMatches = []; - node = null; - } - - return { value: undefined, done: true }; - } - - /// TODO Call when the search text changes - retry() { - if (this.lastResult) { - this.lastResult.offsetEnd = this.lastResult.offset; - } - - } - - toJSON() { - const results = Array.from(this); - } -} - -function scrollParent(node) { - let elt = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement; - - while (elt) { - if (elt.scrollHeight > elt.clientHeight) - return elt; - elt = elt.parentElement; - } -} - -function scrollToRect({top, height}, node, pad=20, padBottom=60) { - const scrollToTop = top - pad; - - let scrollBy = scrollToTop; - - if (scrollToTop >= 0) { - const visible = window.visualViewport; - const scrollToBottom = top + height + padBottom - visible.height; - // The top of the rect is already in the viewport - if (scrollToBottom <= 0 || scrollToTop === 0) - // Don't need to scroll up--or can't - return; - - scrollBy = Math.min(scrollToBottom, scrollBy); - } - - scrollParent(node).scrollBy({ top: scrollBy }); -} - -function withEncodedArg(fn) { - return function(encodedData, ...rest) { - const data = encodedData && JSON.parse(atob(encodedData)); - return fn(data, ...rest); - } -} - -function escapeRegex(s) { - return s.replace(/[.?*+^$\\()[\]{}]/g, '\\$&'); -} - -class FindState { - constructor(options) { - let { text, caseSensitive, regex } = options; - - if (!regex) - text = escapeRegex(text); - - const finder = new Finder(new RegExp(text, caseSensitive ? 'g' : 'ig')); - this.results = Array.from(finder); - this.index = -1; - this.options = options; - } - - get selected() { - return this.index > -1 ? this.results[this.index] : null; - } - - toJSON() { - return { - index: this.index > -1 ? this.index : null, - results: this.results, - count: this.results.length - }; - } - - selectNext(step=1) { - const index = this.index + step; - const result = this.results[index]; - if (result) { - this.index = index; - result.highlight(); - result.scrollTo(); - } - return result; - } - - selectPrevious() { - return this.selectNext(-1); - } -} - -CurrentFindState = null; - -const ExcludeKeys = new Set(['top', 'right', 'bottom', 'left']); -updateFind = withEncodedArg(options => { - // TODO Start at the current result position - // TODO Introduce slight delay, cap the number of results, and report results asynchronously - - let newFindState; - if (!options || !options.text) { - clearHighlightRects(); - return - } - - try { - newFindState = new FindState(options); - } catch (err) { - clearHighlightRects(); - throw err; - } - - if (newFindState.results.length) { - let selected = CurrentFindState && CurrentFindState.selected; - let selectIndex = 0; - if (selected) { - let {node: currentNode, offset: currentOffset} = selected; - selectIndex = newFindState.results.findIndex(r => { - if (r.node === currentNode) { - return r.offset >= currentOffset; - } - - let relation = currentNode.compareDocumentPosition(r.node); - return Boolean(relation & Node.DOCUMENT_POSITION_FOLLOWING); - }); - } - - newFindState.selectNext(selectIndex+1); - } else { - clearHighlightRects(); - } - - CurrentFindState = newFindState; - return btoa(JSON.stringify(CurrentFindState, (k, v) => (ExcludeKeys.has(k) ? undefined : v))); -}); - -selectNextResult = withEncodedArg(options => { - if (CurrentFindState) - CurrentFindState.selectNext(); -}); - -selectPreviousResult = withEncodedArg(options => { - if (CurrentFindState) - CurrentFindState.selectPrevious(); -}); - -function endFind() { - clearHighlightRects() - CurrentFindState = null; -} diff --git a/Multiplatform/Shared/Article/page.html b/Multiplatform/Shared/Article/page.html deleted file mode 100644 index 9d38c005e..000000000 --- a/Multiplatform/Shared/Article/page.html +++ /dev/null @@ -1,21 +0,0 @@ - - - [[title]] - - - - - - - - - - [[body]] - - diff --git a/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index 4f1440c52..000000000 --- a/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.933", - "green" : "0.416", - "red" : "0.031" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.957", - "green" : "0.620", - "red" : "0.369" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 8ba7ca9ea..000000000 --- a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "images" : [ - { - "filename" : "icon-40.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "icon-60.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "icon-58.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "icon-87.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "icon-80.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "icon-120.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "icon-121.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "icon-180.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "icon-20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "icon-41.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "icon-29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "icon-59.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "icon-42.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "icon-81.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "icon-76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "icon-152.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "icon-167.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "icon-1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - }, - { - "filename" : "Icon-MacOS-16x16@1x.png.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "filename" : "Icon-MacOS-16x16@2x.png.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "filename" : "Icon-MacOS-32x32@1x.png.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "filename" : "Icon-MacOS-32x32@2x.png.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "filename" : "Icon-MacOS-128x128@1x.png.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "filename" : "Icon-MacOS-128x128@2x.png.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "filename" : "Icon-MacOS-256x256@1x.png.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "filename" : "Icon-MacOS-256x256@2x.png.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "filename" : "Icon-MacOS-512x512@1x.png.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "filename" : "Icon-MacOS-512x512@2x.png.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@1x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@1x.png.png deleted file mode 100644 index e8c6573fd..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@1x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@2x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@2x.png.png deleted file mode 100644 index 176ad77e6..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@2x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@1x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@1x.png.png deleted file mode 100644 index 4477733ee..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@1x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@2x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@2x.png.png deleted file mode 100644 index 5a536a436..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@2x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@1x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@1x.png.png deleted file mode 100644 index 176ad77e6..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@1x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@2x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@2x.png.png deleted file mode 100644 index 943c3a89e..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@2x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@1x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@1x.png.png deleted file mode 100644 index 084985da6..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@1x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@2x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@2x.png.png deleted file mode 100644 index 0c1d6e3b4..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@2x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@1x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@1x.png.png deleted file mode 100644 index 943c3a89e..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@1x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@2x.png.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@2x.png.png deleted file mode 100644 index 61f85ee9d..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@2x.png.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-1024.png deleted file mode 100644 index c2bb5d540..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-1024.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-120.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-120.png deleted file mode 100644 index 6d1a94fd9..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-120.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-121.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-121.png deleted file mode 100644 index 6d1a94fd9..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-121.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-152.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-152.png deleted file mode 100644 index b217d09c4..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-152.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-167.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-167.png deleted file mode 100644 index 4cd8fa6c0..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-167.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-180.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-180.png deleted file mode 100644 index 8c5c93b8c..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-180.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-20.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-20.png deleted file mode 100644 index 6be295367..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-20.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-29.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-29.png deleted file mode 100644 index c9c8ffb32..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-29.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-40.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-40.png deleted file mode 100644 index 180a98b25..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-40.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-41.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-41.png deleted file mode 100644 index 180a98b25..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-41.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-42.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-42.png deleted file mode 100644 index 180a98b25..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-42.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-58.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-58.png deleted file mode 100644 index a53d44864..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-58.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-59.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-59.png deleted file mode 100644 index a53d44864..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-59.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-60.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-60.png deleted file mode 100644 index 7a01bc978..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-60.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-76.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-76.png deleted file mode 100644 index 4aea101ae..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-76.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-80.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-80.png deleted file mode 100644 index 85289428d..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-80.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-81.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-81.png deleted file mode 100644 index 85289428d..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-81.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-87.png b/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-87.png deleted file mode 100644 index dd27b3ca3..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/AppIcon.appiconset/icon-87.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/ArticleExtractorError.imageset/ArticleExtractorError.pdf b/Multiplatform/Shared/Assets.xcassets/ArticleExtractorError.imageset/ArticleExtractorError.pdf deleted file mode 100644 index 7d1450ec6..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/ArticleExtractorError.imageset/ArticleExtractorError.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/ArticleExtractorError.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/ArticleExtractorError.imageset/Contents.json deleted file mode 100644 index 0f8e6142c..000000000 --- a/Multiplatform/Shared/Assets.xcassets/ArticleExtractorError.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "ArticleExtractorError.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/ArticleExtractorOn.symbolset/Contents.json b/Multiplatform/Shared/Assets.xcassets/ArticleExtractorOn.symbolset/Contents.json deleted file mode 100644 index 842391314..000000000 --- a/Multiplatform/Shared/Assets.xcassets/ArticleExtractorOn.symbolset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "symbols" : [ - { - "filename" : "doc.plaintext.on.svg", - "idiom" : "universal" - } - ] -} diff --git a/Multiplatform/Shared/Assets.xcassets/ArticleExtractorOn.symbolset/doc.plaintext.on.svg b/Multiplatform/Shared/Assets.xcassets/ArticleExtractorOn.symbolset/doc.plaintext.on.svg deleted file mode 100644 index f0a3af753..000000000 --- a/Multiplatform/Shared/Assets.xcassets/ArticleExtractorOn.symbolset/doc.plaintext.on.svg +++ /dev/null @@ -1,218 +0,0 @@ - - - - Untitled - Created with Sketch. - - - - - - - Weight/Scale Variations - - - Ultralight - - - Thin - - - Light - - - Regular - - - Medium - - - Semibold - - - Bold - - - Heavy - - - Black - - - - - - - - - - - - - Design Variations - - - Symbols are supported in up to nine weights and three scales. - - - For optimal layout with text and other symbols, vertically align - - - symbols with the adjacent text. - - - - - - - - Margins - - - Leading and trailing margins on the left and right side of each symbol - - - can be adjusted by modifying the width of the blue rectangles. - - - Modifications are automatically applied proportionally to all - - - scales and weights. - - - - - - Exporting - - - Symbols should be outlined when exporting to ensure the - - - design is preserved when submitting to Xcode. - - - Template v.1.0 - - - Generated from doc.plaintext - - - Typeset at 100 points - - - Small - - - Medium - - - Large - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Multiplatform/Shared/Assets.xcassets/Contents.json b/Multiplatform/Shared/Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596a..000000000 --- a/Multiplatform/Shared/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointMarsEdit.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/ExtensionPointMarsEdit.imageset/Contents.json deleted file mode 100644 index 432281179..000000000 --- a/Multiplatform/Shared/Assets.xcassets/ExtensionPointMarsEdit.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "MarsEditOfficial.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointMarsEdit.imageset/MarsEditOfficial.pdf b/Multiplatform/Shared/Assets.xcassets/ExtensionPointMarsEdit.imageset/MarsEditOfficial.pdf deleted file mode 100644 index 82396a369..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/ExtensionPointMarsEdit.imageset/MarsEditOfficial.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointMicroblog.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/ExtensionPointMicroblog.imageset/Contents.json deleted file mode 100644 index 4db95b5b7..000000000 --- a/Multiplatform/Shared/Assets.xcassets/ExtensionPointMicroblog.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "micro-dot-blog.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointMicroblog.imageset/micro-dot-blog.pdf b/Multiplatform/Shared/Assets.xcassets/ExtensionPointMicroblog.imageset/micro-dot-blog.pdf deleted file mode 100644 index ad66dd377..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/ExtensionPointMicroblog.imageset/micro-dot-blog.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/Contents.json deleted file mode 100644 index 1a49be3b3..000000000 --- a/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "reddit-light.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "reddit-dark.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/reddit-dark.pdf b/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/reddit-dark.pdf deleted file mode 100644 index b4b6d7419..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/reddit-dark.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/reddit-light.pdf b/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/reddit-light.pdf deleted file mode 100644 index 4d6267b63..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/ExtensionPointReddit.imageset/reddit-light.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointTwitter.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/ExtensionPointTwitter.imageset/Contents.json deleted file mode 100644 index 834100cda..000000000 --- a/Multiplatform/Shared/Assets.xcassets/ExtensionPointTwitter.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "twitter.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/ExtensionPointTwitter.imageset/twitter.pdf b/Multiplatform/Shared/Assets.xcassets/ExtensionPointTwitter.imageset/twitter.pdf deleted file mode 100644 index e50de4443..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/ExtensionPointTwitter.imageset/twitter.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/FaviconTemplateImage.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/FaviconTemplateImage.imageset/Contents.json deleted file mode 100644 index bd3a2fbbe..000000000 --- a/Multiplatform/Shared/Assets.xcassets/FaviconTemplateImage.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "faviconTemplateImage.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/FaviconTemplateImage.imageset/faviconTemplateImage.pdf b/Multiplatform/Shared/Assets.xcassets/FaviconTemplateImage.imageset/faviconTemplateImage.pdf deleted file mode 100644 index d6bcb5b69..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/FaviconTemplateImage.imageset/faviconTemplateImage.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/IconBackgroundColor.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/IconBackgroundColor.colorset/Contents.json deleted file mode 100644 index 49db4ebbe..000000000 --- a/Multiplatform/Shared/Assets.xcassets/IconBackgroundColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.929", - "green" : "0.922", - "red" : "0.922" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.220", - "green" : "0.220", - "red" : "0.220" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/MarkAllAsRead.symbolset/Contents.json b/Multiplatform/Shared/Assets.xcassets/MarkAllAsRead.symbolset/Contents.json deleted file mode 100644 index 81428a2de..000000000 --- a/Multiplatform/Shared/Assets.xcassets/MarkAllAsRead.symbolset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "symbols" : [ - { - "filename" : "markAllAsRead.svg", - "idiom" : "universal" - } - ] -} diff --git a/Multiplatform/Shared/Assets.xcassets/MarkAllAsRead.symbolset/markAllAsRead.svg b/Multiplatform/Shared/Assets.xcassets/MarkAllAsRead.symbolset/markAllAsRead.svg deleted file mode 100644 index 10e23abd9..000000000 --- a/Multiplatform/Shared/Assets.xcassets/MarkAllAsRead.symbolset/markAllAsRead.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - markAllAsRead - Created with Sketch. - - - - - - - Weight/Scale Variations - - - Ultralight - - - Thin - - - Light - - - Regular - - - Medium - - - Semibold - - - Bold - - - Heavy - - - Black - - - - - - - - - - - - - Design Variations - - - Symbols are supported in up to nine weights and three scales. - - - For optimal layout with text and other symbols, vertically align - - - symbols with the adjacent text. - - - - - - - - Margins - - - Leading and trailing margins on the left and right side of each symbol - - - can be adjusted by modifying the width of the blue rectangles. - - - Modifications are automatically applied proportionally to all - - - scales and weights. - - - - - - Exporting - - - Symbols should be outlined when exporting to ensure the - - - design is preserved when submitting to Xcode. - - - Template v.1.0 - - - Generated from circle - - - Typeset at 100 points - - - Small - - - Medium - - - Large - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPNG.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPNG.imageset/Contents.json deleted file mode 100644 index b494681a3..000000000 --- a/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPNG.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "mark-all-as-read.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPNG.imageset/mark-all-as-read.png b/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPNG.imageset/mark-all-as-read.png deleted file mode 100644 index c78a44c1b..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/MarkAllAsReadPNG.imageset/mark-all-as-read.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountBackground.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountBackground.colorset/Contents.json deleted file mode 100644 index 39632c80d..000000000 --- a/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountBackground.colorset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.248", - "blue" : "0.118", - "green" : "0.118", - "red" : "0.118" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.250", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - }, - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.500", - "blue" : "0.000", - "green" : "0.000", - "red" : "0.000" - } - }, - "idiom" : "mac" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.500", - "blue" : "0.000", - "green" : "0.000", - "red" : "0.000" - } - }, - "idiom" : "mac" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountForeground.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountForeground.colorset/Contents.json deleted file mode 100644 index 91ae93460..000000000 --- a/Multiplatform/Shared/Assets.xcassets/SidebarUnreadCountForeground.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.900", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/StarColor.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/StarColor.colorset/Contents.json deleted file mode 100644 index c464941a7..000000000 --- a/Multiplatform/Shared/Assets.xcassets/StarColor.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.204", - "green" : "0.776", - "red" : "0.976" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/WebStatusBarBackground.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/WebStatusBarBackground.colorset/Contents.json deleted file mode 100644 index c947def89..000000000 --- a/Multiplatform/Shared/Assets.xcassets/WebStatusBarBackground.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.940", - "green" : "0.940", - "red" : "0.940" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.196", - "green" : "0.196", - "red" : "0.196" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountBazQux.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountBazQux.imageset/Contents.json deleted file mode 100644 index 25d8387f8..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountBazQux.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "bazqux-any.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountBazQux.imageset/bazqux-any.pdf b/Multiplatform/Shared/Assets.xcassets/accountBazQux.imageset/bazqux-any.pdf deleted file mode 100644 index d13a0defd..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountBazQux.imageset/bazqux-any.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/Contents.json deleted file mode 100644 index b2f51e691..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "icloud-any.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "icloud-dark.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud-any.pdf b/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud-any.pdf deleted file mode 100644 index 79ba7e3eb..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud-any.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud-dark.pdf b/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud-dark.pdf deleted file mode 100644 index e876337ac..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud-dark.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/Contents.json deleted file mode 100644 index f7b68151c..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/Contents.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "images" : [ - { - "filename" : "feedwranger-any-slice.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "feedwranger-dark-slice.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "feedwranger-any-slice@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "feedwranger-dark-slice@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "feedwranger-any-slice@3x.png", - "idiom" : "universal", - "scale" : "3x" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "feedwranger-dark-slice@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice.png b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice.png deleted file mode 100644 index a04e07f9a..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice@2x.png b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice@2x.png deleted file mode 100644 index dd25a60ae..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice@2x.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice@3x.png b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice@3x.png deleted file mode 100644 index 1fceca03d..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-any-slice@3x.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice.png b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice.png deleted file mode 100644 index ff1990102..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice@2x.png b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice@2x.png deleted file mode 100644 index e2e52edb5..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice@2x.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice@3x.png b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice@3x.png deleted file mode 100644 index e1640465a..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/feedwranger-dark-slice@3x.png and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/Contents.json deleted file mode 100644 index 1b0780896..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "feedbin.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/feedbin.pdf b/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/feedbin.pdf deleted file mode 100644 index 8892e9db6..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/feedbin.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/Contents.json deleted file mode 100644 index 236ba8fa6..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "feedly-logo-any.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "feedly-logo-dark.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/feedly-logo-any.pdf b/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/feedly-logo-any.pdf deleted file mode 100644 index e1ccaab94..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/feedly-logo-any.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/feedly-logo-dark.pdf b/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/feedly-logo-dark.pdf deleted file mode 100644 index f287b271b..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/feedly-logo-dark.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/Contents.json deleted file mode 100644 index f71a580b8..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "FreshRSS.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/FreshRSS.pdf b/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/FreshRSS.pdf deleted file mode 100644 index d9ba3f3ea..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/FreshRSS.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/Contents.json deleted file mode 100644 index 8711b150a..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/Contents.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "images" : [ - { - "filename" : "inoreader_logo-any.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "inoreader_logo-dark.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/inoreader_logo-any.pdf b/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/inoreader_logo-any.pdf deleted file mode 100644 index 4c5befe74..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/inoreader_logo-any.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/inoreader_logo-dark.pdf b/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/inoreader_logo-dark.pdf deleted file mode 100644 index 2ab5d34dd..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountInoreader.imageset/inoreader_logo-dark.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/Contents.json deleted file mode 100644 index 63f3b392e..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "localAccountLight.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "localAccountDark-1.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/localAccountDark-1.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/localAccountDark-1.pdf deleted file mode 100644 index 584b84f4a..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/localAccountDark-1.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/localAccountLight.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/localAccountLight.pdf deleted file mode 100644 index d3d3d40e2..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountLocal.imageset/localAccountLight.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/Contents.json deleted file mode 100644 index 63f3b392e..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "localAccountLight.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "localAccountDark-1.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountDark-1.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountDark-1.pdf deleted file mode 100644 index 584b84f4a..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountDark-1.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountLight.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountLight.pdf deleted file mode 100644 index d3d3d40e2..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountLight.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/Contents.json deleted file mode 100644 index e072222a1..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/Contents.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "images" : [ - { - "filename" : "ipad-any-slice.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "ipad-dark-slice.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/ipad-any-slice.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/ipad-any-slice.pdf deleted file mode 100644 index 91665fa1b..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/ipad-any-slice.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/ipad-dark-slice.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/ipad-dark-slice.pdf deleted file mode 100644 index 9091a096b..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/ipad-dark-slice.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/Contents.json deleted file mode 100644 index 04d21c05f..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/Contents.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "images" : [ - { - "filename" : "iphone-any-slice.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "iphone-dark-slice.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/iphone-any-slice.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/iphone-any-slice.pdf deleted file mode 100644 index f36056970..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/iphone-any-slice.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/iphone-dark-slice.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/iphone-dark-slice.pdf deleted file mode 100644 index cf2247077..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/iphone-dark-slice.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Contents.json deleted file mode 100644 index a73c591f6..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "Newsblur-any.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Newsblur-any.pdf b/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Newsblur-any.pdf deleted file mode 100644 index 216dc4f8a..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Newsblur-any.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/Contents.json deleted file mode 100644 index 231c33ab0..000000000 --- a/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/Contents.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "images" : [ - { - "filename" : "oldreader-icon-any.pdf", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "oldreader-icon-dark.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "original" - } -} diff --git a/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/oldreader-icon-any.pdf b/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/oldreader-icon-any.pdf deleted file mode 100644 index 05b0003b1..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/oldreader-icon-any.pdf and /dev/null differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/oldreader-icon-dark.pdf b/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/oldreader-icon-dark.pdf deleted file mode 100644 index dfe4ce8b4..000000000 Binary files a/Multiplatform/Shared/Assets.xcassets/accountTheOldReader.imageset/oldreader-icon-dark.pdf and /dev/null differ diff --git a/Multiplatform/Shared/CombineExt/DemandBuffer.swift b/Multiplatform/Shared/CombineExt/DemandBuffer.swift deleted file mode 100644 index 7b02ac377..000000000 --- a/Multiplatform/Shared/CombineExt/DemandBuffer.swift +++ /dev/null @@ -1,151 +0,0 @@ -// -// DemandBuffer.swift -// CombineExt -// -// Created by Shai Mishali on 21/02/2020. -// Copyright © 2020 Combine Community. All rights reserved. -// - -#if canImport(Combine) -import Combine -import class Foundation.NSRecursiveLock - -/// A buffer responsible for managing the demand of a downstream -/// subscriber for an upstream publisher -/// -/// It buffers values and completion events and forwards them dynamically -/// according to the demand requested by the downstream -/// -/// In a sense, the subscription only relays the requests for demand, as well -/// the events emitted by the upstream — to this buffer, which manages -/// the entire behavior and backpressure contract -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -class DemandBuffer { - private let lock = NSRecursiveLock() - private var buffer = [S.Input]() - private let subscriber: S - private var completion: Subscribers.Completion? - private var demandState = Demand() - - /// Initialize a new demand buffer for a provided downstream subscriber - /// - /// - parameter subscriber: The downstream subscriber demanding events - init(subscriber: S) { - self.subscriber = subscriber - } - - /// Buffer an upstream value to later be forwarded to - /// the downstream subscriber, once it demands it - /// - /// - parameter value: Upstream value to buffer - /// - /// - returns: The demand fulfilled by the bufferr - func buffer(value: S.Input) -> Subscribers.Demand { - precondition(self.completion == nil, - "How could a completed publisher sent values?! Beats me 🤷‍♂️") - - switch demandState.requested { - case .unlimited: - return subscriber.receive(value) - default: - buffer.append(value) - return flush() - } - } - - /// Complete the demand buffer with an upstream completion event - /// - /// This method will deplete the buffer immediately, - /// based on the currently accumulated demand, and relay the - /// completion event down as soon as demand is fulfilled - /// - /// - parameter completion: Completion event - func complete(completion: Subscribers.Completion) { - precondition(self.completion == nil, - "Completion have already occured, which is quite awkward 🥺") - - self.completion = completion - _ = flush() - } - - /// Signal to the buffer that the downstream requested new demand - /// - /// - note: The buffer will attempt to flush as many events rqeuested - /// by the downstream at this point - func demand(_ demand: Subscribers.Demand) -> Subscribers.Demand { - flush(adding: demand) - } - - /// Flush buffered events to the downstream based on the current - /// state of the downstream's demand - /// - /// - parameter newDemand: The new demand to add. If `nil`, the flush isn't the - /// result of an explicit demand change - /// - /// - note: After fulfilling the downstream's request, if completion - /// has already occured, the buffer will be cleared and the - /// completion event will be sent to the downstream subscriber - private func flush(adding newDemand: Subscribers.Demand? = nil) -> Subscribers.Demand { - lock.lock() - defer { lock.unlock() } - - if let newDemand = newDemand { - demandState.requested += newDemand - } - - // If buffer isn't ready for flushing, return immediately - guard demandState.requested > 0 || newDemand == Subscribers.Demand.none else { return .none } - - while !buffer.isEmpty && demandState.processed < demandState.requested { - demandState.requested += subscriber.receive(buffer.remove(at: 0)) - demandState.processed += 1 - } - - if let completion = completion { - // Completion event was already sent - buffer = [] - demandState = .init() - self.completion = nil - subscriber.receive(completion: completion) - return .none - } - - let sentDemand = demandState.requested - demandState.sent - demandState.sent += sentDemand - return sentDemand - } -} - -// MARK: - Private Helpers -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -private extension DemandBuffer { - /// A model that tracks the downstream's - /// accumulated demand state - struct Demand { - var processed: Subscribers.Demand = .none - var requested: Subscribers.Demand = .none - var sent: Subscribers.Demand = .none - } -} - -// MARK: - Internally-scoped helpers -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension Subscription { - /// Reqeust demand if it's not empty - /// - /// - parameter demand: Requested demand - func requestIfNeeded(_ demand: Subscribers.Demand) { - guard demand > .none else { return } - request(demand) - } -} - -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension Optional where Wrapped == Subscription { - /// Cancel the Optional subscription and nullify it - mutating func kill() { - self?.cancel() - self = nil - } -} -#endif diff --git a/Multiplatform/Shared/CombineExt/ReplaySubject.swift b/Multiplatform/Shared/CombineExt/ReplaySubject.swift deleted file mode 100644 index b639da4fd..000000000 --- a/Multiplatform/Shared/CombineExt/ReplaySubject.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// ReplaySubject.swift -// CombineExt -// -// Created by Jasdev Singh on 13/04/2020. -// Copyright © 2020 Combine Community. All rights reserved. -// - -#if canImport(Combine) -import Combine - -/// A `ReplaySubject` is a subject that can buffer one or more values. It stores value events, up to its `bufferSize` in a -/// first-in-first-out manner and then replays it to -/// future subscribers and also forwards completion events. -/// -/// The implementation borrows heavily from [Entwine’s](https://github.com/tcldr/Entwine/blob/b839c9fcc7466878d6a823677ce608da998b95b9/Sources/Entwine/Operators/ReplaySubject.swift). -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public final class ReplaySubject: Subject { - public typealias Output = Output - public typealias Failure = Failure - - private let bufferSize: Int - private var buffer = [Output]() - - // Keeping track of all live subscriptions, so `send` events can be forwarded to them. - private var subscriptions = [Subscription>]() - - private var completion: Subscribers.Completion? - private var isActive: Bool { completion == nil } - - /// Create a `ReplaySubject`, buffering up to `bufferSize` values and replaying them to new subscribers - /// - Parameter bufferSize: The maximum number of value events to buffer and replay to all future subscribers. - public init(bufferSize: Int) { - self.bufferSize = bufferSize - } - - public func send(_ value: Output) { - guard isActive else { return } - - buffer.append(value) - - if buffer.count > bufferSize { - buffer.removeFirst() - } - - subscriptions.forEach { $0.forwardValueToBuffer(value) } - } - - public func send(completion: Subscribers.Completion) { - guard isActive else { return } - - self.completion = completion - - subscriptions.forEach { $0.forwardCompletionToBuffer(completion) } - } - - public func send(subscription: Combine.Subscription) { - subscription.request(.unlimited) - } - - public func receive(subscriber: Subscriber) where Failure == Subscriber.Failure, Output == Subscriber.Input { - let subscriberIdentifier = subscriber.combineIdentifier - - let subscription = Subscription(downstream: AnySubscriber(subscriber)) { [weak self] in - guard let self = self, - let subscriptionIndex = self.subscriptions - .firstIndex(where: { $0.innerSubscriberIdentifier == subscriberIdentifier }) else { return } - - self.subscriptions.remove(at: subscriptionIndex) - } - - subscriptions.append(subscription) - - subscriber.receive(subscription: subscription) - subscription.replay(buffer, completion: completion) - } -} - -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -extension ReplaySubject { - final class Subscription: Combine.Subscription where Output == Downstream.Input, Failure == Downstream.Failure { - private var demandBuffer: DemandBuffer? - private var cancellationHandler: (() -> Void)? - - fileprivate let innerSubscriberIdentifier: CombineIdentifier - - init(downstream: Downstream, cancellationHandler: (() -> Void)?) { - self.demandBuffer = DemandBuffer(subscriber: downstream) - self.innerSubscriberIdentifier = downstream.combineIdentifier - self.cancellationHandler = cancellationHandler - } - - func replay(_ buffer: [Output], completion: Subscribers.Completion?) { - buffer.forEach(forwardValueToBuffer) - - if let completion = completion { - forwardCompletionToBuffer(completion) - } - } - - func forwardValueToBuffer(_ value: Output) { - _ = demandBuffer?.buffer(value: value) - } - - func forwardCompletionToBuffer(_ completion: Subscribers.Completion) { - demandBuffer?.complete(completion: completion) - } - - func request(_ demand: Subscribers.Demand) { - _ = demandBuffer?.demand(demand) - } - - func cancel() { - cancellationHandler?() - cancellationHandler = nil - - demandBuffer = nil - } - } -} -#endif diff --git a/Multiplatform/Shared/CombineExt/ShareReplay.swift b/Multiplatform/Shared/CombineExt/ShareReplay.swift deleted file mode 100644 index d5c2b24a6..000000000 --- a/Multiplatform/Shared/CombineExt/ShareReplay.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ShareReplay.swift -// CombineExt -// -// Created by Jasdev Singh on 13/04/2020. -// Copyright © 2020 Combine Community. All rights reserved. -// - -#if canImport(Combine) -import Combine - -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public extension Publisher { - /// A variation on [share()](https://developer.apple.com/documentation/combine/publisher/3204754-share) - /// that allows for buffering and replaying a `replay` amount of value events to future subscribers. - /// - /// - Parameter count: The number of value events to buffer in a first-in-first-out manner. - /// - Returns: A publisher that replays the specified number of value events to future subscribers. - func share(replay count: Int) -> Publishers.Autoconnect>> { - multicast { ReplaySubject(bufferSize: count) } - .autoconnect() - } -} -#endif diff --git a/Multiplatform/Shared/CombineExt/Sink.swift b/Multiplatform/Shared/CombineExt/Sink.swift deleted file mode 100644 index c60008ac8..000000000 --- a/Multiplatform/Shared/CombineExt/Sink.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Sink.swift -// CombineExt -// -// Created by Shai Mishali on 14/03/2020. -// Copyright © 2020 Combine Community. All rights reserved. -// - -#if canImport(Combine) -import Combine - -/// A generic sink using an underlying demand buffer to balance -/// the demand of a downstream subscriber for the events of an -/// upstream publisher -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -class Sink: Subscriber { - typealias TransformFailure = (Upstream.Failure) -> Downstream.Failure? - typealias TransformOutput = (Upstream.Output) -> Downstream.Input? - - private(set) var buffer: DemandBuffer - private var upstreamSubscription: Subscription? - private let transformOutput: TransformOutput? - private let transformFailure: TransformFailure? - - /// Initialize a new sink subscribing to the upstream publisher and - /// fulfilling the demand of the downstream subscriber using a backpresurre - /// demand-maintaining buffer. - /// - /// - parameter upstream: The upstream publisher - /// - parameter downstream: The downstream subscriber - /// - parameter transformOutput: Transform the upstream publisher's output type to the downstream's input type - /// - parameter transformFailure: Transform the upstream failure type to the downstream's failure type - /// - /// - note: You **must** provide the two transformation functions above if you're using - /// the default `Sink` implementation. Otherwise, you must subclass `Sink` with your own - /// publisher's sink and manage the buffer accordingly. - init(upstream: Upstream, - downstream: Downstream, - transformOutput: TransformOutput? = nil, - transformFailure: TransformFailure? = nil) { - self.buffer = DemandBuffer(subscriber: downstream) - self.transformOutput = transformOutput - self.transformFailure = transformFailure - upstream.subscribe(self) - } - - func demand(_ demand: Subscribers.Demand) { - let newDemand = buffer.demand(demand) - upstreamSubscription?.requestIfNeeded(newDemand) - } - - func receive(subscription: Subscription) { - upstreamSubscription = subscription - } - - func receive(_ input: Upstream.Output) -> Subscribers.Demand { - guard let transform = transformOutput else { - fatalError(""" - ❌ Missing output transformation - ========================= - - You must either: - - Provide a transformation function from the upstream's output to the downstream's input; or - - Subclass `Sink` with your own publisher's Sink and manage the buffer yourself - """) - } - - guard let input = transform(input) else { return .none } - return buffer.buffer(value: input) - } - - func receive(completion: Subscribers.Completion) { - switch completion { - case .finished: - buffer.complete(completion: .finished) - case .failure(let error): - guard let transform = transformFailure else { - fatalError(""" - ❌ Missing failure transformation - ========================= - - You must either: - - Provide a transformation function from the upstream's failure to the downstream's failuer; or - - Subclass `Sink` with your own publisher's Sink and manage the buffer yourself - """) - } - - guard let error = transform(error) else { return } - buffer.complete(completion: .failure(error)) - } - - cancelUpstream() - } - - func cancelUpstream() { - upstreamSubscription.kill() - } - - deinit { cancelUpstream() } -} -#endif diff --git a/Multiplatform/Shared/CombineExt/WIthLatestFrom.swift b/Multiplatform/Shared/CombineExt/WIthLatestFrom.swift deleted file mode 100644 index cda9b1c84..000000000 --- a/Multiplatform/Shared/CombineExt/WIthLatestFrom.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// WithLatestFrom.swift -// CombineExt -// -// Created by Shai Mishali on 29/08/2019. -// Copyright © 2020 Combine Community. All rights reserved. -// - -#if canImport(Combine) -import Combine - -// MARK: - Operator methods -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public extension Publisher { - /// Merges two publishers into a single publisher by combining each value - /// from self with the latest value from the second publisher, if any. - /// - /// - parameter other: A second publisher source. - /// - parameter resultSelector: Function to invoke for each value from the self combined - /// with the latest value from the second source, if any. - /// - /// - returns: A publisher containing the result of combining each value of the self - /// with the latest value from the second publisher, if any, using the - /// specified result selector function. - func withLatestFrom(_ other: Other, - resultSelector: @escaping (Output, Other.Output) -> Result) - -> Publishers.WithLatestFrom { - return .init(upstream: self, second: other, resultSelector: resultSelector) - } - - /// Merges three publishers into a single publisher by combining each value - /// from self with the latest value from the second and third publisher, if any. - /// - /// - parameter other: A second publisher source. - /// - parameter other1: A third publisher source. - /// - parameter resultSelector: Function to invoke for each value from the self combined - /// with the latest value from the second and third source, if any. - /// - /// - returns: A publisher containing the result of combining each value of the self - /// with the latest value from the second and third publisher, if any, using the - /// specified result selector function. - func withLatestFrom(_ other: Other, - _ other1: Other1, - resultSelector: @escaping (Output, (Other.Output, Other1.Output)) -> Result) - -> Publishers.WithLatestFrom, Result> - where Other.Failure == Failure, Other1.Failure == Failure { - let combined = other.combineLatest(other1) - .eraseToAnyPublisher() - return .init(upstream: self, second: combined, resultSelector: resultSelector) - } - - /// Merges four publishers into a single publisher by combining each value - /// from self with the latest value from the second, third and fourth publisher, if any. - /// - /// - parameter other: A second publisher source. - /// - parameter other1: A third publisher source. - /// - parameter other2: A fourth publisher source. - /// - parameter resultSelector: Function to invoke for each value from the self combined - /// with the latest value from the second, third and fourth source, if any. - /// - /// - returns: A publisher containing the result of combining each value of the self - /// with the latest value from the second, third and fourth publisher, if any, using the - /// specified result selector function. - func withLatestFrom(_ other: Other, - _ other1: Other1, - _ other2: Other2, - resultSelector: @escaping (Output, (Other.Output, Other1.Output, Other2.Output)) -> Result) - -> Publishers.WithLatestFrom, Result> - where Other.Failure == Failure, Other1.Failure == Failure, Other2.Failure == Failure { - let combined = other.combineLatest(other1, other2) - .eraseToAnyPublisher() - return .init(upstream: self, second: combined, resultSelector: resultSelector) - } - - /// Upon an emission from self, emit the latest value from the - /// second publisher, if any exists. - /// - /// - parameter other: A second publisher source. - /// - /// - returns: A publisher containing the latest value from the second publisher, if any. - func withLatestFrom(_ other: Other) - -> Publishers.WithLatestFrom { - return .init(upstream: self, second: other) { $1 } - } - - /// Upon an emission from self, emit the latest value from the - /// second and third publisher, if any exists. - /// - /// - parameter other: A second publisher source. - /// - parameter other1: A third publisher source. - /// - /// - returns: A publisher containing the latest value from the second and third publisher, if any. - func withLatestFrom(_ other: Other, - _ other1: Other1) - -> Publishers.WithLatestFrom, (Other.Output, Other1.Output)> - where Other.Failure == Failure, Other1.Failure == Failure { - withLatestFrom(other, other1) { $1 } - } - - /// Upon an emission from self, emit the latest value from the - /// second, third and forth publisher, if any exists. - /// - /// - parameter other: A second publisher source. - /// - parameter other1: A third publisher source. - /// - parameter other2: A forth publisher source. - /// - /// - returns: A publisher containing the latest value from the second, third and forth publisher, if any. - func withLatestFrom(_ other: Other, - _ other1: Other1, - _ other2: Other2) - -> Publishers.WithLatestFrom, (Other.Output, Other1.Output, Other2.Output)> - where Other.Failure == Failure, Other1.Failure == Failure, Other2.Failure == Failure { - withLatestFrom(other, other1, other2) { $1 } - } -} - -// MARK: - Publisher -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public extension Publishers { - struct WithLatestFrom: Publisher where Upstream.Failure == Other.Failure { - public typealias Failure = Upstream.Failure - public typealias ResultSelector = (Upstream.Output, Other.Output) -> Output - - private let upstream: Upstream - private let second: Other - private let resultSelector: ResultSelector - private var latestValue: Other.Output? - - init(upstream: Upstream, - second: Other, - resultSelector: @escaping ResultSelector) { - self.upstream = upstream - self.second = second - self.resultSelector = resultSelector - } - - public func receive(subscriber: S) where Failure == S.Failure, Output == S.Input { - subscriber.receive(subscription: Subscription(upstream: upstream, - downstream: subscriber, - second: second, - resultSelector: resultSelector)) - } - } -} - -// MARK: - Subscription -@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -private extension Publishers.WithLatestFrom { - class Subscription: Combine.Subscription, CustomStringConvertible where Downstream.Input == Output, Downstream.Failure == Failure { - private let resultSelector: ResultSelector - private var sink: Sink? - - private let upstream: Upstream - private let downstream: Downstream - private let second: Other - - // Secondary (other) publisher - private var latestValue: Other.Output? - private var otherSubscription: Cancellable? - private var preInitialDemand = Subscribers.Demand.none - - init(upstream: Upstream, - downstream: Downstream, - second: Other, - resultSelector: @escaping ResultSelector) { - self.upstream = upstream - self.second = second - self.downstream = downstream - self.resultSelector = resultSelector - - trackLatestFromSecond { [weak self] in - guard let self = self else { return } - self.request(self.preInitialDemand) - self.preInitialDemand = .none - } - } - - func request(_ demand: Subscribers.Demand) { - guard latestValue != nil else { - preInitialDemand += demand - return - } - - self.sink?.demand(demand) - } - - // Create an internal subscription to the `Other` publisher, - // constantly tracking its latest value - private func trackLatestFromSecond(onInitialValue: @escaping () -> Void) { - var gotInitialValue = false - - let subscriber = AnySubscriber( - receiveSubscription: { [weak self] subscription in - self?.otherSubscription = subscription - subscription.request(.unlimited) - }, - receiveValue: { [weak self] value in - guard let self = self else { return .none } - self.latestValue = value - - if !gotInitialValue { - // When getting initial value, start pulling values - // from upstream in the main sink - self.sink = Sink(upstream: self.upstream, - downstream: self.downstream, - transformOutput: { [weak self] value in - guard let self = self, - let other = self.latestValue else { return nil } - - return self.resultSelector(value, other) - }, - transformFailure: { $0 }) - - // Signal initial value to start fulfilling downstream demand - gotInitialValue = true - onInitialValue() - } - - return .unlimited - }, - receiveCompletion: nil) - - self.second.subscribe(subscriber) - } - - var description: String { - return "WithLatestFrom.Subscription<\(Output.self), \(Failure.self)>" - } - - func cancel() { - sink = nil - otherSubscription?.cancel() - } - } -} -#endif diff --git a/Multiplatform/Shared/ErrorHandler.swift b/Multiplatform/Shared/ErrorHandler.swift deleted file mode 100644 index 63bcf26d6..000000000 --- a/Multiplatform/Shared/ErrorHandler.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// ErrorHandler.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/28/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import RSCore -import os.log - -struct ErrorHandler { - - private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") - - public static func log(_ error: Error) { - os_log(.error, log: self.log, "%@", error.localizedDescription) - } - -} diff --git a/Multiplatform/Shared/Images/ArticleIconImageLoader.swift b/Multiplatform/Shared/Images/ArticleIconImageLoader.swift deleted file mode 100644 index cc94abe57..000000000 --- a/Multiplatform/Shared/Images/ArticleIconImageLoader.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// ArticleIconImageLoader.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/1/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import Combine -import Account -import Articles - -final class ArticleIconImageLoader: ObservableObject { - - @Published var image: IconImage? - private var article: Article? - private var cancellables = Set() - - init() { - NotificationCenter.default.publisher(for: .FaviconDidBecomeAvailable).sink { [weak self] _ in - guard let self = self, let article = self.article else { return } - self.image = article.iconImage() - }.store(in: &cancellables) - - NotificationCenter.default.publisher(for: .WebFeedIconDidBecomeAvailable).sink { [weak self] note in - guard let self = self, let article = self.article, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, noteFeed == article.webFeed else { - return - } - self.image = article.iconImage() - }.store(in: &cancellables) - - NotificationCenter.default.publisher(for: .AvatarDidBecomeAvailable).sink { [weak self] note in - guard let self = self, let article = self.article, let authors = article.authors, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { - return - } - for author in authors { - if author.avatarURL == avatarURL { - self.image = article.iconImage() - return - } - } - }.store(in: &cancellables) - } - - func loadImage(for article: Article) { - guard image == nil else { return } - self.article = article - image = article.iconImage() - } - -} diff --git a/Multiplatform/Shared/Images/FeedIconImageLoader.swift b/Multiplatform/Shared/Images/FeedIconImageLoader.swift deleted file mode 100644 index 833055310..000000000 --- a/Multiplatform/Shared/Images/FeedIconImageLoader.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// FeedIconImageLoader.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/29/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Combine -import Account - -final class FeedIconImageLoader: ObservableObject { - - @Published var image: IconImage? - private var feed: Feed? - private var cancellables = Set() - - init() { - NotificationCenter.default.publisher(for: .FaviconDidBecomeAvailable).sink { [weak self] _ in - self?.fetchImage() - }.store(in: &cancellables) - - - NotificationCenter.default.publisher(for: .WebFeedIconDidBecomeAvailable).sink { [weak self] note in - guard let feed = self?.feed as? WebFeed, let noteFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, feed == noteFeed else { - return - } - self?.fetchImage() - }.store(in: &cancellables) - } - - func loadImage(for feed: Feed) { - guard image == nil else { return } - self.feed = feed - fetchImage() - } - -} - -private extension FeedIconImageLoader { - - func fetchImage() { - guard let feed = feed else { return } - image = IconImageCache.shared.imageForFeed(feed) - } -} diff --git a/Multiplatform/Shared/Images/IconImageView.swift b/Multiplatform/Shared/Images/IconImageView.swift deleted file mode 100644 index 822b3a7ad..000000000 --- a/Multiplatform/Shared/Images/IconImageView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// IconImageView.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/29/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct IconImageView: View { - - @Environment(\.colorScheme) var colorScheme - var iconImage: IconImage - - var body: some View { - GeometryReader { proxy in - - let newSize = newImageSize(viewSize: proxy.size) - let tooShort = newSize.height < proxy.size.height - let indistinguishable = colorScheme == .dark ? iconImage.isDark : iconImage.isBright - let showBackground = (tooShort && !iconImage.isSymbol) || indistinguishable - - Group { - Image(rsImage: iconImage.image) - .resizable() - .scaledToFit() - .frame(width: newSize.width, height: newSize.height, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) - } - .frame(width: proxy.size.width, height: proxy.size.height, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) - .background(showBackground ? AppAssets.iconBackgroundColor : nil) - .cornerRadius(4) - - } - } - - func newImageSize(viewSize: CGSize) -> CGSize { - let imageSize = iconImage.image.size - let newSize: CGSize - - if imageSize.height == imageSize.width { - if imageSize.height >= viewSize.height { - newSize = CGSize(width: viewSize.width, height: viewSize.height) - } else { - newSize = CGSize(width: imageSize.width, height: imageSize.height) - } - } else if imageSize.height > imageSize.width { - let factor = viewSize.height / imageSize.height - let width = imageSize.width * factor - newSize = CGSize(width: width, height: viewSize.height) - } else { - let factor = viewSize.width / imageSize.width - let height = imageSize.height * factor - newSize = CGSize(width: viewSize.width, height: height) - } - - return newSize - } - -} diff --git a/Multiplatform/Shared/Inspector/InspectorModel.swift b/Multiplatform/Shared/Inspector/InspectorModel.swift deleted file mode 100644 index 63d774cef..000000000 --- a/Multiplatform/Shared/Inspector/InspectorModel.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// InspectorModel.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 18/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import UserNotifications -import RSCore -import Account -#if os(macOS) -import AppKit -#else -import UIKit -#endif - - -class InspectorModel: ObservableObject { - - // Global Inspector Variables - @Published var editedName: String = "" - @Published var shouldUpdate: Bool = false - - // Account Inspector Variables - @Published var notificationSettings: UNNotificationSettings? - @Published var notifyAboutNewArticles: Bool = false { - didSet { - updateNotificationSettings() - } - } - @Published var alwaysShowReaderView: Bool = false { - didSet { - selectedWebFeed?.isArticleExtractorAlwaysOn = alwaysShowReaderView - } - } - @Published var accountIsActive: Bool = false { - didSet { - selectedAccount?.isActive = accountIsActive - } - } - @Published var showHomePage: Bool = false // iOS only - - // Private Variables - private let centre = UNUserNotificationCenter.current() - private var selectedWebFeed: WebFeed? - private var selectedFolder: Folder? - private var selectedAccount: Account? - - init() { - getNotificationSettings() - } - - func getNotificationSettings() { - centre.getNotificationSettings { (settings) in - DispatchQueue.main.async { - self.notificationSettings = settings - if settings.authorizationStatus == .authorized { - #if os(macOS) - NSApplication.shared.registerForRemoteNotifications() - #else - UIApplication.shared.registerForRemoteNotifications() - #endif - } - } - } - } - - func configure(with feed: WebFeed) { - selectedWebFeed = feed - notifyAboutNewArticles = selectedWebFeed?.isNotifyAboutNewArticles ?? false - alwaysShowReaderView = selectedWebFeed?.isArticleExtractorAlwaysOn ?? false - editedName = feed.nameForDisplay - } - - func configure(with folder: Folder) { - selectedFolder = folder - editedName = folder.nameForDisplay - } - - func configure(with account: Account) { - selectedAccount = account - editedName = account.nameForDisplay - accountIsActive = account.isActive - } - - func updateNotificationSettings() { - guard let feed = selectedWebFeed, - let settings = notificationSettings - else { return } - if settings.authorizationStatus == .denied { - notifyAboutNewArticles = false - } else if settings.authorizationStatus == .authorized { - feed.isNotifyAboutNewArticles = notifyAboutNewArticles - } else { - UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .sound, .alert]) { [weak self] (granted, error) in - self?.updateNotificationSettings() - if granted { - DispatchQueue.main.async { - self?.selectedWebFeed!.isNotifyAboutNewArticles = self?.notifyAboutNewArticles - #if os(macOS) - NSApplication.shared.registerForRemoteNotifications() - #else - UIApplication.shared.registerForRemoteNotifications() - #endif - } - } else { - DispatchQueue.main.async { - self?.notifyAboutNewArticles = false - } - } - } - } - } - - -} - diff --git a/Multiplatform/Shared/Inspector/InspectorPlatformModifier.swift b/Multiplatform/Shared/Inspector/InspectorPlatformModifier.swift deleted file mode 100644 index f78c6f4fb..000000000 --- a/Multiplatform/Shared/Inspector/InspectorPlatformModifier.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// InspectorPlatformModifier.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 18/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct InspectorPlatformModifier: ViewModifier { - - @Environment(\.presentationMode) var presentationMode - @Binding var shouldUpdate: Bool - - @ViewBuilder func body(content: Content) -> some View { - - #if os(macOS) - content - .textFieldStyle(RoundedBorderTextFieldStyle()) - .frame(width: 300) - .padding() - #else - NavigationView { - content - .listStyle(InsetGroupedListStyle()) - .navigationBarTitle("Inspector", displayMode: .inline) - .navigationBarItems( - leading: - Button("Cancel", action: { - presentationMode.wrappedValue.dismiss() - }), - trailing: - Button("Done", action: { - shouldUpdate = true - }) - ) - } - #endif - } - - -} diff --git a/Multiplatform/Shared/Inspector/InspectorView.swift b/Multiplatform/Shared/Inspector/InspectorView.swift deleted file mode 100644 index 3111cd3ab..000000000 --- a/Multiplatform/Shared/Inspector/InspectorView.swift +++ /dev/null @@ -1,259 +0,0 @@ -// -// InspectorView.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 18/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import RSCore -import Account - -struct InspectorView: View { - - @Environment(\.presentationMode) var presentationMode - @StateObject private var feedIconImageLoader = FeedIconImageLoader() - @StateObject private var inspectorModel = InspectorModel() - var sidebarItem: SidebarItem - - var body: some View { - switch sidebarItem.representedType { - case .webFeed: - WebFeedInspectorView - .modifier(InspectorPlatformModifier(shouldUpdate: $inspectorModel.shouldUpdate)) - case .folder: - FolderInspectorView - .modifier(InspectorPlatformModifier(shouldUpdate: $inspectorModel.shouldUpdate)) - case .account: - AccountInspectorView - .modifier(InspectorPlatformModifier(shouldUpdate: $inspectorModel.shouldUpdate)) - default: - EmptyView() - } - } - - // MARK: WebFeed Inspector - - - var WebFeedInspectorView: some View { - Form { - Section(header: webFeedHeader) { - TextField("", text: $inspectorModel.editedName) - } - - #if os(macOS) - Divider() - #endif - - Section(content: { - Toggle("Notify About New Articles", isOn: $inspectorModel.notifyAboutNewArticles) - Toggle("Always Show Reader View", isOn: $inspectorModel.alwaysShowReaderView) - }) - - if let homePageURL = (sidebarItem.feed as? WebFeed)?.homePageURL { - #if os(macOS) - Divider() - #endif - - Section(header: Text("Home Page URL")) { - HStack { - Text(verbatim: homePageURL) - .fixedSize(horizontal: false, vertical: true) - Spacer() - AppAssets.openInBrowserImage - .foregroundColor(.accentColor) - } - .onTapGesture { - if let url = URL(string: homePageURL) { - #if os(macOS) - NSWorkspace.shared.open(url) - #else - inspectorModel.showHomePage = true - #endif - } - } - .contextMenu(ContextMenu(menuItems: { - Button(action: { - #if os(macOS) - URLPasteboardWriter.write(urlString: homePageURL, to: NSPasteboard.general) - #else - UIPasteboard.general.string = homePageURL - #endif - }, label: { - Text("Copy Home Page URL") - }) - })) - .sheet(isPresented: $inspectorModel.showHomePage, onDismiss: { inspectorModel.showHomePage = false }) { - #if os(macOS) - EmptyView() - #else - SafariView(url: URL(string: (sidebarItem.feed as! WebFeed).homePageURL!)!) - #endif - } - } - } - - #if os(macOS) - Divider() - #endif - - Section(header: Text("Feed URL")) { - VStack { -// #if os(macOS) -// Spacer() // This shouldn't be necessary, but for some reason macOS doesn't put the space in itself -// #endif - Text(verbatim: (sidebarItem.feed as? WebFeed)?.url ?? "") - .fixedSize(horizontal: false, vertical: true) - .contextMenu(ContextMenu(menuItems: { - Button(action: { - if let urlString = (sidebarItem.feed as? WebFeed)?.url { - #if os(macOS) - URLPasteboardWriter.write(urlString: urlString, to: NSPasteboard.general) - #else - UIPasteboard.general.string = urlString - #endif - } - }, label: { - Text("Copy Feed URL") - }) - })) - } - } - - #if os(macOS) - HStack { - Spacer() - Button("Cancel", action: { - presentationMode.wrappedValue.dismiss() - }) - Button("Done", action: { - inspectorModel.shouldUpdate = true - }) - }.padding([.top, .bottom], 20) - #endif - } - .onAppear { - inspectorModel.configure(with: sidebarItem.feed as! WebFeed) - feedIconImageLoader.loadImage(for: sidebarItem.feed!) - }.onReceive(inspectorModel.$shouldUpdate) { value in - if value == true { - if inspectorModel.editedName.trimmingWhitespace.count > 0 { - (sidebarItem.feed as? WebFeed)?.rename(to: inspectorModel.editedName.trimmingWhitespace) { _ in } - } - presentationMode.wrappedValue.dismiss() - } - } - } - - var webFeedHeader: some View { - HStack(alignment: .center) { - Spacer() - if let image = feedIconImageLoader.image { - IconImageView(iconImage: image) - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.top, 20) - } - - - // MARK: Folder Inspector - - var FolderInspectorView: some View { - Form { - Section(header: folderHeader) { - TextField("", text: $inspectorModel.editedName) - } - - #if os(macOS) - HStack { - Spacer() - Button("Cancel", action: { - presentationMode.wrappedValue.dismiss() - }) - Button("Done", action: { - inspectorModel.shouldUpdate = true - }) - }.padding([.top, .bottom]) - #endif - } - .onAppear { - inspectorModel.configure(with: sidebarItem.represented as! Folder) - feedIconImageLoader.loadImage(for: sidebarItem.feed!) - } - .onReceive(inspectorModel.$shouldUpdate) { value in - if value == true { - if inspectorModel.editedName.trimmingWhitespace.count > 0 { - (sidebarItem.feed as? Folder)?.rename(to: inspectorModel.editedName.trimmingWhitespace) { _ in } - } - presentationMode.wrappedValue.dismiss() - } - } - } - - var folderHeader: some View { - HStack(alignment: .center) { - Spacer() - if let image = feedIconImageLoader.image { - IconImageView(iconImage: image) - .frame(width: 50, height: 50) - } - Spacer() - }.padding(.top, 20) - } - - - // MARK: Account Inspector - - var AccountInspectorView: some View { - Form { - Section(header: accountHeader) { - TextField("", text: $inspectorModel.editedName) - Toggle("Active", isOn: $inspectorModel.accountIsActive) - } - - #if os(macOS) - HStack { - Spacer() - Button("Cancel", action: { - presentationMode.wrappedValue.dismiss() - }).keyboardShortcut(.cancelAction) - Button("Done", action: { - inspectorModel.shouldUpdate = true - }).keyboardShortcut(.defaultAction) - }.padding(.top) - #endif - } - .onAppear { - inspectorModel.configure(with: sidebarItem.represented as! Account) - } - .onReceive(inspectorModel.$shouldUpdate) { value in - if value == true { - if inspectorModel.editedName.trimmingWhitespace.count > 0 { - (sidebarItem.represented as? Account)?.name = inspectorModel.editedName - } else { - (sidebarItem.represented as? Account)?.name = nil - } - presentationMode.wrappedValue.dismiss() - } - } - } - - var accountHeader: some View { - HStack(alignment: .center) { - Spacer() - if let image = (sidebarItem.represented as? Account)?.smallIcon?.image { - Image(rsImage: image) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 50, height: 50) - } - Spacer() - }.padding() - } - - -} - - diff --git a/Multiplatform/Shared/MainApp.swift b/Multiplatform/Shared/MainApp.swift deleted file mode 100644 index e3b3dc473..000000000 --- a/Multiplatform/Shared/MainApp.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// MainApp.swift -// Shared -// -// Created by Maurice Parker on 6/27/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -@main -struct MainApp: App { - - #if os(macOS) - @NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate - @State private var selectedPane: MacPreferencePane = .general - #endif - #if os(iOS) - @UIApplicationDelegateAdaptor(AppDelegate.self) private var delegate - #endif - - @StateObject private var refreshProgress = RefreshProgressModel() - @StateObject private var defaults = AppDefaults.shared - - @SceneBuilder var body: some Scene { - #if os(macOS) - WindowGroup { - SceneNavigationView() - .frame(minWidth: 600, idealWidth: 1000, maxWidth: .infinity, minHeight: 600, idealHeight: 700, maxHeight: .infinity) - .onAppear { refreshProgress.startup() } - .environmentObject(refreshProgress) - .environmentObject(defaults) - .preferredColorScheme(AppDefaults.userInterfaceColorScheme) - } - .windowToolbarStyle(UnifiedWindowToolbarStyle()) - .commands { - SidebarCommands() - CommandGroup(after: .newItem, addition: { - Button("New Feed", action: {}) - .keyboardShortcut("N") - Button("New Folder", action: {}) - .keyboardShortcut("N", modifiers: [.shift, .command]) - Button("Refresh", action: {}) - .keyboardShortcut("R") - }) - CommandMenu("Subscriptions", content: { - Button("Import Subscriptions", action: {}) - .keyboardShortcut("I", modifiers: [.shift, .command]) - Button("Import NNW 3 Subscriptions", action: {}) - .keyboardShortcut("O", modifiers: [.shift, .command]) - Button("Export Subscriptions", action: {}) - .keyboardShortcut("E", modifiers: [.shift, .command]) - }) - CommandMenu("Go", content: { - Button("Next Unread", action: {}) - .keyboardShortcut("/", modifiers: [.command]) - Button("Today", action: {}) - .keyboardShortcut("1", modifiers: [.command]) - Button("All Unread", action: {}) - .keyboardShortcut("2", modifiers: [.command]) - Button("Starred", action: {}) - .keyboardShortcut("3", modifiers: [.command]) - }) - CommandMenu("Article", content: { - Button("Mark as Read", action: {}) - .keyboardShortcut("U", modifiers: [.shift, .command]) - Button("Mark All as Read", action: {}) - .keyboardShortcut("K", modifiers: [.command]) - Button("Mark Older as Read", action: {}) - .keyboardShortcut("K", modifiers: [.shift, .command]) - Button("Mark as Starred", action: {}) - .keyboardShortcut("L", modifiers: [.shift, .command]) - Button("Open in Browser", action: {}) - .keyboardShortcut(.rightArrow, modifiers: [.command]) - }) - CommandGroup(after: .help, addition: { - Button("Release Notes", action: { - NSWorkspace.shared.open(URL.releaseNotes) - }) - .keyboardShortcut("V", modifiers: [.shift, .command]) - }) - } - - // Mac Preferences - Settings { - TabView(selection: $selectedPane) { - GeneralPreferencesView() - .tabItem { - Image(systemName: "gearshape") - .font(.title2) - Text("General") - } - .tag(MacPreferencePane.general) - - AccountsPreferencesView() - .tabItem { - Image(systemName: "at") - .font(.title2) - Text("Accounts") - } - .tag(MacPreferencePane.accounts) - - LayoutPreferencesView() - .tabItem { - Image(systemName: "eyeglasses") - .font(.title2) - Text("Viewing") - } - .tag(MacPreferencePane.viewing) - - AdvancedPreferencesView() - .tabItem { - Image(systemName: "scale.3d") - .font(.title2) - Text("Advanced") - } - .tag(MacPreferencePane.advanced) - } - .preferredColorScheme(AppDefaults.userInterfaceColorScheme) - .frame(width: 500) - .padding() - } - #endif - - #if os(iOS) - WindowGroup { - SceneNavigationView() - .onAppear { refreshProgress.startup() } - .environmentObject(refreshProgress) - .environmentObject(defaults) - .preferredColorScheme(AppDefaults.userInterfaceColorScheme) - } - #endif - } -} diff --git a/Multiplatform/Shared/Previews/PreviewArticles.swift b/Multiplatform/Shared/Previews/PreviewArticles.swift deleted file mode 100644 index 75cc895d9..000000000 --- a/Multiplatform/Shared/Previews/PreviewArticles.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// PreviewArticles.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/1/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import Articles - -enum PreviewArticles { - - static var basicUnread: Article { - return makeBasicArticle(read: false, starred: false) - } - - static var basicRead: Article { - return makeBasicArticle(read: true, starred: false) - } - - static var basicStarred: Article { - return makeBasicArticle(read: false, starred: true) - } - -} - -private extension PreviewArticles { - - static var shortTitle: String { - return "Short article title" - } - - static var shortSummary: String { - return "Summary of article to be shown after title." - } - - static func makeBasicArticle(read: Bool, starred: Bool) -> Article { - let articleID = "prototype" - let status = ArticleStatus(articleID: articleID, read: read, starred: starred, dateArrived: Date()) - return Article(accountID: articleID, - articleID: articleID, - webFeedID: articleID, - uniqueID: articleID, - title: shortTitle, - contentHTML: nil, - contentText: nil, - url: nil, - externalURL: nil, - summary: shortSummary, - imageURL: nil, - datePublished: Date(), - dateModified: nil, - authors: nil, - status: status) - } - -} diff --git a/Multiplatform/Shared/RefreshProgressModel.swift b/Multiplatform/Shared/RefreshProgressModel.swift deleted file mode 100644 index 26e5c4a02..000000000 --- a/Multiplatform/Shared/RefreshProgressModel.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// RefreshProgressModel.swift -// NetNewsWire -// -// Created by Phil Viso on 7/2/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Combine -import RSCore -import Account - -class RefreshProgressModel: ObservableObject { - - enum State { - case refreshProgress(Float) - case lastRefreshDateText(String) - case none - } - - @Published var state = State.none - - private static var dateFormatter: RelativeDateTimeFormatter = { - let formatter = RelativeDateTimeFormatter() - formatter.dateTimeStyle = .named - - return formatter - }() - - private static let lastRefreshDateTextUpdateInterval = 60 - private static let lastRefreshDateTextRelativeDateFormattingThreshold = 60.0 - - func startup() { - updateState() - observeRefreshProgress() - scheduleLastRefreshDateTextUpdate() - } - - // MARK: Observing account changes - - private func observeRefreshProgress() { - NotificationCenter.default.addObserver(self, selector: #selector(accountRefreshProgressDidChange), name: .AccountRefreshProgressDidChange, object: nil) - } - - // MARK: Refreshing state - - @objc private func accountRefreshProgressDidChange() { - CoalescingQueue.standard.add(self, #selector(updateState)) - } - - @objc private func updateState() { - let progress = AccountManager.shared.combinedRefreshProgress - - if !progress.isComplete { - let fractionCompleted = Float(progress.numberCompleted) / Float(progress.numberOfTasks) - self.state = .refreshProgress(fractionCompleted) - } else if let lastRefreshDate = AccountManager.shared.lastArticleFetchEndTime { - let text = localizedLastRefreshText(lastRefreshDate: lastRefreshDate) - self.state = .lastRefreshDateText(text) - } else { - self.state = .none - } - } - - private func scheduleLastRefreshDateTextUpdate() { - DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(Self.lastRefreshDateTextUpdateInterval)) { - self.updateState() - self.scheduleLastRefreshDateTextUpdate() - } - } - - private func localizedLastRefreshText(lastRefreshDate: Date) -> String { - let now = Date() - - if now > lastRefreshDate.addingTimeInterval(Self.lastRefreshDateTextRelativeDateFormattingThreshold) { - let localizedDate = Self.dateFormatter.localizedString(for: lastRefreshDate, relativeTo: now) - let formatString = NSLocalizedString("Updated %@", comment: "Updated") as NSString - - return NSString.localizedStringWithFormat(formatString, localizedDate) as String - } else { - return NSLocalizedString("Updated Just Now", comment: "Updated Just Now") - } - } - -} diff --git a/Multiplatform/Shared/SceneModel.swift b/Multiplatform/Shared/SceneModel.swift deleted file mode 100644 index f644c391d..000000000 --- a/Multiplatform/Shared/SceneModel.swift +++ /dev/null @@ -1,215 +0,0 @@ -// -// SceneModel.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/28/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import Combine -import Account -import Articles -import RSCore - -final class SceneModel: ObservableObject { - - @Published var markAllAsReadButtonState: Bool? - @Published var nextUnreadButtonState: Bool? - @Published var readButtonState: Bool? - @Published var starButtonState: Bool? - @Published var extractorButtonState: ArticleExtractorButtonState? - @Published var openInBrowserButtonState: Bool? - @Published var shareButtonState: Bool? - @Published var accountSyncErrors: [AccountSyncError] = [] - - var selectedArticles: [Article] { - timelineModel.selectedArticles - } - - private var refreshProgressModel: RefreshProgressModel? = nil - private var articleIconSchemeHandler: ArticleIconSchemeHandler? = nil - - private(set) var webViewProvider: WebViewProvider? = nil - private(set) lazy var sidebarModel = SidebarModel(delegate: self) - private(set) lazy var timelineModel = TimelineModel(delegate: self) - - private var cancellables = Set() - - // MARK: Initialization API - - /// Prepares the SceneModel to be used in the views - func startup() { - self.articleIconSchemeHandler = ArticleIconSchemeHandler(sceneModel: self) - self.webViewProvider = WebViewProvider(articleIconSchemeHandler: self.articleIconSchemeHandler!) - - subscribeToAccountSyncErrors() - subscribeToToolbarChangeEvents() - } - - // MARK: Navigation API - - /// Goes to the next unread item found in Sidebar and Timeline order, top to bottom - func goToNextUnread() { - if !timelineModel.goToNextUnread() { - timelineModel.selectNextUnreadSubject.send(true) - sidebarModel.selectNextUnread.send() - } - } - - // MARK: Article Management API - - /// Marks all the articles in the Timeline as read - func markAllAsRead() { - timelineModel.markAllAsRead() - } - - /// Toggles the read status for the selected articles - func toggleReadStatusForSelectedArticles() { - timelineModel.toggleReadStatusForSelectedArticles() - } - - /// Toggles the star status for the selected articles - func toggleStarredStatusForSelectedArticles() { - timelineModel.toggleStarredStatusForSelectedArticles() - } - - /// Opens the selected article in an external browser - func openSelectedArticleInBrowser() { - timelineModel.openSelectedArticleInBrowser() - } - - /// Retrieves the article before the given article in the Timeline - func findPrevArticle(_ article: Article) -> Article? { - return timelineModel.findPrevArticle(article) - } - - /// Retrieves the article after the given article in the Timeline - func findNextArticle(_ article: Article) -> Article? { - return timelineModel.findNextArticle(article) - } - - /// Returns the article with the given articleID - func articleFor(_ articleID: String) -> Article? { - return timelineModel.articleFor(articleID) - } - -} - -// MARK: SidebarModelDelegate - -extension SceneModel: SidebarModelDelegate { - - func unreadCount(for feed: Feed) -> Int { - // TODO: Get the count from the timeline if Feed is the current timeline - return feed.unreadCount - } - -} - -// MARK: TimelineModelDelegate - -extension SceneModel: TimelineModelDelegate { - - var selectedFeedsPublisher: AnyPublisher<[Feed], Never>? { - return sidebarModel.selectedFeedsPublisher - } - - func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed) { - } - -} - -// MARK: Private - -private extension SceneModel { - - // MARK: Subscriptions - func subscribeToToolbarChangeEvents() { - guard let selectedArticlesPublisher = timelineModel.selectedArticlesPublisher else { return } - - NotificationCenter.default.publisher(for: .UnreadCountDidChange) - .compactMap { $0.object as? AccountManager } - .sink { [weak self] accountManager in - self?.updateNextUnreadButtonState(accountManager: accountManager) - }.store(in: &cancellables) - - let blankNotification = Notification(name: .StatusesDidChange) - let statusesDidChangePublisher = NotificationCenter.default.publisher(for: .StatusesDidChange).prepend(blankNotification) - - statusesDidChangePublisher - .combineLatest(selectedArticlesPublisher) - .sink { [weak self] _, selectedArticles in - self?.updateArticleButtonsState(selectedArticles: selectedArticles) - } - .store(in: &cancellables) - - statusesDidChangePublisher - .combineLatest(timelineModel.articlesSubject) - .sink { [weak self] _, articles in - self?.updateMarkAllAsReadButtonsState(articles: articles) - } - .store(in: &cancellables) - } - - func subscribeToAccountSyncErrors() { - NotificationCenter.default.publisher(for: .AccountsDidFailToSyncWithErrors) - .sink { [weak self] notification in - guard let syncErrors = notification.userInfo?[Account.UserInfoKey.syncErrors] as? [AccountSyncError] else { - return - } - self?.accountSyncErrors = syncErrors - }.store(in: &cancellables) - } - - // MARK: Button State Updates - - func updateNextUnreadButtonState(accountManager: AccountManager) { - if accountManager.unreadCount > 0 { - self.nextUnreadButtonState = false - } else { - self.nextUnreadButtonState = nil - } - } - - func updateMarkAllAsReadButtonsState(articles: [Article]) { - if articles.canMarkAllAsRead() { - markAllAsReadButtonState = false - } else { - markAllAsReadButtonState = nil - } - } - - func updateArticleButtonsState(selectedArticles: [Article]) { - guard !selectedArticles.isEmpty else { - readButtonState = nil - starButtonState = nil - openInBrowserButtonState = nil - shareButtonState = nil - return - } - - if selectedArticles.anyArticleIsUnread() { - readButtonState = true - } else if selectedArticles.anyArticleIsReadAndCanMarkUnread() { - readButtonState = false - } else { - readButtonState = nil - } - - if selectedArticles.anyArticleIsUnstarred() { - starButtonState = false - } else { - starButtonState = true - } - - if selectedArticles.count == 1, selectedArticles.first?.preferredLink != nil { - openInBrowserButtonState = true - shareButtonState = true - } else { - openInBrowserButtonState = nil - shareButtonState = nil - } - } - -} diff --git a/Multiplatform/Shared/SceneNavigationModel.swift b/Multiplatform/Shared/SceneNavigationModel.swift deleted file mode 100644 index f613210f7..000000000 --- a/Multiplatform/Shared/SceneNavigationModel.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// SceneNavigationModel.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 13/8/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation - - -class SceneNavigationModel: ObservableObject { - @Published var sheetToShow: SidebarSheets = .none { - didSet { - sheetToShow != .none ? (showSheet = true) : (showSheet = false) - } - } - @Published var showSheet = false - @Published var showShareSheet = false - @Published var showAccountSyncErrorAlert = false -} diff --git a/Multiplatform/Shared/SceneNavigationView.swift b/Multiplatform/Shared/SceneNavigationView.swift deleted file mode 100644 index 190164638..000000000 --- a/Multiplatform/Shared/SceneNavigationView.swift +++ /dev/null @@ -1,219 +0,0 @@ -// -// SceneNavigationView.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/28/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -#if os(macOS) -import AppKit -#endif - - -struct SceneNavigationView: View { - - @StateObject private var sceneModel = SceneModel() - @StateObject private var sceneNavigationModel = SceneNavigationModel() - - #if os(iOS) - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - #endif - - var body: some View { - NavigationView { - #if os(macOS) - SidebarContainerView() - .frame(minWidth: 100, idealWidth: 150, maxHeight: .infinity) - #else - SidebarContainerView() - #endif - - #if os(iOS) - if horizontalSizeClass != .compact { - TimelineContainerView() - } - #else - TimelineContainerView() - #endif - - ArticleContainerView() - } - .environmentObject(sceneModel) - .onAppear { - sceneModel.startup() - } - .onReceive(sceneModel.$accountSyncErrors) { errors in - if errors.count == 0 { - sceneNavigationModel.showAccountSyncErrorAlert = false - } else { - if errors.count > 1 { - sceneNavigationModel.showAccountSyncErrorAlert = true - } else { - sceneNavigationModel.sheetToShow = .fixCredentials - } - } - } - .sheet(isPresented: $sceneNavigationModel.showSheet, - onDismiss: { - sceneNavigationModel.sheetToShow = .none - sceneModel.accountSyncErrors = [] - }) { - if sceneNavigationModel.sheetToShow == .web { - AddWebFeedView(isPresented: $sceneNavigationModel.showSheet) - } - if sceneNavigationModel.sheetToShow == .folder { - AddFolderView(isPresented: $sceneNavigationModel.showSheet) - } - #if os(iOS) - if sceneNavigationModel.sheetToShow == .settings { - SettingsView() - } - #endif - if sceneNavigationModel.sheetToShow == .fixCredentials { - FixAccountCredentialView(accountSyncError: sceneModel.accountSyncErrors[0]) - } - } - .alert(isPresented: $sceneNavigationModel.showAccountSyncErrorAlert, content: { - #if os(macOS) - return Alert(title: Text("Account Sync Error"), - message: Text("The following accounts failed to sync: ") + Text(sceneModel.accountSyncErrors.map({ $0.account.nameForDisplay }).joined(separator: ", ")) + Text(". You can update credentials in Preferences"), - dismissButton: .default(Text("Dismiss"))) - #else - return Alert(title: Text("Account Sync Error"), - message: Text("The following accounts failed to sync: ") + Text(sceneModel.accountSyncErrors.map({ $0.account.nameForDisplay }).joined(separator: ", ")) + Text(". You can update credentials in Settings"), - primaryButton: .default(Text("Show Settings"), action: { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { - sceneNavigationModel.sheetToShow = .settings - }) - - }), - secondaryButton: .cancel(Text("Dismiss"))) - - #endif - }) - .toolbar { - - #if os(macOS) - ToolbarItem(placement: .navigation) { - Button { - NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) - } label: { - AppAssets.sidebarToggleImage - } - .help("Toggle Sidebar") - } - ToolbarItem() { - Menu { - Button("Add Web Feed", action: { sceneNavigationModel.sheetToShow = .web }) - Button("Add Reddit Feed", action: { }) - Button("Add Twitter Feed", action: { }) - Button("Add Folder", action: { sceneNavigationModel.sheetToShow = .folder}) - } label : { - AppAssets.addMenuImage - } - } - ToolbarItem { - Button { - AccountManager.shared.refreshAll(completion: nil) - - } label: { - AppAssets.refreshImage - } - .help("Refresh").padding(.trailing, 40) - } - ToolbarItem { - Button { - sceneModel.markAllAsRead() - } label: { - AppAssets.markAllAsReadImagePNG - .offset(y: 7) - } - .disabled(sceneModel.markAllAsReadButtonState == nil) - .help("Mark All as Read") - } -// ToolbarItem { -// MacSearchField() -// .frame(width: 200) -// } - ToolbarItem { - Button { - sceneModel.goToNextUnread() - } label: { - AppAssets.nextUnreadArticleImage - } - .disabled(sceneModel.nextUnreadButtonState == nil) - .help("Go to Next Unread").padding(.trailing, 40) - } - ToolbarItem { - Button { - sceneModel.toggleReadStatusForSelectedArticles() - } label: { - if sceneModel.readButtonState ?? false { - AppAssets.readClosedImage - } else { - AppAssets.readOpenImage - } - } - .disabled(sceneModel.readButtonState == nil) - .help(sceneModel.readButtonState ?? false ? "Mark as Unread" : "Mark as Read") - } - ToolbarItem { - Button { - sceneModel.toggleStarredStatusForSelectedArticles() - } label: { - if sceneModel.starButtonState ?? false { - AppAssets.starClosedImage - } else { - AppAssets.starOpenImage - } - } - .disabled(sceneModel.starButtonState == nil) - .help(sceneModel.starButtonState ?? false ? "Mark as Unstarred" : "Mark as Starred") - } - ToolbarItem { - Button { - } label: { - AppAssets.articleExtractorOff - } - .disabled(sceneModel.extractorButtonState == nil) - .help("Show Reader View") - } - ToolbarItem { - Button { - sceneModel.openSelectedArticleInBrowser() - } label: { - AppAssets.openInBrowserImage - } - .disabled(sceneModel.openInBrowserButtonState == nil) - .help("Open in Browser") - } - ToolbarItem { - ZStack { - if sceneNavigationModel.showShareSheet { - SharingServiceView(articles: sceneModel.selectedArticles, showing: $sceneNavigationModel.showShareSheet) - .frame(width: 20, height: 20) - } - Button { - sceneNavigationModel.showShareSheet = true - } label: { - AppAssets.shareImage - } - } - .disabled(sceneModel.shareButtonState == nil) - .help("Share") - } - #endif - } - } - -} - -struct NavigationView_Previews: PreviewProvider { - static var previews: some View { - SceneNavigationView() - .environmentObject(SceneModel()) - } -} diff --git a/Multiplatform/Shared/Sidebar/SidebarContainerView.swift b/Multiplatform/Shared/Sidebar/SidebarContainerView.swift deleted file mode 100644 index 4be6cdc71..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarContainerView.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// SidebarContainerView.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/28/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct SidebarContainerView: View { - - @Environment(\.undoManager) var undoManager - @EnvironmentObject private var sceneModel: SceneModel - - @State var sidebarItems = [SidebarItem]() - - var body: some View { - SidebarView(sidebarItems: $sidebarItems) - .modifier(SidebarToolbarModifier()) - .modifier(SidebarListStyleModifier()) - .environmentObject(sceneModel.sidebarModel) - .onAppear { - sceneModel.sidebarModel.undoManager = undoManager - } - .onReceive(sceneModel.sidebarModel.sidebarItemsPublisher!) { newItems in - withAnimation { - sidebarItems = newItems - } - } - } - -} - -struct SidebarContainerView_Previews: PreviewProvider { - static var previews: some View { - SidebarContainerView() - .environmentObject(SceneModel()) - } -} diff --git a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift deleted file mode 100644 index 21971884a..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift +++ /dev/null @@ -1,184 +0,0 @@ -// -// SidebarContextMenu.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/17/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import RSCore -import Account - -struct SidebarContextMenu: View { - - @Environment(\.undoManager) var undoManager - @Environment(\.openURL) var openURL - @EnvironmentObject private var sidebarModel: SidebarModel - @Binding var showInspector: Bool - var sidebarItem: SidebarItem - - - var body: some View { - // MARK: Account Context Menu - if sidebarItem.representedType == .account { - Button { - showInspector = true - } label: { - Text("Get Info") - #if os(iOS) - AppAssets.getInfoImage - #endif - } - Button { - sidebarModel.markAllAsReadInAccount.send(sidebarItem.represented as! Account) - } label: { - Text("Mark All As Read") - #if os(iOS) - AppAssets.markAllAsReadImage - #endif - } - } - - // MARK: Pseudofeed Context Menu - if sidebarItem.representedType == .pseudoFeed { - Button { - guard let feed = sidebarItem.feed else { - return - } - sidebarModel.markAllAsReadInFeed.send(feed) - } label: { - Text("Mark All As Read") - #if os(iOS) - AppAssets.markAllAsReadImage - #endif - } - } - - // MARK: Webfeed Context Menu - if sidebarItem.representedType == .webFeed { - Button { - showInspector = true - } label: { - Text("Get Info") - #if os(iOS) - AppAssets.getInfoImage - #endif - } - Button { - guard let feed = sidebarItem.feed else { - return - } - sidebarModel.markAllAsReadInFeed.send(feed) - } label: { - Text("Mark All As Read") - #if os(iOS) - AppAssets.markAllAsReadImage - #endif - } - Divider() - Button { - guard let homepage = (sidebarItem.feed as? WebFeed)?.homePageURL, - let url = URL(string: homepage) else { - return - } - openURL(url) - } label: { - Text("Open Home Page") - #if os(iOS) - AppAssets.openInBrowserImage - #endif - } - Divider() - Button { - guard let feedUrl = (sidebarItem.feed as? WebFeed)?.url else { - return - } - #if os(macOS) - URLPasteboardWriter.write(urlString: feedUrl, to: NSPasteboard.general) - #else - UIPasteboard.general.string = feedUrl - #endif - - } label: { - Text("Copy Feed URL") - #if os(iOS) - AppAssets.copyImage - #endif - } - Button { - guard let homepage = (sidebarItem.feed as? WebFeed)?.homePageURL else { - return - } - #if os(macOS) - URLPasteboardWriter.write(urlString: homepage, to: NSPasteboard.general) - #else - UIPasteboard.general.string = homepage - #endif - } label: { - Text("Copy Home Page URL") - #if os(iOS) - AppAssets.copyImage - #endif - } - Divider() - Button { - if AppDefaults.shared.sidebarConfirmDelete == false { - sidebarModel.deleteFromAccount.send(sidebarItem.feed!) - } else { - sidebarModel.sidebarItemToDelete = sidebarItem.feed! - sidebarModel.showDeleteConfirmation = true - } - } label: { - Text("Delete") - #if os(iOS) - AppAssets.deleteImage - #endif - } - } - - // MARK: Folder Context Menu - if sidebarItem.representedType == .folder { - Button { - showInspector = true - } label: { - Text("Get Info") - #if os(iOS) - AppAssets.getInfoImage - #endif - } - Button { - guard let feed = sidebarItem.feed else { - return - } - sidebarModel.markAllAsReadInFeed.send(feed) - } label: { - Text("Mark All As Read") - #if os(iOS) - AppAssets.markAllAsReadImage - #endif - } - - /* - You cannot select folder level items in b4. Delete is disabled for the time being. - */ - /* - Divider() - Button { - if AppDefaults.shared.sidebarConfirmDelete == false { - sidebarModel.deleteFromAccount.send(sidebarItem.feed!) - } else { - sidebarModel.sidebarContextMenuItem = sidebarItem.feed - sidebarModel.showDeleteConfirmation = true - } - } label: { - Text("Delete") - #if os(iOS) - AppAssets.deleteImage - #endif - } - */ - } - - } -} diff --git a/Multiplatform/Shared/Sidebar/SidebarExpandedContainers.swift b/Multiplatform/Shared/Sidebar/SidebarExpandedContainers.swift deleted file mode 100644 index 878be0408..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarExpandedContainers.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// SidebarExpandedContainers.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/30/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Combine -import Account - -struct SidebarExpandedContainers { - - var expandedTable = [ContainerIdentifier: Bool]() - - var data: Data { - get { - let encoder = PropertyListEncoder() - encoder.outputFormat = .binary - return (try? encoder.encode(expandedTable)) ?? Data() - } - set { - let decoder = PropertyListDecoder() - expandedTable = (try? decoder.decode([ContainerIdentifier: Bool].self, from: newValue)) ?? [ContainerIdentifier: Bool]() - } - } - - func contains(_ containerID: ContainerIdentifier) -> Bool { - return expandedTable.keys.contains(containerID) - } - - subscript(_ containerID: ContainerIdentifier) -> Bool { - get { - if let result = expandedTable[containerID] { - return result - } - switch containerID { - case .smartFeedController, .account: - return true - default: - return false - } - } - set(newValue) { - expandedTable[containerID] = newValue - } - } - -} diff --git a/Multiplatform/Shared/Sidebar/SidebarItem.swift b/Multiplatform/Shared/Sidebar/SidebarItem.swift deleted file mode 100644 index b4cda8772..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarItem.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// SidebarItem.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/29/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import RSCore -import Account - -public enum SidebarItemIdentifier: Hashable, Equatable { - case smartFeedController - case account(String) - case feed(FeedIdentifier) -} - -public enum RepresentedType { - case smartFeedController, webFeed, folder, pseudoFeed, account, unknown -} - -struct SidebarItem: Identifiable { - - var id: SidebarItemIdentifier - var represented: Any - var children: [SidebarItem] = [SidebarItem]() - - var unreadCount: Int - var nameForDisplay: String - - var feed: Feed? { - represented as? Feed - } - - var containerID: ContainerIdentifier? { - return (represented as? ContainerIdentifiable)?.containerID - } - - var representedType: RepresentedType { - switch type(of: represented) { - case is SmartFeedsController.Type: - return .smartFeedController - case is SmartFeed.Type: - return .pseudoFeed - case is UnreadFeed.Type: - return .pseudoFeed - case is WebFeed.Type: - return .webFeed - case is Folder.Type: - return .folder - case is Account.Type: - return .account - default: - return .unknown - } - } - - init(_ smartFeedsController: SmartFeedsController) { - self.id = .smartFeedController - self.represented = smartFeedsController - self.unreadCount = 0 - self.nameForDisplay = smartFeedsController.nameForDisplay - } - - init(_ account: Account) { - self.id = .account(account.accountID) - self.represented = account - self.unreadCount = account.unreadCount - self.nameForDisplay = account.nameForDisplay - } - - init(_ feed: Feed, unreadCount: Int) { - self.id = .feed(feed.feedID!) - self.represented = feed - self.unreadCount = unreadCount - self.nameForDisplay = feed.nameForDisplay - } - - /// Add a sidebar item to the child list - mutating func addChild(_ sidebarItem: SidebarItem) { - children.append(sidebarItem) - } - - /// Recursively visits each sidebar item. Return true when done visiting. - @discardableResult - func visit(_ block: (SidebarItem) -> Bool) -> Bool { - let stop = block(self) - if !stop { - for child in children { - if child.visit(block) { - break - } - } - } - return stop - } -} diff --git a/Multiplatform/Shared/Sidebar/SidebarItemView.swift b/Multiplatform/Shared/Sidebar/SidebarItemView.swift deleted file mode 100644 index b14e18a62..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarItemView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// SidebarItemView.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/29/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct SidebarItemView: View { - - @StateObject var feedIconImageLoader = FeedIconImageLoader() - @EnvironmentObject private var sidebarModel: SidebarModel - @State private var showInspector: Bool = false - var sidebarItem: SidebarItem - - var body: some View { - HStack { - #if os(macOS) - HStack { - if let image = feedIconImageLoader.image { - IconImageView(iconImage: image) - .frame(width: 20, height: 20, alignment: .center) - } - Text(verbatim: sidebarItem.nameForDisplay) - Spacer() - if sidebarItem.unreadCount > 0 { - UnreadCountView(count: sidebarItem.unreadCount) - } - } - #else - HStack(alignment: .top) { - if let image = feedIconImageLoader.image { - IconImageView(iconImage: image) - .frame(width: 20, height: 20) - } - Text(verbatim: sidebarItem.nameForDisplay) - } - Spacer() - if sidebarItem.unreadCount > 0 { - UnreadCountView(count: sidebarItem.unreadCount) - } - if sidebarItem.representedType == .webFeed || sidebarItem.representedType == .pseudoFeed { - Spacer() - .frame(width: 16) - } - #endif - } - .onAppear { - if let feed = sidebarItem.feed { - feedIconImageLoader.loadImage(for: feed) - } - }.contextMenu { - SidebarContextMenu(showInspector: $showInspector, sidebarItem: sidebarItem) - .environmentObject(sidebarModel) - } - .sheet(isPresented: $showInspector, onDismiss: { showInspector = false}) { - InspectorView(sidebarItem: sidebarItem) - } - } - -} diff --git a/Multiplatform/Shared/Sidebar/SidebarListStyleModifier.swift b/Multiplatform/Shared/Sidebar/SidebarListStyleModifier.swift deleted file mode 100644 index d564457e3..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarListStyleModifier.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// SidebarListStyleModifier.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct SidebarListStyleModifier: ViewModifier { - - #if os(iOS) - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - #endif - - @ViewBuilder func body(content: Content) -> some View { - #if os(macOS) - content - .listStyle(SidebarListStyle()) - #else - if horizontalSizeClass == .compact { - content - .listStyle(PlainListStyle()) - } else { - content - .listStyle(SidebarListStyle()) - } - #endif - - } - -} diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift deleted file mode 100644 index da8e36de7..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ /dev/null @@ -1,357 +0,0 @@ -// -// SidebarModel.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/28/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import Combine -import RSCore -import Account -import Articles - -protocol SidebarModelDelegate: AnyObject { - func unreadCount(for: Feed) -> Int -} - -class SidebarModel: ObservableObject, UndoableCommandRunner { - - @Published var selectedFeedIdentifiers = Set() - @Published var selectedFeedIdentifier: FeedIdentifier? = .none - @Published var isReadFiltered = false - @Published var expandedContainers = SidebarExpandedContainers() - @Published var showDeleteConfirmation: Bool = false - - weak var delegate: SidebarModelDelegate? - - var sidebarItemsPublisher: AnyPublisher<[SidebarItem], Never>? - var selectedFeedsPublisher: AnyPublisher<[Feed], Never>? - - var selectNextUnread = PassthroughSubject() - var markAllAsReadInFeed = PassthroughSubject() - var markAllAsReadInAccount = PassthroughSubject() - var deleteFromAccount = PassthroughSubject() - - var sidebarItemToDelete: Feed? - - private var cancellables = Set() - - var undoManager: UndoManager? - var undoableCommands = [UndoableCommand]() - - init(delegate: SidebarModelDelegate) { - self.delegate = delegate - subscribeToSelectedFeedChanges() - subscribeToRebuildSidebarItemsEvents() - subscribeToNextUnread() - subscribeToMarkAllAsReadInFeed() - subscribeToMarkAllAsReadInAccount() - subscribeToDeleteFromAccount() - } - -} - - -extension SidebarModel { - - func countOfFeedsToDelete() -> Int { - var selectedFeeds = selectedFeedIdentifiers - - if sidebarItemToDelete != nil { - selectedFeeds.insert(sidebarItemToDelete!.feedID!) - } - - return selectedFeeds.count - } - - - func namesOfFeedsToDelete() -> String { - var selectedFeeds = selectedFeedIdentifiers - - if sidebarItemToDelete != nil { - selectedFeeds.insert(sidebarItemToDelete!.feedID!) - } - - let feeds: [Feed] = selectedFeeds - .compactMap({ AccountManager.shared.existingFeed(with: $0) }) - - return feeds - .map({ $0.nameForDisplay }) - .joined(separator: ", ") - } - -} - -// MARK: Private - -private extension SidebarModel { - - // MARK: Subscriptions - - func subscribeToSelectedFeedChanges() { - - let selectedFeedIdentifersPublisher = $selectedFeedIdentifiers - .map { [weak self] feedIDs -> [Feed] in - return feedIDs.compactMap { self?.findFeed($0) } - } - - - let selectedFeedIdentiferPublisher = $selectedFeedIdentifier - .compactMap { [weak self] feedID -> [Feed]? in - if let feedID = feedID, let feed = self?.findFeed(feedID) { - return [feed] - } else { - return nil - } - } - - selectedFeedsPublisher = selectedFeedIdentifersPublisher - .merge(with: selectedFeedIdentiferPublisher) - .removeDuplicates(by: { previousFeeds, currentFeeds in - return previousFeeds.elementsEqual(currentFeeds, by: { $0.feedID == $1.feedID }) - }) - .share() - .eraseToAnyPublisher() - } - - func subscribeToRebuildSidebarItemsEvents() { - guard let selectedFeedsPublisher = selectedFeedsPublisher else { return } - - let chidrenDidChangePublisher = NotificationCenter.default.publisher(for: .ChildrenDidChange) - let batchUpdateDidPerformPublisher = NotificationCenter.default.publisher(for: .BatchUpdateDidPerform) - let displayNameDidChangePublisher = NotificationCenter.default.publisher(for: .DisplayNameDidChange) - let accountStateDidChangePublisher = NotificationCenter.default.publisher(for: .AccountStateDidChange) - let userDidAddAccountPublisher = NotificationCenter.default.publisher(for: .UserDidAddAccount) - let userDidDeleteAccountPublisher = NotificationCenter.default.publisher(for: .UserDidDeleteAccount) - let unreadCountDidInitializePublisher = NotificationCenter.default.publisher(for: .UnreadCountDidInitialize) - let unreadCountDidChangePublisher = NotificationCenter.default.publisher(for: .UnreadCountDidChange) - - let sidebarRebuildPublishers = chidrenDidChangePublisher.merge(with: batchUpdateDidPerformPublisher, - displayNameDidChangePublisher, - accountStateDidChangePublisher, - userDidAddAccountPublisher, - userDidDeleteAccountPublisher, - unreadCountDidInitializePublisher, - unreadCountDidChangePublisher) - - let kickStarter = Notification(name: Notification.Name(rawValue: "Kick Starter")) - - sidebarItemsPublisher = sidebarRebuildPublishers - .prepend(kickStarter) - .debounce(for: .milliseconds(500), scheduler: RunLoop.main) - .combineLatest($isReadFiltered, selectedFeedsPublisher) - .compactMap { [weak self] _, readFilter, selectedFeeds in - self?.rebuildSidebarItems(isReadFiltered: readFilter, selectedFeeds: selectedFeeds) - } - .share() - .eraseToAnyPublisher() - } - - func subscribeToNextUnread() { - guard let sidebarItemsPublisher = sidebarItemsPublisher, let selectedFeedsPublisher = selectedFeedsPublisher else { return } - - selectNextUnread - .withLatestFrom(sidebarItemsPublisher, selectedFeedsPublisher) - .compactMap { [weak self] (sidebarItems, selectedFeeds) in - return self?.nextUnread(sidebarItems: sidebarItems, selectedFeeds: selectedFeeds) - } - .sink { [weak self] nextFeedID in - self?.select(nextFeedID) - } - .store(in: &cancellables) - } - - func subscribeToMarkAllAsReadInFeed() { - guard let selectedFeedsPublisher = selectedFeedsPublisher else { return } - - markAllAsReadInFeed - .withLatestFrom(selectedFeedsPublisher, resultSelector: { givenFeed, selectedFeeds -> [Feed] in - if selectedFeeds.contains(where: { $0.feedID == givenFeed.feedID }) { - return selectedFeeds - } else { - return [givenFeed] - } - }) - .map { feeds in - var articles = [Article]() - for feed in feeds { - articles.append(contentsOf: (try? feed.fetchUnreadArticles()) ?? Set
()) - } - return articles - } - .sink { [weak self] allArticles in - self?.markAllAsRead(allArticles) - } - .store(in: &cancellables) - } - - func subscribeToMarkAllAsReadInAccount() { - markAllAsReadInAccount - .map { account in - var articles = [Article]() - for feed in account.flattenedWebFeeds() { - articles.append(contentsOf: (try? feed.fetchUnreadArticles()) ?? Set
()) - } - return articles - } - .sink { [weak self] articles in - self?.markAllAsRead(articles) - } - .store(in: &cancellables) - } - - func subscribeToDeleteFromAccount() { - guard let selectedFeedsPublisher = selectedFeedsPublisher else { return } - - deleteFromAccount - .withLatestFrom(selectedFeedsPublisher.prepend([Feed]()), resultSelector: { givenFeed, selectedFeeds -> [Feed] in - if selectedFeeds.contains(where: { $0.feedID == givenFeed.feedID }) { - return selectedFeeds - } else { - return [givenFeed] - } - }) - .sink { feeds in - for feed in feeds { - if let webFeed = feed as? WebFeed { - guard let account = webFeed.account, - let containerID = account.containerID, - let container = AccountManager.shared.existingContainer(with: containerID) else { - return - } - account.removeWebFeed(webFeed, from: container, completion: { result in - switch result { - case .success: - break - case .failure(let err): - print(err) - } - }) - } - if let folder = feed as? Folder { - folder.account?.removeFolder(folder) { _ in } - } - } - } - .store(in: &cancellables) - } - - /// Marks provided artices as read. - /// - Parameter articles: An array of `Article`s. - /// - Warning: An `UndoManager` is created here as the `Environment`'s undo manager appears to be `nil`. - func markAllAsRead(_ articles: [Article]) { - guard let undoManager = undoManager, - let markAsReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { - return - } - runCommand(markAsReadCommand) - } - - // MARK: Sidebar Building - - func sort(_ folders: Set) -> [Folder] { - return folders.sorted(by: { $0.nameForDisplay.localizedStandardCompare($1.nameForDisplay) == .orderedAscending }) - } - - func sort(_ feeds: Set) -> [Feed] { - return feeds.sorted(by: { $0.nameForDisplay.localizedStandardCompare($1.nameForDisplay) == .orderedAscending }) - } - - func rebuildSidebarItems(isReadFiltered: Bool, selectedFeeds: [Feed]) -> [SidebarItem] { - var items = [SidebarItem]() - guard let delegate = delegate else { return items } - - var smartFeedControllerItem = SidebarItem(SmartFeedsController.shared) - for feed in SmartFeedsController.shared.smartFeeds { -// It looks like SwiftUI loses its mind when the last element in a section is removed. Don't filter -// the smartfeeds yet or we crash about everytime because Starred is almost always filtered -// if !isReadFiltered || feed.unreadCount > 0 { - smartFeedControllerItem.addChild(SidebarItem(feed, unreadCount: delegate.unreadCount(for: feed))) -// } - } - items.append(smartFeedControllerItem) - - let selectedFeedIDs = Set(selectedFeeds.map { $0.feedID }) - - for account in AccountManager.shared.sortedActiveAccounts { - var accountItem = SidebarItem(account) - - for webFeed in sort(account.topLevelWebFeeds) { - if !isReadFiltered || !(webFeed.unreadCount < 1 && !selectedFeedIDs.contains(webFeed.feedID)) { - accountItem.addChild(SidebarItem(webFeed, unreadCount: delegate.unreadCount(for: webFeed))) - } - } - - for folder in sort(account.folders ?? Set()) { - if !isReadFiltered || !(folder.unreadCount < 1 && !selectedFeedIDs.contains(folder.feedID)) { - var folderItem = SidebarItem(folder, unreadCount: delegate.unreadCount(for: folder)) - for webFeed in sort(folder.topLevelWebFeeds) { - if !isReadFiltered || !(webFeed.unreadCount < 1 && !selectedFeedIDs.contains(webFeed.feedID)) { - folderItem.addChild(SidebarItem(webFeed, unreadCount: delegate.unreadCount(for: webFeed))) - } - } - accountItem.addChild(folderItem) - } - } - - items.append(accountItem) - } - - return items - } - - // MARK: - - func findFeed(_ feedID: FeedIdentifier) -> Feed? { - switch feedID { - case .smartFeed: - return SmartFeedsController.shared.find(by: feedID) - default: - return AccountManager.shared.existingFeed(with: feedID) - } - } - - func nextUnread(sidebarItems: [SidebarItem], selectedFeeds: [Feed]) -> FeedIdentifier? { - guard let startFeed = selectedFeeds.first ?? sidebarItems.first?.children.first?.feed else { return nil } - - if let feedID = nextUnread(sidebarItems: sidebarItems, startingAt: startFeed) { - return feedID - } else { - return nextUnread(sidebarItems: sidebarItems, startingAt: nil) - } - } - - @discardableResult - func nextUnread(sidebarItems: [SidebarItem], startingAt: Feed?) -> FeedIdentifier? { - var foundStartFeed = startingAt == nil ? true : false - var nextSidebarItem: SidebarItem? = nil - - for section in sidebarItems { - if nextSidebarItem == nil { - section.visit { sidebarItem in - if !foundStartFeed && sidebarItem.feed?.feedID == startingAt?.feedID { - foundStartFeed = true - return false - } - if foundStartFeed && sidebarItem.unreadCount > 0 { - nextSidebarItem = sidebarItem - return true - } - return false - } - } - } - - return nextSidebarItem?.feed?.feedID - } - - func select(_ feedID: FeedIdentifier) { - selectedFeedIdentifiers = Set([feedID]) - selectedFeedIdentifier = feedID - } - - - -} diff --git a/Multiplatform/Shared/Sidebar/SidebarToolbarModel.swift b/Multiplatform/Shared/Sidebar/SidebarToolbarModel.swift deleted file mode 100644 index d5b3ad00d..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarToolbarModel.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// SidebarToolbarModel.swift -// NetNewsWire -// -// Created by Stuart Breckenridge on 4/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation - -enum SidebarSheets { - case none, web, twitter, reddit, folder, settings, fixCredentials -} - -class SidebarToolbarModel: ObservableObject { - - @Published var showSheet: Bool = false - @Published var sheetToShow: SidebarSheets = .none { - didSet { - sheetToShow != .none ? (showSheet = true) : (showSheet = false) - } - } - @Published var showAddSheet: Bool = false - -} diff --git a/Multiplatform/Shared/Sidebar/SidebarToolbarModifier.swift b/Multiplatform/Shared/Sidebar/SidebarToolbarModifier.swift deleted file mode 100644 index b0096513e..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarToolbarModifier.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// SidebarToolbarModifier.swift -// Multiplatform iOS -// -// Created by Stuart Breckenridge on 30/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct SidebarToolbarModifier: ViewModifier { - - @EnvironmentObject private var refreshProgress: RefreshProgressModel - @EnvironmentObject private var defaults: AppDefaults - @EnvironmentObject private var sidebarModel: SidebarModel - @StateObject private var viewModel = SidebarToolbarModel() - - @ViewBuilder func body(content: Content) -> some View { - #if os(iOS) - content - .toolbar { - - ToolbarItem(placement: .primaryAction) { - Button { - withAnimation { - sidebarModel.isReadFiltered.toggle() - } - } label: { - if sidebarModel.isReadFiltered { - AppAssets.filterActiveImage.font(.title3) - } else { - AppAssets.filterInactiveImage.font(.title3) - } - } - .help(sidebarModel.isReadFiltered ? "Show Read Feeds" : "Filter Read Feeds") - } - - ToolbarItem(placement: .bottomBar) { - Button { - viewModel.sheetToShow = .settings - } label: { - AppAssets.settingsImage.font(.title3) - } - .help("Settings") - } - - ToolbarItem(placement: .bottomBar) { - Spacer() - } - - ToolbarItem(placement: .bottomBar) { - switch refreshProgress.state { - case .refreshProgress(let progress): - ProgressView(value: progress) - .frame(width: 100) - case .lastRefreshDateText(let text): - Text(text) - .lineLimit(1) - .font(.caption) - .foregroundColor(.secondary) - case .none: - EmptyView() - } - } - - ToolbarItem(placement: .bottomBar) { - Spacer() - } - - ToolbarItem(placement: .bottomBar, content: { - Menu(content: { - Button { viewModel.sheetToShow = .web } label: { Text("Add Web Feed") } - Button { viewModel.sheetToShow = .twitter } label: { Text("Add Twitter Feed") } - Button { viewModel.sheetToShow = .reddit } label: { Text("Add Reddit Feed") } - Button { viewModel.sheetToShow = .folder } label: { Text("Add Folder") } - }, label: { - AppAssets.addMenuImage.font(.title3) - }) - }) - - } - .sheet(isPresented: $viewModel.showSheet, onDismiss: { viewModel.sheetToShow = .none }) { - if viewModel.sheetToShow == .web { - AddWebFeedView(isPresented: $viewModel.showSheet) - } - if viewModel.sheetToShow == .folder { - AddFolderView(isPresented: $viewModel.showSheet) - } - if viewModel.sheetToShow == .settings { - SettingsView() - .preferredColorScheme(AppDefaults.userInterfaceColorScheme) - } - } - #else - content - .toolbar { - ToolbarItem { - Spacer() - } - } - #endif - } -} - - diff --git a/Multiplatform/Shared/Sidebar/SidebarView.swift b/Multiplatform/Shared/Sidebar/SidebarView.swift deleted file mode 100644 index 97cfe801f..000000000 --- a/Multiplatform/Shared/Sidebar/SidebarView.swift +++ /dev/null @@ -1,237 +0,0 @@ -// -// SidebarView.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/29/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct SidebarView: View { - - @Binding var sidebarItems: [SidebarItem] - - @EnvironmentObject private var refreshProgress: RefreshProgressModel - @EnvironmentObject private var sceneModel: SceneModel - @EnvironmentObject private var sidebarModel: SidebarModel - - // I had to comment out SceneStorage because it blows up if used on macOS - // @SceneStorage("expandedContainers") private var expandedContainerData = Data() - - private let threshold: CGFloat = 80 - @State private var previousScrollOffset: CGFloat = 0 - @State private var scrollOffset: CGFloat = 0 - @State var pulling: Bool = false - @State var refreshing: Bool = false - - var body: some View { - #if os(macOS) - VStack { - HStack { - Spacer() - Button (action: { - withAnimation { - sidebarModel.isReadFiltered.toggle() - } - }, label: { - if sidebarModel.isReadFiltered { - AppAssets.filterActiveImage - } else { - AppAssets.filterInactiveImage - } - }) - .padding(.top, 8).padding(.trailing) - .buttonStyle(PlainButtonStyle()) - .help(sidebarModel.isReadFiltered ? "Show Read Feeds" : "Filter Read Feeds") - } - List(selection: $sidebarModel.selectedFeedIdentifiers) { - rows - } - if case .refreshProgress(let percent) = refreshProgress.state { - HStack(alignment: .center) { - Spacer() - ProgressView(value: percent).frame(width: 100) - Spacer() - } - .padding(8) - .background(Color(NSColor.windowBackgroundColor)) - .frame(height: 30) - .animation(.easeInOut(duration: 0.5)) - .transition(.move(edge: .bottom)) - } - } - .alert(isPresented: $sidebarModel.showDeleteConfirmation, content: { - Alert(title: sidebarModel.countOfFeedsToDelete() > 1 ? - (Text("Delete multiple items?")) : - (Text("Delete \(sidebarModel.namesOfFeedsToDelete())?")), - message: Text("Are you sure you wish to delete \(sidebarModel.namesOfFeedsToDelete())?"), - primaryButton: .destructive(Text("Delete"), - action: { - sidebarModel.deleteFromAccount.send(sidebarModel.sidebarItemToDelete!) - sidebarModel.sidebarItemToDelete = nil - sidebarModel.selectedFeedIdentifiers.removeAll() - sidebarModel.showDeleteConfirmation = false - }), - secondaryButton: .cancel(Text("Cancel"), action: { - sidebarModel.sidebarItemToDelete = nil - sidebarModel.showDeleteConfirmation = false - })) - }) - #else - ZStack(alignment: .top) { - List { - rows - } - .background(RefreshFixedView()) - .navigationTitle(Text("Feeds")) - .onPreferenceChange(RefreshKeyTypes.PrefKey.self) { values in - refreshLogic(values: values) - } - if pulling { - ProgressView().offset(y: -40) - } - } - .alert(isPresented: $sidebarModel.showDeleteConfirmation, content: { - Alert(title: sidebarModel.countOfFeedsToDelete() > 1 ? - (Text("Delete multiple items?")) : - (Text("Delete \(sidebarModel.namesOfFeedsToDelete())?")), - message: Text("Are you sure you wish to delete \(sidebarModel.namesOfFeedsToDelete())?"), - primaryButton: .destructive(Text("Delete"), - action: { - sidebarModel.deleteFromAccount.send(sidebarModel.sidebarItemToDelete!) - sidebarModel.sidebarItemToDelete = nil - sidebarModel.selectedFeedIdentifiers.removeAll() - sidebarModel.showDeleteConfirmation = false - }), - secondaryButton: .cancel(Text("Cancel"), action: { - sidebarModel.sidebarItemToDelete = nil - sidebarModel.showDeleteConfirmation = false - })) - }) - #endif - -// .onAppear { -// expandedContainers.data = expandedContainerData -// } -// .onReceive(expandedContainers.objectDidChange) { -// expandedContainerData = expandedContainers.data -// } - } - - func refreshLogic(values: [RefreshKeyTypes.PrefData]) { - DispatchQueue.main.async { - let movingBounds = values.first { $0.vType == .movingView }?.bounds ?? .zero - let fixedBounds = values.first { $0.vType == .fixedView }?.bounds ?? .zero - scrollOffset = movingBounds.minY - fixedBounds.minY - - // Crossing the threshold on the way down, we start the refresh process - if !pulling && (scrollOffset > threshold && previousScrollOffset <= threshold) { - pulling = true - AccountManager.shared.refreshAll() - } - - // Crossing the threshold on the way UP, we end the refresh - if pulling && previousScrollOffset > threshold && scrollOffset <= threshold { - pulling = false - } - - // Update last scroll offset - self.previousScrollOffset = self.scrollOffset - } - } - - struct RefreshFixedView: View { - var body: some View { - GeometryReader { proxy in - Color.clear.preference(key: RefreshKeyTypes.PrefKey.self, value: [RefreshKeyTypes.PrefData(vType: .fixedView, bounds: proxy.frame(in: .global))]) - } - } - } - - struct RefreshKeyTypes { - enum ViewType: Int { - case movingView - case fixedView - } - - struct PrefData: Equatable { - let vType: ViewType - let bounds: CGRect - } - - struct PrefKey: PreferenceKey { - static var defaultValue: [PrefData] = [] - - static func reduce(value: inout [PrefData], nextValue: () -> [PrefData]) { - value.append(contentsOf: nextValue()) - } - - typealias Value = [PrefData] - } - } - - var rows: some View { - ForEach(sidebarItems) { sidebarItem in - if let containerID = sidebarItem.containerID { - DisclosureGroup(isExpanded: $sidebarModel.expandedContainers[containerID]) { - ForEach(sidebarItem.children) { sidebarItem in - if let containerID = sidebarItem.containerID { - DisclosureGroup(isExpanded: $sidebarModel.expandedContainers[containerID]) { - ForEach(sidebarItem.children) { sidebarItem in - SidebarItemNavigation(sidebarItem: sidebarItem) - } - } label: { - SidebarItemNavigation(sidebarItem: sidebarItem) - } - } else { - SidebarItemNavigation(sidebarItem: sidebarItem) - } - } - } label: { - #if os(macOS) - SidebarItemView(sidebarItem: sidebarItem) - .padding(.leading, 4) - .environmentObject(sidebarModel) - #else - if sidebarItem.representedType == .smartFeedController { - GeometryReader { proxy in - SidebarItemView(sidebarItem: sidebarItem) - .preference(key: RefreshKeyTypes.PrefKey.self, value: [RefreshKeyTypes.PrefData(vType: .movingView, bounds: proxy.frame(in: .global))]) - .environmentObject(sidebarModel) - } - } else { - SidebarItemView(sidebarItem: sidebarItem) - .environmentObject(sidebarModel) - } - #endif - } - } - } - } - - struct SidebarItemNavigation: View { - - @EnvironmentObject private var sidebarModel: SidebarModel - var sidebarItem: SidebarItem - - var body: some View { - #if os(macOS) - SidebarItemView(sidebarItem: sidebarItem) - .tag(sidebarItem.feed!.feedID!) - #else - ZStack { - SidebarItemView(sidebarItem: sidebarItem) - NavigationLink(destination: TimelineContainerView(), - tag: sidebarItem.feed!.feedID!, - selection: $sidebarModel.selectedFeedIdentifier) { - EmptyView() - }.buttonStyle(PlainButtonStyle()) - } - #endif - } - - } - -} diff --git a/Multiplatform/Shared/Sidebar/UnreadCountView.swift b/Multiplatform/Shared/Sidebar/UnreadCountView.swift deleted file mode 100644 index 72cf9d5ec..000000000 --- a/Multiplatform/Shared/Sidebar/UnreadCountView.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// UnreadCountView.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/29/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct UnreadCountView: View { - - var count: Int - - var body: some View { - Text(verbatim: String(count)) - .font(.caption) - .fontWeight(.bold) - .padding(.horizontal, 7) - .padding(.vertical, 1) - .background(AppAssets.sidebarUnreadCountBackground) - .foregroundColor(AppAssets.sidebarUnreadCountForeground) - .cornerRadius(8) - } -} - -struct UnreadCountView_Previews: PreviewProvider { - static var previews: some View { - UnreadCountView(count: 123) - } -} diff --git a/Multiplatform/Shared/SwiftUI Extensions/HiddenModifier.swift b/Multiplatform/Shared/SwiftUI Extensions/HiddenModifier.swift deleted file mode 100644 index c59d23f59..000000000 --- a/Multiplatform/Shared/SwiftUI Extensions/HiddenModifier.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// HiddenModifier.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/12/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -extension View { - func hidden(_ hide: Bool) -> some View { - Group { - if hide { - self.hidden() - } else { - self - } - } - } -} diff --git a/Multiplatform/Shared/SwiftUI Extensions/Image-Extensions.swift b/Multiplatform/Shared/SwiftUI Extensions/Image-Extensions.swift deleted file mode 100644 index 517991676..000000000 --- a/Multiplatform/Shared/SwiftUI Extensions/Image-Extensions.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Image-Extensions.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/1/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import RSCore - -extension Image { - - init(rsImage: RSImage) { - #if os(macOS) - self = Image(nsImage: rsImage) - #endif - #if os(iOS) - self = Image(uiImage: rsImage) - #endif - } - -} diff --git a/Multiplatform/Shared/SwiftUI Extensions/PreferredColorSchemeModifier.swift b/Multiplatform/Shared/SwiftUI Extensions/PreferredColorSchemeModifier.swift deleted file mode 100644 index ba9a04d36..000000000 --- a/Multiplatform/Shared/SwiftUI Extensions/PreferredColorSchemeModifier.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// PreferredColorSchemeModifier.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/3/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct PreferredColorSchemeModifier: ViewModifier { - - var preferredColorScheme: UserInterfaceColorPalette - - @ViewBuilder - func body(content: Content) -> some View { - switch preferredColorScheme { - case .automatic: - content.preferredColorScheme(nil) - case .dark: - content.preferredColorScheme(.dark) - case .light: - content.preferredColorScheme(.light) - } - } - -} diff --git a/Multiplatform/Shared/Timeline/TimelineContainerView.swift b/Multiplatform/Shared/Timeline/TimelineContainerView.swift deleted file mode 100644 index 70f8de1b8..000000000 --- a/Multiplatform/Shared/Timeline/TimelineContainerView.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// TimelineContainerView.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/30/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct TimelineContainerView: View { - - @Environment(\.undoManager) var undoManager - @EnvironmentObject private var sceneModel: SceneModel - - @State private var timelineItems = TimelineItems() - @State private var isReadFiltered: Bool? = nil - - var body: some View { - TimelineView(timelineItems: $timelineItems, isReadFiltered: $isReadFiltered) - .modifier(TimelineToolbarModifier()) - .environmentObject(sceneModel.timelineModel) - .onAppear { - sceneModel.timelineModel.undoManager = undoManager - } - .onReceive(sceneModel.timelineModel.readFilterAndFeedsPublisher!) { (_, filtered) in - isReadFiltered = filtered - } - .onReceive(sceneModel.timelineModel.timelineItemsSelectPublisher!) { (items, selectTimelineItemID) in - timelineItems = items - if let selectID = selectTimelineItemID { - #if os(macOS) - sceneModel.timelineModel.selectedTimelineItemIDs = Set([selectID]) - #else - sceneModel.timelineModel.selectedTimelineItemID = selectID - #endif - } - } - .onReceive(sceneModel.timelineModel.articleStatusChangePublisher!) { articleIDs in - articleIDs.forEach { articleID in - if let position = timelineItems.index[articleID] { - timelineItems.items[position].updateStatus() - } - } - } - } - -} diff --git a/Multiplatform/Shared/Timeline/TimelineContextMenu.swift b/Multiplatform/Shared/Timeline/TimelineContextMenu.swift deleted file mode 100644 index 96e49c9e0..000000000 --- a/Multiplatform/Shared/Timeline/TimelineContextMenu.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// TimelineContextMenu.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/17/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct TimelineContextMenu: View { - - @EnvironmentObject private var timelineModel: TimelineModel - var timelineItem: TimelineItem - - var body: some View { - - if timelineModel.canMarkIndicatedArticlesAsRead(timelineItem) { - Button { - timelineModel.markIndicatedArticlesAsRead(timelineItem) - } label: { - Text("Mark as Read") - #if os(iOS) - AppAssets.readOpenImage - #endif - } - } - - if timelineModel.canMarkIndicatedArticlesAsUnread(timelineItem) { - Button { - timelineModel.markIndicatedArticlesAsUnread(timelineItem) - } label: { - Text("Mark as Unread") - #if os(iOS) - AppAssets.readClosedImage - #endif - } - } - - if timelineModel.canMarkIndicatedArticlesAsStarred(timelineItem) { - Button { - timelineModel.markIndicatedArticlesAsStarred(timelineItem) - } label: { - Text("Mark as Starred") - #if os(iOS) - AppAssets.starClosedImage - #endif - } - } - - if timelineModel.canMarkIndicatedArticlesAsUnstarred(timelineItem) { - Button { - timelineModel.markIndicatedArticlesAsUnstarred(timelineItem) - } label: { - Text("Mark as Unstarred") - #if os(iOS) - AppAssets.starOpenImage - #endif - } - } - - if timelineModel.canMarkAboveAsRead(timelineItem) { - Button { - timelineModel.markAboveAsRead(timelineItem) - } label: { - Text("Mark Above as Read") - #if os(iOS) - AppAssets.markAboveAsReadImage - #endif - } - } - - if timelineModel.canMarkBelowAsRead(timelineItem) { - Button { - timelineModel.markBelowAsRead(timelineItem) - } label: { - Text("Mark Below As Read") - #if os(iOS) - AppAssets.markBelowAsReadImage - #endif - } - } - - if timelineModel.canMarkAllAsReadInWebFeed(timelineItem) { - Divider() - Button { - timelineModel.markAllAsReadInWebFeed(timelineItem) - } label: { - Text("Mark All as Read in “\(timelineItem.article.webFeed?.nameForDisplay ?? "")”") - #if os(iOS) - AppAssets.markAllAsReadImage - #endif - } - } - - if timelineModel.canOpenIndicatedArticleInBrowser(timelineItem) { - Divider() - Button { - timelineModel.openIndicatedArticleInBrowser(timelineItem) - } label: { - Text("Open in Browser") - #if os(iOS) - AppAssets.openInBrowserImage - #endif - } - } - - } -} diff --git a/Multiplatform/Shared/Timeline/TimelineItem.swift b/Multiplatform/Shared/Timeline/TimelineItem.swift deleted file mode 100644 index 77f5eedf6..000000000 --- a/Multiplatform/Shared/Timeline/TimelineItem.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// TimelineItem.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/30/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Articles - -enum TimelineItemStatus { - case showStar - case showUnread - case showNone -} - -struct TimelineItem: Identifiable { - - var id: String - var position: Int - var article: Article - - var status: TimelineItemStatus = .showNone - var truncatedTitle: String - var truncatedSummary: String - var byline: String - var dateTimeString: String - - init(position: Int, article: Article) { - self.id = article.articleID - self.position = position - self.article = article - self.byline = article.webFeed?.nameForDisplay ?? "" - self.dateTimeString = ArticleStringFormatter.dateString(article.logicalDatePublished) - self.truncatedTitle = ArticleStringFormatter.truncatedTitle(article) - self.truncatedSummary = ArticleStringFormatter.truncatedSummary(article) - updateStatus() - } - - var isReadOnly: Bool { - return article.status.read == true && article.status.starred == false - } - - mutating func updateStatus() { - if article.status.starred == true { - status = .showStar - } else { - if article.status.read == false { - status = .showUnread - } else { - status = .showNone - } - } - } - - func numberOfTitleLines(width: CGFloat) -> Int { - guard !truncatedTitle.isEmpty else { return 0 } - - #if os(macOS) - let descriptor = NSFont.preferredFont(forTextStyle: .body).fontDescriptor.withSymbolicTraits(.bold) - guard let font = NSFont(descriptor: descriptor, size: 0) else { return 0 } - #else - guard let descriptor = UIFont.preferredFont(forTextStyle: .body).fontDescriptor.withSymbolicTraits(.traitBold) else { return 0 } - let font = UIFont(descriptor: descriptor, size: 0) - #endif - - let lines = Int(AppDefaults.shared.timelineNumberOfLines) - let sizeInfo = TimelineTextSizer.size(for: truncatedTitle, font: font, numberOfLines: lines, width: adjustedWidth(width)) - return sizeInfo.numberOfLinesUsed - } - - func numberOfSummaryLines(width: CGFloat, titleLines: Int) -> Int { - guard !truncatedSummary.isEmpty else { return 0 } - - let remainingLines = Int(AppDefaults.shared.timelineNumberOfLines) - titleLines - guard remainingLines > 0 else { return 0 } - - #if os(macOS) - let font = NSFont.preferredFont(forTextStyle: .body) - #else - let font = UIFont.preferredFont(forTextStyle: .body) - #endif - - let sizeInfo = TimelineTextSizer.size(for: truncatedSummary, font: font, numberOfLines: remainingLines, width: adjustedWidth(width)) - return sizeInfo.numberOfLinesUsed - } - -} - -private extension TimelineItem { - - // This clearly isn't correct yet, but it gets us close enough for now. -Maurice - func adjustedWidth(_ width: CGFloat) -> Int { - return Int(width - CGFloat(AppDefaults.shared.timelineIconDimensions + 64)) - } - -} diff --git a/Multiplatform/Shared/Timeline/TimelineItemStatusView.swift b/Multiplatform/Shared/Timeline/TimelineItemStatusView.swift deleted file mode 100644 index 540c35f04..000000000 --- a/Multiplatform/Shared/Timeline/TimelineItemStatusView.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// TimelineItemStatusView.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/1/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct TimelineItemStatusView: View { - - var selected: Bool - var status: TimelineItemStatus - - var statusView: some View { - ZStack { - Spacer().frame(width: 12) - switch status { - case .showUnread: - if selected { - AppAssets.timelineUnreadSelected - .resizable() - .frame(width: 8, height: 8, alignment: .center) - .padding(.all, 2) - } else { - AppAssets.timelineUnread - .resizable() - .frame(width: 8, height: 8, alignment: .center) - .padding(.all, 2) - } - case .showStar: - AppAssets.timelineStarred - .resizable() - .frame(width: 10, height: 10, alignment: .center) - case .showNone: - AppAssets.timelineUnread - .resizable() - .frame(width: 8, height: 8, alignment: .center) - .padding(.all, 2) - .opacity(0) - } - } - } - - var body: some View { - statusView - .padding(.top, 4) - .padding(.leading, 4) - } - -} diff --git a/Multiplatform/Shared/Timeline/TimelineItemView.swift b/Multiplatform/Shared/Timeline/TimelineItemView.swift deleted file mode 100644 index 955d6dd97..000000000 --- a/Multiplatform/Shared/Timeline/TimelineItemView.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// TimelineItemView.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/1/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct TimelineItemView: View { - - @EnvironmentObject var defaults: AppDefaults - @StateObject var articleIconImageLoader = ArticleIconImageLoader() - - var selected: Bool - var width: CGFloat - var timelineItem: TimelineItem - - #if os(macOS) - var verticalPadding: CGFloat = 10 - #endif - #if os(iOS) - var verticalPadding: CGFloat = 0 - #endif - - var body: some View { - HStack(alignment: .top) { - TimelineItemStatusView(selected: selected, status: timelineItem.status) - if let image = articleIconImageLoader.image { - IconImageView(iconImage: image) - .frame(width: CGFloat(defaults.timelineIconDimensions), height: CGFloat(defaults.timelineIconDimensions), alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) - } - VStack { - let titleLines = timelineItem.numberOfTitleLines(width: width) - if titleLines > 0 { - Text(verbatim: timelineItem.truncatedTitle) - .fontWeight(.semibold) - .lineLimit(titleLines) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.trailing, 4) - .fixedSize(horizontal: false, vertical: true) - } - let summaryLines = timelineItem.numberOfSummaryLines(width: width, titleLines: titleLines) - if summaryLines > 0 { - Text(verbatim: timelineItem.truncatedSummary) - .lineLimit(summaryLines) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.trailing, 4) - .fixedSize(horizontal: false, vertical: true) - } - Spacer(minLength: 0) - HStack { - Text(verbatim: timelineItem.byline) - .lineLimit(1) - .truncationMode(.tail) - .font(.footnote) - .foregroundColor(.secondary) - Spacer() - Text(verbatim: timelineItem.dateTimeString) - .lineLimit(1) - .font(.footnote) - .foregroundColor(.secondary) - .padding(.trailing, 4) - } - } - } - .padding(.vertical, verticalPadding) - .onAppear { - articleIconImageLoader.loadImage(for: timelineItem.article) - } - .contextMenu { - TimelineContextMenu(timelineItem: timelineItem) - } - } -} diff --git a/Multiplatform/Shared/Timeline/TimelineItems.swift b/Multiplatform/Shared/Timeline/TimelineItems.swift deleted file mode 100644 index 787323ff5..000000000 --- a/Multiplatform/Shared/Timeline/TimelineItems.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// TimelineItems.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/25/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation - -struct TimelineItems { - - var index = [String: Int]() - var items = [TimelineItem]() - - init() {} - - subscript(key: String) -> TimelineItem? { - get { - if let position = index[key] { - return items[position] - } - return nil - } - } - - mutating func append(_ item: TimelineItem) { - index[item.id] = item.position - items.append(item) - } - -} diff --git a/Multiplatform/Shared/Timeline/TimelineModel.swift b/Multiplatform/Shared/Timeline/TimelineModel.swift deleted file mode 100644 index 35b1ed020..000000000 --- a/Multiplatform/Shared/Timeline/TimelineModel.swift +++ /dev/null @@ -1,635 +0,0 @@ -// -// TimelineModel.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/30/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -#if os(macOS) -import AppKit -#else -import UIKit -#endif -import Combine -import RSCore -import Account -import Articles - -protocol TimelineModelDelegate: AnyObject { - var selectedFeedsPublisher: AnyPublisher<[Feed], Never>? { get } - func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed) -} - -class TimelineModel: ObservableObject, UndoableCommandRunner { - - weak var delegate: TimelineModelDelegate? - - @Published var nameForDisplay = "" - @Published var selectedTimelineItemIDs = Set() // Don't use directly. Use selectedTimelineItemsPublisher - @Published var selectedTimelineItemID: String? = nil // Don't use directly. Use selectedTimelineItemsPublisher - @Published var listID = "" - - var selectedArticles: [Article] { - return selectedTimelineItems.map { $0.article } - } - - var timelineItemsPublisher: AnyPublisher? - var timelineItemsSelectPublisher: AnyPublisher<(TimelineItems, String?), Never>? - var selectedTimelineItemsPublisher: AnyPublisher<[TimelineItem], Never>? - var selectedArticlesPublisher: AnyPublisher<[Article], Never>? - var articleStatusChangePublisher: AnyPublisher, Never>? - var readFilterAndFeedsPublisher: AnyPublisher<([Feed], Bool?), Never>? - - var articlesSubject = ReplaySubject<[Article], Never>(bufferSize: 1) - - var changeReadFilterSubject = PassthroughSubject() - var selectNextUnreadSubject = PassthroughSubject() - - var readFilterEnabledTable = [FeedIdentifier: Bool]() - - var undoManager: UndoManager? - var undoableCommands = [UndoableCommand]() - - private var cancellables = Set() - - private var sortDirectionSubject = ReplaySubject(bufferSize: 1) - private var groupByFeedSubject = ReplaySubject(bufferSize: 1) - - private var selectedTimelineItems = [TimelineItem]() - private var timelineItems = TimelineItems() - private var articles = [Article]() - - init(delegate: TimelineModelDelegate) { - self.delegate = delegate - subscribeToUserDefaultsChanges() - subscribeToReadFilterAndFeedChanges() - subscribeToArticleFetchChanges() - subscribeToArticleSelectionChanges() - subscribeToArticleStatusChanges() - } - - // MARK: API - - @discardableResult - func goToNextUnread() -> Bool { - var startIndex: Int - if let index = selectedTimelineItems.sorted(by: { $0.position < $1.position }).first?.position { - startIndex = index + 1 - } else { - startIndex = 0 - } - - for i in startIndex.. Article? { - return timelineItems[articleID]?.article - } - - func findPrevArticle(_ article: Article) -> Article? { - guard let index = timelineItems.index[article.articleID], index > 0 else { - return nil - } - return timelineItems.items[index - 1].article - } - - func findNextArticle(_ article: Article) -> Article? { - guard let index = timelineItems.index[article.articleID], index + 1 != timelineItems.items.count else { - return nil - } - return timelineItems.items[index + 1].article - } - - func selectArticle(_ article: Article) { - // TODO: Implement me! - } - - func toggleReadStatusForSelectedArticles() { - guard !selectedArticles.isEmpty else { - return - } - if selectedArticles.anyArticleIsUnread() { - markSelectedArticlesAsRead() - } else { - markSelectedArticlesAsUnread() - } - } - - func canMarkIndicatedArticlesAsRead(_ timelineItem: TimelineItem) -> Bool { - let articles = indicatedTimelineItems(timelineItem).map { $0.article } - return articles.anyArticleIsUnread() - } - - func markIndicatedArticlesAsRead(_ timelineItem: TimelineItem) { - let articles = indicatedTimelineItems(timelineItem).map { $0.article } - markArticlesWithUndo(articles, statusKey: .read, flag: true) - } - - func markSelectedArticlesAsRead() { - markArticlesWithUndo(selectedArticles, statusKey: .read, flag: true) - } - - func canMarkIndicatedArticlesAsUnread(_ timelineItem: TimelineItem) -> Bool { - let articles = indicatedTimelineItems(timelineItem).map { $0.article } - return articles.anyArticleIsReadAndCanMarkUnread() - } - - func markIndicatedArticlesAsUnread(_ timelineItem: TimelineItem) { - let articles = indicatedTimelineItems(timelineItem).map { $0.article } - markArticlesWithUndo(articles, statusKey: .read, flag: false) - } - - func markSelectedArticlesAsUnread() { - markArticlesWithUndo(selectedArticles, statusKey: .read, flag: false) - } - - func canMarkAboveAsRead(_ timelineItem: TimelineItem) -> Bool { - let timelineItem = indicatedAboveTimelineItem(timelineItem) - return articles.articlesAbove(position: timelineItem.position).canMarkAllAsRead() - } - - func markAboveAsRead(_ timelineItem: TimelineItem) { - let timelineItem = indicatedAboveTimelineItem(timelineItem) - let articlesToMark = articles.articlesAbove(position: timelineItem.position) - guard !articlesToMark.isEmpty else { return } - markArticlesWithUndo(articlesToMark, statusKey: .read, flag: true) - } - - func canMarkBelowAsRead(_ timelineItem: TimelineItem) -> Bool { - let timelineItem = indicatedBelowTimelineItem(timelineItem) - return articles.articlesBelow(position: timelineItem.position).canMarkAllAsRead() - } - - func markBelowAsRead(_ timelineItem: TimelineItem) { - let timelineItem = indicatedBelowTimelineItem(timelineItem) - let articlesToMark = articles.articlesBelow(position: timelineItem.position) - guard !articlesToMark.isEmpty else { return } - markArticlesWithUndo(articlesToMark, statusKey: .read, flag: true) - } - - func canMarkAllAsReadInWebFeed(_ timelineItem: TimelineItem) -> Bool { - return timelineItem.article.webFeed?.unreadCount ?? 0 > 0 - } - - func markAllAsReadInWebFeed(_ timelineItem: TimelineItem) { - guard let articlesSet = try? timelineItem.article.webFeed?.fetchArticles() else { return } - let articlesToMark = Array(articlesSet) - markArticlesWithUndo(articlesToMark, statusKey: .read, flag: true) - } - - func canMarkAllAsRead() -> Bool { - return articles.canMarkAllAsRead() - } - - func markAllAsRead() { - markArticlesWithUndo(articles, statusKey: .read, flag: true) - } - - func toggleStarredStatusForSelectedArticles() { - guard !selectedArticles.isEmpty else { - return - } - if selectedArticles.anyArticleIsUnstarred() { - markSelectedArticlesAsStarred() - } else { - markSelectedArticlesAsUnstarred() - } - } - - func canMarkIndicatedArticlesAsStarred(_ timelineItem: TimelineItem) -> Bool { - let articles = indicatedTimelineItems(timelineItem).map { $0.article } - return articles.anyArticleIsUnstarred() - } - - func markIndicatedArticlesAsStarred(_ timelineItem: TimelineItem) { - let articles = indicatedTimelineItems(timelineItem).map { $0.article } - markArticlesWithUndo(articles, statusKey: .starred, flag: true) - } - - func markSelectedArticlesAsStarred() { - markArticlesWithUndo(selectedArticles, statusKey: .starred, flag: true) - } - - func canMarkIndicatedArticlesAsUnstarred(_ timelineItem: TimelineItem) -> Bool { - let articles = indicatedTimelineItems(timelineItem).map { $0.article } - return articles.anyArticleIsStarred() - } - - func markIndicatedArticlesAsUnstarred(_ timelineItem: TimelineItem) { - let articles = indicatedTimelineItems(timelineItem).map { $0.article } - markArticlesWithUndo(articles, statusKey: .starred, flag: false) - } - - func markSelectedArticlesAsUnstarred() { - markArticlesWithUndo(selectedArticles, statusKey: .starred, flag: false) - } - - func canOpenIndicatedArticleInBrowser(_ timelineItem: TimelineItem) -> Bool { - guard indicatedTimelineItems(timelineItem).count == 1 else { return false } - return timelineItem.article.preferredLink != nil - } - - func openSelectedArticleInBrowser() { - guard let article = selectedArticles.first else { return } - openIndicatedArticleInBrowser(article) - } - - func openIndicatedArticleInBrowser(_ timelineItem: TimelineItem) { - openIndicatedArticleInBrowser(timelineItem.article) - } - - func openIndicatedArticleInBrowser(_ article: Article) { - #if os(macOS) - guard let link = article.preferredLink else { return } - Browser.open(link, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false) - #else - guard let url = article.preferredURL else { return } - UIApplication.shared.open(url, options: [:]) - #endif - } -} - -// MARK: Private - -private extension TimelineModel { - - // MARK: Subscriptions - - func subscribeToArticleStatusChanges() { - articleStatusChangePublisher = NotificationCenter.default.publisher(for: .StatusesDidChange) - .compactMap { $0.userInfo?[Account.UserInfoKey.articleIDs] as? Set } - .eraseToAnyPublisher() - } - - func subscribeToUserDefaultsChanges() { - let kickStartNote = Notification(name: Notification.Name("Kick Start")) - NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification) - .prepend(kickStartNote) - .sink { [weak self] _ in - self?.sortDirectionSubject.send(AppDefaults.shared.timelineSortDirection) - self?.groupByFeedSubject.send(AppDefaults.shared.timelineGroupByFeed) - }.store(in: &cancellables) - } - - func subscribeToReadFilterAndFeedChanges() { - guard let selectedFeedsPublisher = delegate?.selectedFeedsPublisher else { return } - - // Set the timeline name for display - selectedFeedsPublisher - .map { feeds -> String in - switch feeds.count { - case 0: - return "" - case 1: - return feeds.first!.nameForDisplay - default: - return NSLocalizedString("Multiple", comment: "Multiple") - } - } - .assign(to: &$nameForDisplay) - - selectedFeedsPublisher - .map { _ in - return UUID().uuidString - } - .assign(to: &$listID) - - // Clear the selected timeline items when the selected feed(s) change - selectedFeedsPublisher - .sink { [weak self] _ in - self?.selectedTimelineItemIDs = Set() - self?.selectedTimelineItemID = nil - } - .store(in: &cancellables) - - let toggledReadFilterPublisher = changeReadFilterSubject - .map { Optional($0) } - .withLatestFrom(selectedFeedsPublisher, resultSelector: { ($1, $0) }) - .share() - - toggledReadFilterPublisher - .sink { [weak self] (selectedFeeds, readFiltered) in - if let feedID = selectedFeeds.first?.feedID { - self?.readFilterEnabledTable[feedID] = readFiltered - } - } - .store(in: &cancellables) - - let feedsReadFilterPublisher = selectedFeedsPublisher - .map { [weak self] feeds -> ([Feed], Bool?) in - guard let self = self else { return (feeds, nil) } - - guard feeds.count == 1, let timelineFeed = feeds.first else { - return (feeds, nil) - } - - guard timelineFeed.defaultReadFilterType != .alwaysRead else { - return (feeds, nil) - } - - if let feedID = timelineFeed.feedID, let readFilterEnabled = self.readFilterEnabledTable[feedID] { - return (feeds, readFilterEnabled) - } else { - return (feeds, timelineFeed.defaultReadFilterType == .read) - } - } - - readFilterAndFeedsPublisher = toggledReadFilterPublisher - .merge(with: feedsReadFilterPublisher) - .share(replay: 1) - .eraseToAnyPublisher() - } - - func subscribeToArticleFetchChanges() { - guard let readFilterAndFeedsPublisher = readFilterAndFeedsPublisher else { return } - - let sortDirectionPublisher = sortDirectionSubject.removeDuplicates() - let groupByPublisher = groupByFeedSubject.removeDuplicates() - - // Download articles and transform them into timeline items - let inputTimelineItemsPublisher = readFilterAndFeedsPublisher - .flatMap { (feeds, readFilter) in - Self.fetchArticlesPublisher(feeds: feeds, isReadFiltered: readFilter) - } - .combineLatest(sortDirectionPublisher, groupByPublisher) - .compactMap { articles, sortDirection, groupBy -> TimelineItems in - let sortedArticles = Array(articles).sortedByDate(sortDirection ? .orderedDescending : .orderedAscending, groupByFeed: groupBy) - return Self.buildTimelineItems(articles: sortedArticles) - } - - guard let selectedFeedsPublisher = delegate?.selectedFeedsPublisher else { return } - - // Subscribe to any article downloads that may need to update the timeline - let accountDidDownloadPublisher = NotificationCenter.default.publisher(for: .AccountDidDownloadArticles) - .compactMap { $0.userInfo?[Account.UserInfoKey.webFeeds] as? Set } - .withLatestFrom(selectedFeedsPublisher, resultSelector: { ($0, $1) }) - .map { (noteFeeds, selectedFeeds) in - return Self.anyFeedIsPseudoFeed(selectedFeeds) || Self.anyFeedIntersection(selectedFeeds, webFeeds: noteFeeds) - } - .filter { $0 } - - // Download articles and merge them and then transform into timeline items - let downloadTimelineItemsPublisher = accountDidDownloadPublisher - .withLatestFrom(readFilterAndFeedsPublisher) - .flatMap { (feeds, readFilter) in - Self.fetchArticlesPublisher(feeds: feeds, isReadFiltered: readFilter) - } - .withLatestFrom(articlesSubject, sortDirectionPublisher, groupByPublisher, resultSelector: { (downloadArticles, latest) in - return (downloadArticles, latest.0, latest.1, latest.2) - }) - .map { (downloadArticles, currentArticles, sortDirection, groupBy) -> TimelineItems in - let downloadArticleIDs = downloadArticles.articleIDs() - var updatedArticles = downloadArticles - - for article in currentArticles { - if !downloadArticleIDs.contains(article.articleID) { - updatedArticles.insert(article) - } - } - - let sortedArticles = Array(updatedArticles).sortedByDate(sortDirection ? .orderedDescending : .orderedAscending, groupByFeed: groupBy) - return Self.buildTimelineItems(articles: sortedArticles) - } - - timelineItemsPublisher = inputTimelineItemsPublisher - .merge(with: downloadTimelineItemsPublisher) - .share() - .eraseToAnyPublisher() - - timelineItemsPublisher! - .sink { [weak self] timelineItems in - self?.timelineItems = timelineItems - self?.articles = timelineItems.items.map { $0.article } - } - .store(in: &cancellables) - - // Transform to articles for those that just need articles - timelineItemsPublisher! - .map { timelineItems in - timelineItems.items.map { $0.article } - } - .sink { [weak self] articles in - self?.articlesSubject.send(articles) - } - .store(in: &cancellables) - - // Automatically select the first unread if requested - timelineItemsSelectPublisher = timelineItemsPublisher! - .withLatestFrom(selectNextUnreadSubject.prepend(false), resultSelector: { ($0, $1) }) - .map { (timelineItems, selectNextUnread) -> (TimelineItems, String?) in - var selectTimelineItemID: String? = nil - if selectNextUnread { - selectTimelineItemID = timelineItems.items.first(where: { $0.article.status.read == false })?.id - } - return (timelineItems, selectTimelineItemID) - } - .share(replay: 1) - .eraseToAnyPublisher() - - timelineItemsSelectPublisher! - .sink { [weak self] _ in - self?.selectNextUnreadSubject.send(false) - } - .store(in: &cancellables) - } - - func subscribeToArticleSelectionChanges() { - guard let timelineItemsPublisher = timelineItemsPublisher else { return } - - let timelineSelectedIDsPublisher = $selectedTimelineItemIDs - .withLatestFrom(timelineItemsPublisher, resultSelector: { timelineItemIds, timelineItems -> [TimelineItem] in - return timelineItemIds.compactMap { timelineItems[$0] } - }) - - let timelineSelectedIDPublisher = $selectedTimelineItemID - .withLatestFrom(timelineItemsPublisher, resultSelector: { timelineItemId, timelineItems -> [TimelineItem] in - if let id = timelineItemId, let item = timelineItems[id] { - return [item] - } else { - return [TimelineItem]() - } - }) - - selectedTimelineItemsPublisher = timelineSelectedIDsPublisher - .merge(with: timelineSelectedIDPublisher) - .share(replay: 1) - .eraseToAnyPublisher() - - selectedArticlesPublisher = selectedTimelineItemsPublisher! - .map { timelineItems in timelineItems.map { $0.article } } - .share(replay: 1) - .eraseToAnyPublisher() - - selectedTimelineItemsPublisher! - .sink { [weak self] selectedTimelineItems in - self?.selectedTimelineItems = selectedTimelineItems - } - .store(in: &cancellables) - - // Automatically mark a selected record as read - selectedTimelineItemsPublisher! - .filter { $0.count == 1 } - .compactMap { $0.first?.article } - .filter { !$0.status.read } - .sink { markArticles(Set([$0]), statusKey: .read, flag: true) } - .store(in: &cancellables) - } - - // MARK: Article Fetching - - func fetchArticles(feeds: [Feed], isReadFiltered: Bool?) -> Set
{ - if feeds.isEmpty { - return Set
() - } - - var fetchedArticles = Set
() - for feed in feeds { - if isReadFiltered ?? true { - if let articles = try? feed.fetchUnreadArticles() { - fetchedArticles.formUnion(articles) - } - } else { - if let articles = try? feed.fetchArticles() { - fetchedArticles.formUnion(articles) - } - } - } - - return fetchedArticles - } - - static func fetchArticlesPublisher(feeds: [Feed], isReadFiltered: Bool?) -> Future, Never> { - return Future, Never> { promise in - - if feeds.isEmpty { - promise(.success(Set
())) - } - - #if os(macOS) - - var result = Set
() - - for feed in feeds { - if isReadFiltered ?? true { - if let articles = try? feed.fetchUnreadArticles() { - result.formUnion(articles) - } - } else { - if let articles = try? feed.fetchArticles() { - result.formUnion(articles) - } - } - } - - promise(.success(result)) - - #else - - let group = DispatchGroup() - var result = Set
() - - for feed in feeds { - if isReadFiltered ?? true { - group.enter() - feed.fetchUnreadArticlesAsync { articleSetResult in - let articles = (try? articleSetResult.get()) ?? Set
() - result.formUnion(articles) - group.leave() - } - } - else { - group.enter() - feed.fetchArticlesAsync { articleSetResult in - let articles = (try? articleSetResult.get()) ?? Set
() - result.formUnion(articles) - group.leave() - } - } - } - - group.notify(queue: DispatchQueue.main) { - promise(.success(result)) - } - - #endif - - } - - } - - static func buildTimelineItems(articles: [Article]) -> TimelineItems { - var items = TimelineItems() - for (position, article) in articles.enumerated() { - items.append(TimelineItem(position: position, article: article)) - } - return items - } - - static func anyFeedIsPseudoFeed(_ feeds: [Feed]) -> Bool { - return feeds.contains(where: { $0 is PseudoFeed}) - } - - static func anyFeedIntersection(_ feeds: [Feed], webFeeds: Set) -> Bool { - for feed in feeds { - if let selectedWebFeed = feed as? WebFeed { - for webFeed in webFeeds { - if selectedWebFeed.webFeedID == webFeed.webFeedID || selectedWebFeed.url == webFeed.url { - return true - } - } - } else if let folder = feed as? Folder { - for webFeed in webFeeds { - if folder.hasWebFeed(with: webFeed.webFeedID) || folder.hasWebFeed(withURL: webFeed.url) { - return true - } - } - } - } - return false - } - - // MARK: Aricle Marking - - func indicatedTimelineItems(_ timelineItem: TimelineItem) -> [TimelineItem] { - if selectedTimelineItems.contains(where: { $0.id == timelineItem.id }) { - return selectedTimelineItems - } else { - return [timelineItem] - } - } - - func indicatedAboveTimelineItem(_ timelineItem: TimelineItem) -> TimelineItem { - if selectedTimelineItems.contains(where: { $0.id == timelineItem.id }) { - return selectedTimelineItems.sorted(by: { $0.position < $1.position }).first! - } else { - return timelineItem - } - } - - func indicatedBelowTimelineItem(_ timelineItem: TimelineItem) -> TimelineItem { - if selectedTimelineItems.contains(where: { $0.id == timelineItem.id }) { - return selectedTimelineItems.sorted(by: { $0.position < $1.position }).last! - } else { - return timelineItem - } - } - - func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool) { - if let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager) { - runCommand(markReadCommand) - } else { - markArticles(Set(articles), statusKey: statusKey, flag: flag) - } - } - -} diff --git a/Multiplatform/Shared/Timeline/TimelineSortOrderView.swift b/Multiplatform/Shared/Timeline/TimelineSortOrderView.swift deleted file mode 100644 index eb5034da6..000000000 --- a/Multiplatform/Shared/Timeline/TimelineSortOrderView.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// TimelineSortOrderView.swift -// Multiplatform macOS -// -// Created by Maurice Parker on 7/12/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct TimelineSortOrderView: View { - - @EnvironmentObject var settings: AppDefaults - @State var selection: Int = 1 - - var body: some View { - Menu { - Button { - settings.timelineSortDirection = true - } label: { - HStack { - Text("Newest to Oldest") - if settings.timelineSortDirection { - Spacer() - AppAssets.checkmarkImage - } - } - } - Button { - settings.timelineSortDirection = false - } label: { - HStack { - Text("Oldest to Newest") - if !settings.timelineSortDirection { - Spacer() - AppAssets.checkmarkImage - } - } - } - Divider() - Button { - settings.timelineGroupByFeed.toggle() - } label: { - HStack { - Text("Group by Feed") - if settings.timelineGroupByFeed { - Spacer() - AppAssets.checkmarkImage - } - } - } - } label : { - if settings.timelineSortDirection { - Text("Sort Newest to Oldest") - } else { - Text("Sort Oldest to Newest") - } - } - .font(.subheadline) - .frame(width: 150) - .padding(.top, 8).padding(.leading) - .menuStyle(BorderlessButtonMenuStyle()) - } -} diff --git a/Multiplatform/Shared/Timeline/TimelineTextSizer.swift b/Multiplatform/Shared/Timeline/TimelineTextSizer.swift deleted file mode 100644 index 82cfcfbf8..000000000 --- a/Multiplatform/Shared/Timeline/TimelineTextSizer.swift +++ /dev/null @@ -1,197 +0,0 @@ -// -// MultilineUILabelSizer.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/16/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -#if os(macOS) -import AppKit -typealias RSFont = NSFont -#else -import UIKit -typealias RSFont = UIFont -#endif - -// Get the height of an NSTextField given a string, font, and width. -// Uses a cache. Avoids actually measuring text as much as possible. -// Main thread only. - -typealias WidthHeightCache = [Int: Int] // width: height - -private struct TextSizerSpecifier: Hashable { - let numberOfLines: Int - let font: RSFont -} - -struct TextSizeInfo { - - let size: CGSize // Integral size (ceiled) - let numberOfLinesUsed: Int // A two-line text field may only use one line, for instance. This would equal 1, then. -} - -final class TimelineTextSizer { - - private let numberOfLines: Int - private let font: RSFont - private let singleLineHeightEstimate: Int - private let doubleLineHeightEstimate: Int - private var cache = [String: WidthHeightCache]() // Each string has a cache. - private static var sizers = [TextSizerSpecifier: TimelineTextSizer]() - - private init(numberOfLines: Int, font: RSFont) { - - self.numberOfLines = numberOfLines - self.font = font - - self.singleLineHeightEstimate = TimelineTextSizer.calculateHeight("AqLjJ0/y", 200, font) - self.doubleLineHeightEstimate = TimelineTextSizer.calculateHeight("AqLjJ0/y\nAqLjJ0/y", 200, font) - - } - - static func size(for string: String, font: RSFont, numberOfLines: Int, width: Int) -> TextSizeInfo { - return sizer(numberOfLines: numberOfLines, font: font).sizeInfo(for: string, width: width) - } - - static func emptyCache() { - sizers = [TextSizerSpecifier: TimelineTextSizer]() - } - -} - -// MARK: - Private - -private extension TimelineTextSizer { - - static func sizer(numberOfLines: Int, font: RSFont) -> TimelineTextSizer { - - let specifier = TextSizerSpecifier(numberOfLines: numberOfLines, font: font) - if let cachedSizer = sizers[specifier] { - return cachedSizer - } - - let newSizer = TimelineTextSizer(numberOfLines: numberOfLines, font: font) - sizers[specifier] = newSizer - return newSizer - - } - - func sizeInfo(for string: String, width: Int) -> TextSizeInfo { - - let textFieldHeight = height(for: string, width: width) - let numberOfLinesUsed = numberOfLines(for: textFieldHeight) - - let size = CGSize(width: width, height: textFieldHeight) - let sizeInfo = TextSizeInfo(size: size, numberOfLinesUsed: numberOfLinesUsed) - return sizeInfo - - } - - func height(for string: String, width: Int) -> Int { - - if cache[string] == nil { - cache[string] = WidthHeightCache() - } - - if let height = cache[string]![width] { - return height - } - - if let height = heightConsideringNeighbors(cache[string]!, width) { - return height - } - - var height = TimelineTextSizer.calculateHeight(string, width, font) - - if numberOfLines != 0 { - let maxHeight = singleLineHeightEstimate * numberOfLines - if height > maxHeight { - height = maxHeight - } - } - - cache[string]![width] = height - - return height - } - - static func calculateHeight(_ string: String, _ width: Int, _ font: RSFont) -> Int { - let height = string.height(withConstrainedWidth: CGFloat(width), font: font) - return Int(ceil(height)) - } - - func numberOfLines(for height: Int) -> Int { - - // We’ll have to see if this really works reliably. - - let averageHeight = CGFloat(doubleLineHeightEstimate) / 2.0 - let lines = Int(round(CGFloat(height) / averageHeight)) - return lines - - } - - func heightIsProbablySingleLineHeight(_ height: Int) -> Bool { - return heightIsProbablyEqualToEstimate(height, singleLineHeightEstimate) - } - - func heightIsProbablyDoubleLineHeight(_ height: Int) -> Bool { - return heightIsProbablyEqualToEstimate(height, doubleLineHeightEstimate) - } - - func heightIsProbablyEqualToEstimate(_ height: Int, _ estimate: Int) -> Bool { - - let slop = 4 - let minimum = estimate - slop - let maximum = estimate + slop - return height >= minimum && height <= maximum - - } - - func heightConsideringNeighbors(_ heightCache: WidthHeightCache, _ width: Int) -> Int? { - - // Given width, if the height at width - something and width + something is equal, - // then that height must be correct for the given width. - // Also: - // If a narrower neighbor’s height is single line height, then this wider width must also be single-line height. - // If a wider neighbor’s height is double line height, and numberOfLines == 2, then this narrower width must able be double-line height. - - var smallNeighbor = (width: 0, height: 0) - var largeNeighbor = (width: 0, height: 0) - - for (oneWidth, oneHeight) in heightCache { - - if oneWidth < width && heightIsProbablySingleLineHeight(oneHeight) { - return oneHeight - } - if numberOfLines == 2 && oneWidth > width && heightIsProbablyDoubleLineHeight(oneHeight) { - return oneHeight - } - - if oneWidth < width && (oneWidth > smallNeighbor.width || smallNeighbor.width == 0) { - smallNeighbor = (oneWidth, oneHeight) - } - else if oneWidth > width && (oneWidth < largeNeighbor.width || largeNeighbor.width == 0) { - largeNeighbor = (oneWidth, oneHeight) - } - - if smallNeighbor.width != 0 && smallNeighbor.height == largeNeighbor.height { - return smallNeighbor.height - } - } - - return nil - - } - -} - -extension String { - - func height(withConstrainedWidth width: CGFloat, font: RSFont) -> CGFloat { - let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) - let boundingBox = self.boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font: font], context: nil) - return ceil(boundingBox.height) - } - -} diff --git a/Multiplatform/Shared/Timeline/TimelineToolbarModifier.swift b/Multiplatform/Shared/Timeline/TimelineToolbarModifier.swift deleted file mode 100644 index 59329427f..000000000 --- a/Multiplatform/Shared/Timeline/TimelineToolbarModifier.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// TimelineToolbarModifier.swift -// NetNewsWire -// -// Created by Maurice Parker on 7/5/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct TimelineToolbarModifier: ViewModifier { - - @EnvironmentObject private var sceneModel: SceneModel - @EnvironmentObject private var timelineModel: TimelineModel - @Environment(\.presentationMode) var presentationMode - #if os(iOS) - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - #endif - @State private var isReadFiltered: Bool? = nil - - func body(content: Content) -> some View { - content - .toolbar { - #if os(iOS) - ToolbarItem(placement: .primaryAction) { - Button { - if let filter = isReadFiltered { - timelineModel.changeReadFilterSubject.send(!filter) - } - } label: { - if isReadFiltered ?? false { - AppAssets.filterActiveImage.font(.title3) - } else { - AppAssets.filterInactiveImage.font(.title3) - } - } - .onReceive(timelineModel.readFilterAndFeedsPublisher!) { (_, filtered) in - isReadFiltered = filtered - } - .hidden(isReadFiltered == nil) - .help(isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles") - } - - ToolbarItem(placement: .bottomBar) { - Button { - sceneModel.markAllAsRead() - #if os(iOS) - if horizontalSizeClass == .compact { - presentationMode.wrappedValue.dismiss() - } - #endif - } label: { - AppAssets.markAllAsReadImage - } - .disabled(sceneModel.markAllAsReadButtonState == nil) - .help("Mark All As Read") - } - - ToolbarItem(placement: .bottomBar) { - Spacer() - } - #endif - } - } - -} diff --git a/Multiplatform/Shared/Timeline/TimelineView.swift b/Multiplatform/Shared/Timeline/TimelineView.swift deleted file mode 100644 index 34a0a2421..000000000 --- a/Multiplatform/Shared/Timeline/TimelineView.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// TimelineView.swift -// NetNewsWire -// -// Created by Maurice Parker on 6/30/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct TimelineView: View { - - @Binding var timelineItems: TimelineItems - @Binding var isReadFiltered: Bool? - - @EnvironmentObject private var timelineModel: TimelineModel - - @State private var timelineItemFrames = [String: CGRect]() - - var body: some View { - GeometryReader { geometryReaderProxy in - #if os(macOS) - VStack { - HStack { - TimelineSortOrderView() - Spacer() - Button (action: { - if let filtered = isReadFiltered { - timelineModel.changeReadFilterSubject.send(!filtered) - } - }, label: { - if isReadFiltered ?? false { - AppAssets.filterActiveImage - } else { - AppAssets.filterInactiveImage - } - }) - .hidden(isReadFiltered == nil) - .padding(.top, 8).padding(.trailing) - .buttonStyle(PlainButtonStyle()) - .help(isReadFiltered ?? false ? "Show Read Articles" : "Filter Read Articles") - } - ScrollViewReader { scrollViewProxy in - List(timelineItems.items, selection: $timelineModel.selectedTimelineItemIDs) { timelineItem in - let selected = timelineModel.selectedTimelineItemIDs.contains(timelineItem.article.articleID) - TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem) - .background(TimelineItemFramePreferenceView(timelineItem: timelineItem)) - } - .id(timelineModel.listID) - .onPreferenceChange(TimelineItemFramePreferenceKey.self) { preferences in - for pref in preferences { - timelineItemFrames[pref.articleID] = pref.frame - } - } - .onChange(of: timelineModel.selectedTimelineItemIDs) { selectedArticleIDs in - let proxyFrame = geometryReaderProxy.frame(in: .global) - for articleID in selectedArticleIDs { - if let itemFrame = timelineItemFrames[articleID] { - if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 35 { - withAnimation { - scrollViewProxy.scrollTo(articleID, anchor: .center) - } - } - } - } - } - } - } - .navigationTitle(Text(verbatim: timelineModel.nameForDisplay)) - #else - ScrollViewReader { scrollViewProxy in - List(timelineItems.items) { timelineItem in - ZStack { - let selected = timelineModel.selectedTimelineItemID == timelineItem.article.articleID - TimelineItemView(selected: selected, width: geometryReaderProxy.size.width, timelineItem: timelineItem) - .background(TimelineItemFramePreferenceView(timelineItem: timelineItem)) - NavigationLink(destination: ArticleContainerView(), - tag: timelineItem.article.articleID, - selection: $timelineModel.selectedTimelineItemID) { - EmptyView() - }.buttonStyle(PlainButtonStyle()) - } - } - .id(timelineModel.listID) - .onPreferenceChange(TimelineItemFramePreferenceKey.self) { preferences in - for pref in preferences { - timelineItemFrames[pref.articleID] = pref.frame - } - } - .onChange(of: timelineModel.selectedTimelineItemID) { selectedArticleID in - let proxyFrame = geometryReaderProxy.frame(in: .global) - if let articleID = selectedArticleID, let itemFrame = timelineItemFrames[articleID] { - if itemFrame.minY < proxyFrame.minY + 3 || itemFrame.maxY > proxyFrame.maxY - 3 { - withAnimation { - scrollViewProxy.scrollTo(articleID, anchor: .center) - } - } - } - } - } - .navigationBarTitle(Text(verbatim: timelineModel.nameForDisplay), displayMode: .inline) - #endif - } - } - -} - -struct TimelineItemFramePreferenceKey: PreferenceKey { - typealias Value = [TimelineItemFramePreference] - - static var defaultValue: [TimelineItemFramePreference] = [] - - static func reduce(value: inout [TimelineItemFramePreference], nextValue: () -> [TimelineItemFramePreference]) { - value.append(contentsOf: nextValue()) - } -} - -struct TimelineItemFramePreference: Equatable { - let articleID: String - let frame: CGRect -} - -struct TimelineItemFramePreferenceView: View { - let timelineItem: TimelineItem - - var body: some View { - GeometryReader { proxy in - Rectangle() - .fill(Color.clear) - .preference(key: TimelineItemFramePreferenceKey.self, - value: [TimelineItemFramePreference(articleID: timelineItem.article.articleID, frame: proxy.frame(in: .global))]) - } - } -} diff --git a/Multiplatform/iOS/AppDelegate.swift b/Multiplatform/iOS/AppDelegate.swift deleted file mode 100644 index d11a0f693..000000000 --- a/Multiplatform/iOS/AppDelegate.swift +++ /dev/null @@ -1,418 +0,0 @@ -// -// AppDelegate.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 6/28/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit -import RSCore -import RSWeb -import Account -import BackgroundTasks -import os.log -import Secrets - -var appDelegate: AppDelegate! - -class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider { - - private var bgTaskDispatchQueue = DispatchQueue.init(label: "BGTaskScheduler") - - private var waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid - private var syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid - - var syncTimer: ArticleStatusSyncTimer? - - var shuttingDown = false { - didSet { - if shuttingDown { - syncTimer?.shuttingDown = shuttingDown - syncTimer?.invalidate() - } - } - } - - var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") - - var userNotificationManager: UserNotificationManager! - var faviconDownloader: FaviconDownloader! - var imageDownloader: ImageDownloader! - var authorAvatarDownloader: AuthorAvatarDownloader! - var webFeedIconDownloader: WebFeedIconDownloader! - // TODO: Add Extension back in -// var extensionContainersFile: ExtensionContainersFile! -// var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile! - - var unreadCount = 0 { - didSet { - if unreadCount != oldValue { - postUnreadCountDidChangeNotification() - UIApplication.shared.applicationIconBadgeNumber = unreadCount - } - } - } - - var isSyncArticleStatusRunning = false - var isWaitingForSyncTasks = false - - override init() { - super.init() - appDelegate = self - - SecretsManager.provider = Secrets() - - let documentFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! - let documentAccountsFolder = documentFolder.appendingPathComponent("Accounts").absoluteString - let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7))) - AccountManager.shared = AccountManager(accountsFolder: documentAccountsFolderPath) - - let documentThemesFolder = documentFolder.appendingPathComponent("Themes").absoluteString - let documentThemesFolderPath = String(documentThemesFolder.suffix(from: documentAccountsFolder.index(documentThemesFolder.startIndex, offsetBy: 7))) - ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentThemesFolderPath) - - FeedProviderManager.shared.delegate = ExtensionPointManager.shared - - NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(accountRefreshDidFinish(_:)), name: .AccountRefreshDidFinish, object: nil) - } - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - AppDefaults.registerDefaults() - - let isFirstRun = AppDefaults.shared.isFirstRun() - if isFirstRun { - os_log("Is first run.", log: log, type: .info) - } - - if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() { - let localAccount = AccountManager.shared.defaultAccount - DefaultFeedsImporter.importDefaultFeeds(account: localAccount) - } - - registerBackgroundTasks() - CacheCleaner.purgeIfNecessary() - initializeDownloaders() - initializeHomeScreenQuickActions() - - DispatchQueue.main.async { - self.unreadCount = AccountManager.shared.unreadCount - } - - UNUserNotificationCenter.current().getNotificationSettings { (settings) in - if settings.authorizationStatus == .authorized { - DispatchQueue.main.async { - UIApplication.shared.registerForRemoteNotifications() - } - } - } - - UNUserNotificationCenter.current().delegate = self - userNotificationManager = UserNotificationManager() - - NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil) - - -// extensionContainersFile = ExtensionContainersFile() -// extensionFeedAddRequestFile = ExtensionFeedAddRequestFile() - - syncTimer = ArticleStatusSyncTimer() - - #if DEBUG - syncTimer!.update() - #endif - - return true - - } - - func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - DispatchQueue.main.async { - self.resumeDatabaseProcessingIfNecessary() - AccountManager.shared.receiveRemoteNotification(userInfo: userInfo) { - self.suspendApplication() - completionHandler(.newData) - } - } - } - - func applicationWillTerminate(_ application: UIApplication) { - shuttingDown = true - } - - // MARK: Notifications - - @objc func unreadCountDidChange(_ note: Notification) { - if note.object is AccountManager { - unreadCount = AccountManager.shared.unreadCount - } - } - - @objc func accountRefreshDidFinish(_ note: Notification) { - AppDefaults.shared.lastRefresh = Date() - } - - // MARK: - API - - func resumeDatabaseProcessingIfNecessary() { - if AccountManager.shared.isSuspended { - AccountManager.shared.resumeAll() - os_log("Application processing resumed.", log: self.log, type: .info) - } - } - - func prepareAccountsForBackground() { -// extensionFeedAddRequestFile.suspend() - syncTimer?.invalidate() - scheduleBackgroundFeedRefresh() - syncArticleStatus() - waitForSyncTasksToFinish() - } - - func prepareAccountsForForeground() { -// extensionFeedAddRequestFile.resume() - syncTimer?.update() - - if let lastRefresh = AppDefaults.shared.lastRefresh { - if Date() > lastRefresh.addingTimeInterval(15 * 60) { - AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) - } else { - AccountManager.shared.syncArticleStatusAll() - } - } else { - AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) - } - } - - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - completionHandler([.banner, .badge, .sound]) - } - - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - defer { completionHandler() } - - // TODO: Add back in User Notification handling -// if let sceneDelegate = response.targetScene?.delegate as? SceneDelegate { -// sceneDelegate.handle(response) -// } - - } - -} - -// MARK: App Initialization - -private extension AppDelegate { - - private func initializeDownloaders() { - let tempDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! - let faviconsFolderURL = tempDir.appendingPathComponent("Favicons") - let imagesFolderURL = tempDir.appendingPathComponent("Images") - - try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil) - let faviconsFolder = faviconsFolderURL.absoluteString - let faviconsFolderPath = faviconsFolder.suffix(from: faviconsFolder.index(faviconsFolder.startIndex, offsetBy: 7)) - faviconDownloader = FaviconDownloader(folder: String(faviconsFolderPath)) - - let imagesFolder = imagesFolderURL.absoluteString - let imagesFolderPath = imagesFolder.suffix(from: imagesFolder.index(imagesFolder.startIndex, offsetBy: 7)) - try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil) - imageDownloader = ImageDownloader(folder: String(imagesFolderPath)) - - authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader) - - let tempFolder = tempDir.absoluteString - let tempFolderPath = tempFolder.suffix(from: tempFolder.index(tempFolder.startIndex, offsetBy: 7)) - webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: String(tempFolderPath)) - } - - private func initializeHomeScreenQuickActions() { - let unreadTitle = NSLocalizedString("First Unread", comment: "First Unread") - let unreadIcon = UIApplicationShortcutIcon(systemImageName: "chevron.down.circle") - let unreadItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.FirstUnread", localizedTitle: unreadTitle, localizedSubtitle: nil, icon: unreadIcon, userInfo: nil) - - let searchTitle = NSLocalizedString("Search", comment: "Search") - let searchIcon = UIApplicationShortcutIcon(systemImageName: "magnifyingglass") - let searchItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.ShowSearch", localizedTitle: searchTitle, localizedSubtitle: nil, icon: searchIcon, userInfo: nil) - - let addTitle = NSLocalizedString("Add Feed", comment: "Add Feed") - let addIcon = UIApplicationShortcutIcon(systemImageName: "plus") - let addItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.ShowAdd", localizedTitle: addTitle, localizedSubtitle: nil, icon: addIcon, userInfo: nil) - - UIApplication.shared.shortcutItems = [addItem, searchItem, unreadItem] - } - -} - -// MARK: Go To Background - -private extension AppDelegate { - - func waitForSyncTasksToFinish() { - guard !isWaitingForSyncTasks && UIApplication.shared.applicationState == .background else { return } - - isWaitingForSyncTasks = true - - self.waitBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { [weak self] in - guard let self = self else { return } - self.completeProcessing(true) - os_log("Accounts wait for progress terminated for running too long.", log: self.log, type: .info) - } - - DispatchQueue.main.async { [weak self] in - self?.waitToComplete() { [weak self] suspend in - self?.completeProcessing(suspend) - } - } - } - - func waitToComplete(completion: @escaping (Bool) -> Void) { - guard UIApplication.shared.applicationState == .background else { - os_log("App came back to forground, no longer waiting.", log: self.log, type: .info) - completion(false) - return - } - - if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning { - os_log("Waiting for sync to finish...", log: self.log, type: .info) - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in - self?.waitToComplete(completion: completion) - } - } else { - os_log("Refresh progress complete.", log: self.log, type: .info) - completion(true) - } - } - - func completeProcessing(_ suspend: Bool) { - if suspend { - suspendApplication() - } - UIApplication.shared.endBackgroundTask(self.waitBackgroundUpdateTask) - self.waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid - isWaitingForSyncTasks = false - } - - func syncArticleStatus() { - guard !isSyncArticleStatusRunning else { return } - - isSyncArticleStatusRunning = true - - let completeProcessing = { [unowned self] in - self.isSyncArticleStatusRunning = false - UIApplication.shared.endBackgroundTask(self.syncBackgroundUpdateTask) - self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid - } - - self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { - completeProcessing() - os_log("Accounts sync processing terminated for running too long.", log: self.log, type: .info) - } - - DispatchQueue.main.async { - AccountManager.shared.syncArticleStatusAll() { - completeProcessing() - } - } - } - - func suspendApplication() { - guard UIApplication.shared.applicationState == .background else { return } - - AccountManager.shared.suspendNetworkAll() - AccountManager.shared.suspendDatabaseAll() - CoalescingQueue.standard.performCallsImmediately() - - os_log("Application processing suspended.", log: self.log, type: .info) - } - -} - -// MARK: Background Tasks - -private extension AppDelegate { - - /// Register all background tasks. - func registerBackgroundTasks() { - // Register background feed refresh. - BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.ranchero.NetNewsWire.FeedRefresh", using: nil) { (task) in - self.performBackgroundFeedRefresh(with: task as! BGAppRefreshTask) - } - } - - /// Schedules a background app refresh based on `AppDefaults.refreshInterval`. - func scheduleBackgroundFeedRefresh() { - let request = BGAppRefreshTaskRequest(identifier: "com.ranchero.NetNewsWire.FeedRefresh") - request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) - - // We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the - // task scheduler can hang indefinitely. - bgTaskDispatchQueue.async { - do { - try BGTaskScheduler.shared.submit(request) - } catch { - os_log(.error, log: self.log, "Could not schedule app refresh: %@", error.localizedDescription) - } - } - } - - /// Performs background feed refresh. - /// - Parameter task: `BGAppRefreshTask` - /// - Warning: As of Xcode 11 beta 2, when triggered from the debugger this doesn't work. - func performBackgroundFeedRefresh(with task: BGAppRefreshTask) { - - scheduleBackgroundFeedRefresh() // schedule next refresh - - os_log("Woken to perform account refresh.", log: self.log, type: .info) - - DispatchQueue.main.async { - if AccountManager.shared.isSuspended { - AccountManager.shared.resumeAll() - } - AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in - if !AccountManager.shared.isSuspended { - self.suspendApplication() - os_log("Account refresh operation completed.", log: self.log, type: .info) - task.setTaskCompleted(success: true) - } - } - } - - // set expiration handler - task.expirationHandler = { [weak task] in - DispatchQueue.main.sync { - self.suspendApplication() - } - os_log("Accounts refresh processing terminated for running too long.", log: self.log, type: .info) - task?.setTaskCompleted(success: false) - } - } - -} - -private extension AppDelegate { - @objc func userDefaultsDidChange() { - updateUserInterfaceStyle() - } - - var window: UIWindow? { - guard let scene = UIApplication.shared.connectedScenes.first, - let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, - let window = windowSceneDelegate.window else { - return nil - } - return window - } - - func updateUserInterfaceStyle() { -// switch AppDefaults.shared.userInterfaceColorPalette { -// case .automatic: -// window?.overrideUserInterfaceStyle = .unspecified -// case .light: -// window?.overrideUserInterfaceStyle = .light -// case .dark: -// window?.overrideUserInterfaceStyle = .dark -// } - } -} diff --git a/Multiplatform/iOS/Article/ActivityViewController.swift b/Multiplatform/iOS/Article/ActivityViewController.swift deleted file mode 100644 index 9f4497b1f..000000000 --- a/Multiplatform/iOS/Article/ActivityViewController.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// ArticleShareView.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/13/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit -import SwiftUI -import Articles - -extension UIActivityViewController { - convenience init(url: URL, title: String?, applicationActivities: [UIActivity]?) { - let itemSource = ArticleActivityItemSource(url: url, subject: title) - let titleSource = TitleActivityItemSource(title: title) - self.init(activityItems: [titleSource, itemSource], applicationActivities: applicationActivities) - } -} - -struct ActivityViewController: UIViewControllerRepresentable { - - var title: String? - var url: URL - - func makeUIViewController(context: Context) -> UIActivityViewController { - return UIActivityViewController(url: url, title: title, applicationActivities: [FindInArticleActivity(), OpenInSafariActivity()]) - } - - func updateUIViewController(_ controller: UIActivityViewController, context: Context) { - } - -} diff --git a/Multiplatform/iOS/Article/ArticleActivityItemSource.swift b/Multiplatform/iOS/Article/ArticleActivityItemSource.swift deleted file mode 100644 index f87258a9d..000000000 --- a/Multiplatform/iOS/Article/ArticleActivityItemSource.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// ArticleActivityItemSource.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/13/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit - -class ArticleActivityItemSource: NSObject, UIActivityItemSource { - - private let url: URL - private let subject: String? - - init(url: URL, subject: String?) { - self.url = url - self.subject = subject - } - - func activityViewControllerPlaceholderItem(_ : UIActivityViewController) -> Any { - return url - } - - func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - return url - } - - func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { - return subject ?? "" - } - -} diff --git a/Multiplatform/iOS/Article/ArticleView.swift b/Multiplatform/iOS/Article/ArticleView.swift deleted file mode 100644 index 4310e51c3..000000000 --- a/Multiplatform/iOS/Article/ArticleView.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// ArticleView.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Articles - -struct ArticleView: UIViewControllerRepresentable { - - @EnvironmentObject private var sceneModel: SceneModel - - func makeUIViewController(context: Context) -> ArticleViewController { - let controller = ArticleViewController() - controller.sceneModel = sceneModel - return controller - } - - func updateUIViewController(_ uiViewController: ArticleViewController, context: Context) { - - } - -} diff --git a/Multiplatform/iOS/Article/ArticleViewController.swift b/Multiplatform/iOS/Article/ArticleViewController.swift deleted file mode 100644 index 96581c83b..000000000 --- a/Multiplatform/iOS/Article/ArticleViewController.swift +++ /dev/null @@ -1,163 +0,0 @@ -// -// ArticleViewController.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit -import Combine -import WebKit -import Account -import Articles -import SafariServices - -class ArticleViewController: UIViewController { - - weak var sceneModel: SceneModel? - - private var pageViewController: UIPageViewController! - - private var currentWebViewController: WebViewController? { - return pageViewController?.viewControllers?.first as? WebViewController - } - - var articles: [Article]? { - didSet { - currentArticle = articles?.first - } - } - - var currentArticle: Article? { - didSet { - if let controller = currentWebViewController, controller.article != currentArticle { - controller.setArticle(currentArticle) - DispatchQueue.main.async { - // You have to set the view controller to clear out the UIPageViewController child controller cache. - // You also have to do it in an async call or you will get a strange assertion error. - self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil) - } - } - } - } - - private var cancellables = Set() - - override func viewDidLoad() { - super.viewDidLoad() - - pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:]) - pageViewController.delegate = self - pageViewController.dataSource = self - - pageViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(pageViewController.view) - addChild(pageViewController!) - NSLayoutConstraint.activate([ - view.leadingAnchor.constraint(equalTo: pageViewController.view.leadingAnchor), - view.trailingAnchor.constraint(equalTo: pageViewController.view.trailingAnchor), - view.topAnchor.constraint(equalTo: pageViewController.view.topAnchor), - view.bottomAnchor.constraint(equalTo: pageViewController.view.bottomAnchor) - ]) - - sceneModel?.timelineModel.selectedArticlesPublisher?.sink { [weak self] articles in - self?.articles = articles - } - .store(in: &cancellables) - - let controller = createWebViewController(currentArticle, updateView: true) - self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil) - - } - - // MARK: API - - func focus() { - currentWebViewController?.focus() - } - - func canScrollDown() -> Bool { - return currentWebViewController?.canScrollDown() ?? false - } - - func scrollPageDown() { - currentWebViewController?.scrollPageDown() - } - - func stopArticleExtractorIfProcessing() { - currentWebViewController?.stopArticleExtractorIfProcessing() - } - -} - - -// MARK: WebViewControllerDelegate - -extension ArticleViewController: WebViewControllerDelegate { - - func webViewController(_ webViewController: WebViewController, articleExtractorButtonStateDidUpdate buttonState: ArticleExtractorButtonState) { - if webViewController === currentWebViewController { -// articleExtractorButton.buttonState = buttonState - } - } - -} - -// MARK: UIPageViewControllerDataSource - -extension ArticleViewController: UIPageViewControllerDataSource { - - func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { - guard let webViewController = viewController as? WebViewController, - let currentArticle = webViewController.article, - let article = sceneModel?.findPrevArticle(currentArticle) else { - return nil - } - return createWebViewController(article) - } - - func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { - guard let webViewController = viewController as? WebViewController, - let currentArticle = webViewController.article, - let article = sceneModel?.findNextArticle(currentArticle) else { - return nil - } - return createWebViewController(article) - } - -} - -// MARK: UIPageViewControllerDelegate - -extension ArticleViewController: UIPageViewControllerDelegate { - - func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { - guard finished, completed else { return } -// guard let article = currentWebViewController?.article else { return } - -// articleExtractorButton.buttonState = currentWebViewController?.articleExtractorButtonState ?? .off - - previousViewControllers.compactMap({ $0 as? WebViewController }).forEach({ $0.stopWebViewActivity() }) - } - -} - -// MARK: Private - -private extension ArticleViewController { - - func createWebViewController(_ article: Article?, updateView: Bool = true) -> WebViewController { - let controller = WebViewController() - controller.sceneModel = sceneModel - controller.delegate = self - controller.setArticle(article, updateView: updateView) - return controller - } - -} - -public extension Notification.Name { - static let FindInArticle = Notification.Name("FindInArticle") - static let EndFindInArticle = Notification.Name("EndFindInArticle") -} diff --git a/Multiplatform/iOS/Article/FindInArticleActivity.swift b/Multiplatform/iOS/Article/FindInArticleActivity.swift deleted file mode 100644 index 334a142fb..000000000 --- a/Multiplatform/iOS/Article/FindInArticleActivity.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// FindInArticleActivity.swift -// NetNewsWire-iOS -// -// Created by Brian Sanders on 5/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit - -class FindInArticleActivity: UIActivity { - override var activityTitle: String? { - NSLocalizedString("Find in Article", comment: "Find in Article") - } - - override var activityType: UIActivity.ActivityType? { - UIActivity.ActivityType(rawValue: "com.ranchero.NetNewsWire.find") - } - - override var activityImage: UIImage? { - UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)) - } - - override class var activityCategory: UIActivity.Category { - .action - } - - override func canPerform(withActivityItems activityItems: [Any]) -> Bool { - true - } - - override func prepare(withActivityItems activityItems: [Any]) { - - } - - override func perform() { - NotificationCenter.default.post(Notification(name: .FindInArticle)) - activityDidFinish(true) - } -} diff --git a/Multiplatform/iOS/Article/IconView.swift b/Multiplatform/iOS/Article/IconView.swift deleted file mode 100644 index dc44dc0d6..000000000 --- a/Multiplatform/iOS/Article/IconView.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// IconView.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit - -final class IconView: UIView { - - var iconImage: IconImage? = nil { - didSet { - if iconImage !== oldValue { - imageView.image = iconImage?.image - - if self.traitCollection.userInterfaceStyle == .dark { - if self.iconImage?.isDark ?? false { - self.isDiscernable = false - self.setNeedsLayout() - } else { - self.isDiscernable = true - self.setNeedsLayout() - } - } else { - if self.iconImage?.isBright ?? false { - self.isDiscernable = false - self.setNeedsLayout() - } else { - self.isDiscernable = true - self.setNeedsLayout() - } - } - self.setNeedsLayout() - } - } - } - - private var isDiscernable = true - - private let imageView: UIImageView = { - let imageView = UIImageView(image: AppAssets.faviconTemplateImage) - imageView.contentMode = .scaleAspectFit - imageView.clipsToBounds = true - imageView.layer.cornerRadius = 4.0 - return imageView - }() - - private var isVerticalBackgroundExposed: Bool { - return imageView.frame.size.height < bounds.size.height - } - - private var isSymbolImage: Bool { - return iconImage?.isSymbol ?? false - } - - private var isBackgroundSuppressed: Bool { - return iconImage?.isBackgroundSupressed ?? false - } - - override init(frame: CGRect) { - super.init(frame: frame) - commonInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() - } - - convenience init() { - self.init(frame: .zero) - } - - override func didMoveToSuperview() { - setNeedsLayout() - } - - override func layoutSubviews() { - imageView.setFrameIfNotEqual(rectForImageView()) - if (iconImage != nil && isVerticalBackgroundExposed) || !isDiscernable { - backgroundColor = AppAssets.uiIconBackgroundColor - } else { - backgroundColor = nil - } - } - -} - -private extension IconView { - - func commonInit() { - layer.cornerRadius = 4 - clipsToBounds = true - addSubview(imageView) - } - - func rectForImageView() -> CGRect { - guard let image = iconImage?.image else { - return CGRect.zero - } - - let imageSize = image.size - let viewSize = bounds.size - if imageSize.height == imageSize.width { - if imageSize.height >= viewSize.height { - return CGRect(x: 0.0, y: 0.0, width: viewSize.width, height: viewSize.height) - } - let offset = floor((viewSize.height - imageSize.height) / 2.0) - return CGRect(x: offset, y: offset, width: imageSize.width, height: imageSize.height) - } - else if imageSize.height > imageSize.width { - let factor = viewSize.height / imageSize.height - let width = imageSize.width * factor - let originX = floor((viewSize.width - width) / 2.0) - return CGRect(x: originX, y: 0.0, width: width, height: viewSize.height) - } - - // Wider than tall: imageSize.width > imageSize.height - let factor = viewSize.width / imageSize.width - let height = imageSize.height * factor - let originY = floor((viewSize.height - height) / 2.0) - return CGRect(x: 0.0, y: originY, width: viewSize.width, height: height) - } - -} diff --git a/Multiplatform/iOS/Article/ImageScrollView.swift b/Multiplatform/iOS/Article/ImageScrollView.swift deleted file mode 100644 index 31fe3289c..000000000 --- a/Multiplatform/iOS/Article/ImageScrollView.swift +++ /dev/null @@ -1,361 +0,0 @@ -// -// ImageScrollView.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit - -@objc public protocol ImageScrollViewDelegate: UIScrollViewDelegate { - func imageScrollViewDidGestureSwipeUp(imageScrollView: ImageScrollView) - func imageScrollViewDidGestureSwipeDown(imageScrollView: ImageScrollView) -} - -open class ImageScrollView: UIScrollView { - - @objc public enum ScaleMode: Int { - case aspectFill - case aspectFit - case widthFill - case heightFill - } - - @objc public enum Offset: Int { - case begining - case center - } - - static let kZoomInFactorFromMinWhenDoubleTap: CGFloat = 2 - - @objc open var imageContentMode: ScaleMode = .widthFill - @objc open var initialOffset: Offset = .begining - - @objc public private(set) var zoomView: UIImageView? = nil - - @objc open weak var imageScrollViewDelegate: ImageScrollViewDelegate? - - var imageSize: CGSize = CGSize.zero - private var pointToCenterAfterResize: CGPoint = CGPoint.zero - private var scaleToRestoreAfterResize: CGFloat = 1.0 - var maxScaleFromMinScale: CGFloat = 3.0 - - var zoomedFrame: CGRect { - return zoomView?.frame ?? CGRect.zero - } - - override open var frame: CGRect { - willSet { - if frame.equalTo(newValue) == false && newValue.equalTo(CGRect.zero) == false && imageSize.equalTo(CGSize.zero) == false { - prepareToResize() - } - } - - didSet { - if frame.equalTo(oldValue) == false && frame.equalTo(CGRect.zero) == false && imageSize.equalTo(CGSize.zero) == false { - recoverFromResizing() - } - } - } - - override public init(frame: CGRect) { - super.init(frame: frame) - - initialize() - } - - required public init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - - initialize() - } - - private func initialize() { - showsVerticalScrollIndicator = false - showsHorizontalScrollIndicator = false - bouncesZoom = true - decelerationRate = UIScrollView.DecelerationRate.fast - delegate = self - } - - @objc public func adjustFrameToCenter() { - - guard let unwrappedZoomView = zoomView else { - return - } - - var frameToCenter = unwrappedZoomView.frame - - // center horizontally - if frameToCenter.size.width < bounds.width { - frameToCenter.origin.x = (bounds.width - frameToCenter.size.width) / 2 - } else { - frameToCenter.origin.x = 0 - } - - // center vertically - if frameToCenter.size.height < bounds.height { - frameToCenter.origin.y = (bounds.height - frameToCenter.size.height) / 2 - } else { - frameToCenter.origin.y = 0 - } - - unwrappedZoomView.frame = frameToCenter - } - - private func prepareToResize() { - let boundsCenter = CGPoint(x: bounds.midX, y: bounds.midY) - pointToCenterAfterResize = convert(boundsCenter, to: zoomView) - - scaleToRestoreAfterResize = zoomScale - - // If we're at the minimum zoom scale, preserve that by returning 0, which will be converted to the minimum - // allowable scale when the scale is restored. - if scaleToRestoreAfterResize <= minimumZoomScale + CGFloat(Float.ulpOfOne) { - scaleToRestoreAfterResize = 0 - } - } - - private func recoverFromResizing() { - setMaxMinZoomScalesForCurrentBounds() - - // restore zoom scale, first making sure it is within the allowable range. - let maxZoomScale = max(minimumZoomScale, scaleToRestoreAfterResize) - zoomScale = min(maximumZoomScale, maxZoomScale) - - // restore center point, first making sure it is within the allowable range. - - // convert our desired center point back to our own coordinate space - let boundsCenter = convert(pointToCenterAfterResize, to: zoomView) - - // calculate the content offset that would yield that center point - var offset = CGPoint(x: boundsCenter.x - bounds.size.width/2.0, y: boundsCenter.y - bounds.size.height/2.0) - - // restore offset, adjusted to be within the allowable range - let maxOffset = maximumContentOffset() - let minOffset = minimumContentOffset() - - var realMaxOffset = min(maxOffset.x, offset.x) - offset.x = max(minOffset.x, realMaxOffset) - - realMaxOffset = min(maxOffset.y, offset.y) - offset.y = max(minOffset.y, realMaxOffset) - - contentOffset = offset - } - - private func maximumContentOffset() -> CGPoint { - return CGPoint(x: contentSize.width - bounds.width,y:contentSize.height - bounds.height) - } - - private func minimumContentOffset() -> CGPoint { - return CGPoint.zero - } - - // MARK: - Set up - - open func setup() { - var topSupperView = superview - - while topSupperView?.superview != nil { - topSupperView = topSupperView?.superview - } - - // Make sure views have already layout with precise frame - topSupperView?.layoutIfNeeded() - } - - // MARK: - Display image - - @objc open func display(image: UIImage) { - - if let zoomView = zoomView { - zoomView.removeFromSuperview() - } - - zoomView = UIImageView(image: image) - zoomView!.isUserInteractionEnabled = true - addSubview(zoomView!) - - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapGestureRecognizer(_:))) - tapGesture.numberOfTapsRequired = 2 - zoomView!.addGestureRecognizer(tapGesture) - - let downSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeUpGestureRecognizer(_:))) - downSwipeGesture.direction = .down - zoomView!.addGestureRecognizer(downSwipeGesture) - - let upSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeDownGestureRecognizer(_:))) - upSwipeGesture.direction = .up - zoomView!.addGestureRecognizer(upSwipeGesture) - - configureImageForSize(image.size) - adjustFrameToCenter() - } - - private func configureImageForSize(_ size: CGSize) { - imageSize = size - contentSize = imageSize - setMaxMinZoomScalesForCurrentBounds() - zoomScale = minimumZoomScale - - switch initialOffset { - case .begining: - contentOffset = CGPoint.zero - case .center: - let xOffset = contentSize.width < bounds.width ? 0 : (contentSize.width - bounds.width)/2 - let yOffset = contentSize.height < bounds.height ? 0 : (contentSize.height - bounds.height)/2 - - switch imageContentMode { - case .aspectFit: - contentOffset = CGPoint.zero - case .aspectFill: - contentOffset = CGPoint(x: xOffset, y: yOffset) - case .heightFill: - contentOffset = CGPoint(x: xOffset, y: 0) - case .widthFill: - contentOffset = CGPoint(x: 0, y: yOffset) - } - } - } - - private func setMaxMinZoomScalesForCurrentBounds() { - // calculate min/max zoomscale - let xScale = bounds.width / imageSize.width // the scale needed to perfectly fit the image width-wise - let yScale = bounds.height / imageSize.height // the scale needed to perfectly fit the image height-wise - - var minScale: CGFloat = 1 - - switch imageContentMode { - case .aspectFill: - minScale = max(xScale, yScale) - case .aspectFit: - minScale = min(xScale, yScale) - case .widthFill: - minScale = xScale - case .heightFill: - minScale = yScale - } - - - let maxScale = maxScaleFromMinScale*minScale - - // don't let minScale exceed maxScale. (If the image is smaller than the screen, we don't want to force it to be zoomed.) - if minScale > maxScale { - minScale = maxScale - } - - maximumZoomScale = maxScale - minimumZoomScale = minScale // * 0.999 // the multiply factor to prevent user cannot scroll page while they use this control in UIPageViewController - } - - // MARK: - Gesture - - @objc func doubleTapGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) { - // zoom out if it bigger than middle scale point. Else, zoom in - if zoomScale >= maximumZoomScale / 2.0 { - setZoomScale(minimumZoomScale, animated: true) - } else { - let center = gestureRecognizer.location(in: gestureRecognizer.view) - let zoomRect = zoomRectForScale(ImageScrollView.kZoomInFactorFromMinWhenDoubleTap * minimumZoomScale, center: center) - zoom(to: zoomRect, animated: true) - } - } - - @objc func swipeUpGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) { - if gestureRecognizer.state == .ended { - imageScrollViewDelegate?.imageScrollViewDidGestureSwipeUp(imageScrollView: self) - } - } - - @objc func swipeDownGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) { - if gestureRecognizer.state == .ended { - imageScrollViewDelegate?.imageScrollViewDidGestureSwipeDown(imageScrollView: self) - } - } - - private func zoomRectForScale(_ scale: CGFloat, center: CGPoint) -> CGRect { - var zoomRect = CGRect.zero - - // the zoom rect is in the content view's coordinates. - // at a zoom scale of 1.0, it would be the size of the imageScrollView's bounds. - // as the zoom scale decreases, so more content is visible, the size of the rect grows. - zoomRect.size.height = frame.size.height / scale - zoomRect.size.width = frame.size.width / scale - - // choose an origin so as to get the right center. - zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0) - zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0) - - return zoomRect - } - - open func refresh() { - if let image = zoomView?.image { - display(image: image) - } - } - - open func resize() { - self.configureImageForSize(self.imageSize) - } -} - -extension ImageScrollView: UIScrollViewDelegate { - - public func scrollViewDidScroll(_ scrollView: UIScrollView) { - imageScrollViewDelegate?.scrollViewDidScroll?(scrollView) - } - - public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - imageScrollViewDelegate?.scrollViewWillBeginDragging?(scrollView) - } - - public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - imageScrollViewDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset) - } - - public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - imageScrollViewDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) - } - - public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { - imageScrollViewDelegate?.scrollViewWillBeginDecelerating?(scrollView) - } - - public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - imageScrollViewDelegate?.scrollViewDidEndDecelerating?(scrollView) - } - - public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { - imageScrollViewDelegate?.scrollViewDidEndScrollingAnimation?(scrollView) - } - - public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { - imageScrollViewDelegate?.scrollViewWillBeginZooming?(scrollView, with: view) - } - - public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { - imageScrollViewDelegate?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale) - } - - public func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { - return false - } - - @available(iOS 11.0, *) - public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { - imageScrollViewDelegate?.scrollViewDidChangeAdjustedContentInset?(scrollView) - } - - public func viewForZooming(in scrollView: UIScrollView) -> UIView? { - return zoomView - } - - public func scrollViewDidZoom(_ scrollView: UIScrollView) { - adjustFrameToCenter() - imageScrollViewDelegate?.scrollViewDidZoom?(scrollView) - } - -} diff --git a/Multiplatform/iOS/Article/ImageTransition.swift b/Multiplatform/iOS/Article/ImageTransition.swift deleted file mode 100644 index 01f460348..000000000 --- a/Multiplatform/iOS/Article/ImageTransition.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// ImageTransition.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit - -class ImageTransition: NSObject, UIViewControllerAnimatedTransitioning { - - private weak var webViewController: WebViewController? - private let duration = 0.4 - var presenting = true - var originFrame: CGRect! - var maskFrame: CGRect! - var originImage: UIImage! - - init(controller: WebViewController) { - self.webViewController = controller - } - - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return duration - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - if presenting { - animateTransitionPresenting(using: transitionContext) - } else { - animateTransitionReturning(using: transitionContext) - } - } - - private func animateTransitionPresenting(using transitionContext: UIViewControllerContextTransitioning) { - - let imageView = UIImageView(image: originImage) - imageView.frame = originFrame - - let fromView = transitionContext.view(forKey: .from)! - fromView.removeFromSuperview() - - transitionContext.containerView.backgroundColor = .systemBackground - transitionContext.containerView.addSubview(imageView) - - webViewController?.hideClickedImage() - - UIView.animate( - withDuration: duration, - delay:0.0, - usingSpringWithDamping: 0.8, - initialSpringVelocity: 0.2, - animations: { - let imageController = transitionContext.viewController(forKey: .to) as! ImageViewController - imageView.frame = imageController.zoomedFrame - }, completion: { _ in - imageView.removeFromSuperview() - let toView = transitionContext.view(forKey: .to)! - transitionContext.containerView.addSubview(toView) - transitionContext.completeTransition(true) - }) - } - - private func animateTransitionReturning(using transitionContext: UIViewControllerContextTransitioning) { - let imageController = transitionContext.viewController(forKey: .from) as! ImageViewController - let imageView = UIImageView(image: originImage) - imageView.frame = imageController.zoomedFrame - - let fromView = transitionContext.view(forKey: .from)! - let windowFrame = fromView.window!.frame - fromView.removeFromSuperview() - - let toView = transitionContext.view(forKey: .to)! - transitionContext.containerView.addSubview(toView) - - let maskingView = UIView() - - let fullMaskFrame = CGRect(x: windowFrame.minX, y: maskFrame.minY, width: windowFrame.width, height: maskFrame.height) - let path = UIBezierPath(rect: fullMaskFrame) - let maskLayer = CAShapeLayer() - maskLayer.path = path.cgPath - maskingView.layer.mask = maskLayer - - maskingView.addSubview(imageView) - transitionContext.containerView.addSubview(maskingView) - - UIView.animate( - withDuration: duration, - delay:0.0, - usingSpringWithDamping: 0.8, - initialSpringVelocity: 0.2, - animations: { - imageView.frame = self.originFrame - }, completion: { _ in - if let controller = self.webViewController { - controller.showClickedImage() { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - imageView.removeFromSuperview() - transitionContext.completeTransition(true) - } - } - } else { - imageView.removeFromSuperview() - transitionContext.completeTransition(true) - } - }) - } - -} diff --git a/Multiplatform/iOS/Article/ImageViewController.swift b/Multiplatform/iOS/Article/ImageViewController.swift deleted file mode 100644 index 4e9ae9466..000000000 --- a/Multiplatform/iOS/Article/ImageViewController.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// ImageViewController.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit - -class ImageViewController: UIViewController { - - @IBOutlet weak var closeButton: UIButton! - @IBOutlet weak var shareButton: UIButton! - @IBOutlet weak var imageScrollView: ImageScrollView! - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var titleBackground: UIVisualEffectView! - @IBOutlet weak var titleLeading: NSLayoutConstraint! - @IBOutlet weak var titleTrailing: NSLayoutConstraint! - - var image: UIImage! - var imageTitle: String? - var zoomedFrame: CGRect { - return imageScrollView.zoomedFrame - } - - override func viewDidLoad() { - super.viewDidLoad() - - closeButton.imageView?.contentMode = .scaleAspectFit - closeButton.accessibilityLabel = NSLocalizedString("Close", comment: "Close") - shareButton.accessibilityLabel = NSLocalizedString("Share", comment: "Share") - - imageScrollView.setup() - imageScrollView.imageScrollViewDelegate = self - imageScrollView.imageContentMode = .aspectFit - imageScrollView.initialOffset = .center - imageScrollView.display(image: image) - - titleLabel.text = imageTitle ?? "" - layoutTitleLabel() - - guard imageTitle != "" else { - titleBackground.removeFromSuperview() - return - } - titleBackground.layer.cornerRadius = 6 - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - coordinator.animate(alongsideTransition: { [weak self] context in - self?.imageScrollView.resize() - }) - } - - @IBAction func share(_ sender: Any) { - guard let image = image else { return } - let activityViewController = UIActivityViewController(activityItems: [image], applicationActivities: nil) - activityViewController.popoverPresentationController?.sourceView = shareButton - activityViewController.popoverPresentationController?.sourceRect = shareButton.bounds - present(activityViewController, animated: true) - } - - @IBAction func done(_ sender: Any) { - dismiss(animated: true) - } - - private func layoutTitleLabel(){ - let width = view.frame.width - let multiplier = UIDevice.current.userInterfaceIdiom == .pad ? CGFloat(0.1) : CGFloat(0.04) - titleLeading.constant += width * multiplier - titleTrailing.constant -= width * multiplier - titleLabel.layoutIfNeeded() - } -} - -// MARK: ImageScrollViewDelegate - -extension ImageViewController: ImageScrollViewDelegate { - - func imageScrollViewDidGestureSwipeUp(imageScrollView: ImageScrollView) { - dismiss(animated: true) - } - - func imageScrollViewDidGestureSwipeDown(imageScrollView: ImageScrollView) { - dismiss(animated: true) - } - - -} - diff --git a/Multiplatform/iOS/Article/OpenInSafariActivity.swift b/Multiplatform/iOS/Article/OpenInSafariActivity.swift deleted file mode 100644 index 5f76c768d..000000000 --- a/Multiplatform/iOS/Article/OpenInSafariActivity.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// OpenInSafariActivity.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/13/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit - -class OpenInSafariActivity: UIActivity { - - private var activityItems: [Any]? - - override var activityTitle: String? { - return NSLocalizedString("Open in Safari", comment: "Open in Safari") - } - - override var activityImage: UIImage? { - return UIImage(systemName: "safari", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)) - } - - override var activityType: UIActivity.ActivityType? { - return UIActivity.ActivityType(rawValue: "com.rancharo.NetNewsWire-Evergreen.safari") - } - - override class var activityCategory: UIActivity.Category { - return .action - } - - override func canPerform(withActivityItems activityItems: [Any]) -> Bool { - return true - } - - override func prepare(withActivityItems activityItems: [Any]) { - self.activityItems = activityItems - } - - override func perform() { - guard let url = activityItems?.first(where: { $0 is URL }) as? URL else { - activityDidFinish(false) - return - } - - UIApplication.shared.open(url, options: [:], completionHandler: nil) - activityDidFinish(true) - } - -} diff --git a/Multiplatform/iOS/Article/TitleActivityItemSource.swift b/Multiplatform/iOS/Article/TitleActivityItemSource.swift deleted file mode 100644 index 6eaf1f520..000000000 --- a/Multiplatform/iOS/Article/TitleActivityItemSource.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// TitleActivityItemSource.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/13/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit - -class TitleActivityItemSource: NSObject, UIActivityItemSource { - - private let title: String? - - init(title: String?) { - self.title = title - } - - func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { - return title as Any - } - - func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - guard let activityType = activityType, - let title = title else { - return NSNull() - } - - switch activityType.rawValue { - case "com.omnigroup.OmniFocus3.iOS.QuickEntry", - "com.culturedcode.ThingsiPhone.ShareExtension", - "com.tapbots.Tweetbot4.shareextension", - "com.tapbots.Tweetbot6.shareextension", - "com.buffer.buffer.Buffer": - return title - default: - return NSNull() - } - } - -} diff --git a/Multiplatform/iOS/Article/WebViewController.swift b/Multiplatform/iOS/Article/WebViewController.swift deleted file mode 100644 index 15a72f3f5..000000000 --- a/Multiplatform/iOS/Article/WebViewController.swift +++ /dev/null @@ -1,765 +0,0 @@ -// -// WebViewController.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import UIKit -import WebKit -import RSCore -import Account -import Articles -import SafariServices -import MessageUI - -protocol WebViewControllerDelegate: AnyObject { - func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState) -} - -class WebViewController: UIViewController { - - private struct MessageName { - static let imageWasClicked = "imageWasClicked" - static let imageWasShown = "imageWasShown" - static let showFeedInspector = "showFeedInspector" - } - - private var topShowBarsView: UIView! - private var bottomShowBarsView: UIView! - private var topShowBarsViewConstraint: NSLayoutConstraint! - private var bottomShowBarsViewConstraint: NSLayoutConstraint! - - private var webView: PreloadedWebView? { - guard view.subviews.count > 0 else { return nil } - return view.subviews[0] as? PreloadedWebView - } - -// private lazy var contextMenuInteraction = UIContextMenuInteraction(delegate: self) - private var isFullScreenAvailable: Bool { - return AppDefaults.shared.articleFullscreenAvailable && traitCollection.userInterfaceIdiom == .phone // && coordinator.isRootSplitCollapsed - } - private lazy var transition = ImageTransition(controller: self) - private var clickedImageCompletion: (() -> Void)? - - private var articleExtractor: ArticleExtractor? = nil - var extractedArticle: ExtractedArticle? { - didSet { - windowScrollY = 0 - } - } - var isShowingExtractedArticle = false - - var articleExtractorButtonState: ArticleExtractorButtonState = .off { - didSet { - delegate?.webViewController(self, articleExtractorButtonStateDidUpdate: articleExtractorButtonState) - } - } - - var sceneModel: SceneModel? - weak var delegate: WebViewControllerDelegate? - - private(set) var article: Article? - - let scrollPositionQueue = CoalescingQueue(name: "Article Scroll Position", interval: 0.3, maxInterval: 0.3) - var windowScrollY = 0 - - override func viewDidLoad() { - super.viewDidLoad() - - NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - - // Configure the tap zones -// configureTopShowBarsView() -// configureBottomShowBarsView() - - loadWebView() - - } - - // MARK: Notifications - - @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { - reloadArticleImage() - } - - @objc func avatarDidBecomeAvailable(_ note: Notification) { - reloadArticleImage() - } - - @objc func faviconDidBecomeAvailable(_ note: Notification) { - reloadArticleImage() - } - - // MARK: Actions - -// @objc func showBars(_ sender: Any) { -// showBars() -// } - - // MARK: API - - func setArticle(_ article: Article?, updateView: Bool = true) { - stopArticleExtractor() - - if article != self.article { - self.article = article - if updateView { - if article?.webFeed?.isArticleExtractorAlwaysOn ?? false { - startArticleExtractor() - } - windowScrollY = 0 - loadWebView() - } - } - - } - - func focus() { - webView?.becomeFirstResponder() - } - - func canScrollDown() -> Bool { - guard let webView = webView else { return false } - return webView.scrollView.contentOffset.y < finalScrollPosition() - } - - func scrollPageDown() { - guard let webView = webView else { return } - - let overlap = 2 * UIFont.systemFont(ofSize: UIFont.systemFontSize).lineHeight * UIScreen.main.scale - let scrollToY: CGFloat = { - let fullScroll = webView.scrollView.contentOffset.y + webView.scrollView.layoutMarginsGuide.layoutFrame.height - overlap - let final = finalScrollPosition() - return fullScroll < final ? fullScroll : final - }() - - let convertedPoint = self.view.convert(CGPoint(x: 0, y: 0), to: webView.scrollView) - let scrollToPoint = CGPoint(x: convertedPoint.x, y: scrollToY) - webView.scrollView.setContentOffset(scrollToPoint, animated: true) - } - - func hideClickedImage() { - webView?.evaluateJavaScript("hideClickedImage();") - } - - func showClickedImage(completion: @escaping () -> Void) { - clickedImageCompletion = completion - webView?.evaluateJavaScript("showClickedImage();") - } - - func fullReload() { - loadWebView(replaceExistingWebView: true) - } - -// func showBars() { -// AppDefaults.shared.articleFullscreenEnabled = false -// coordinator.showStatusBar() -// topShowBarsViewConstraint?.constant = 0 -// bottomShowBarsViewConstraint?.constant = 0 -// navigationController?.setNavigationBarHidden(false, animated: true) -// navigationController?.setToolbarHidden(false, animated: true) -// configureContextMenuInteraction() -// } -// -// func hideBars() { -// if isFullScreenAvailable { -// AppDefaults.shared.articleFullscreenEnabled = true -// coordinator.hideStatusBar() -// topShowBarsViewConstraint?.constant = -44.0 -// bottomShowBarsViewConstraint?.constant = 44.0 -// navigationController?.setNavigationBarHidden(true, animated: true) -// navigationController?.setToolbarHidden(true, animated: true) -// configureContextMenuInteraction() -// } -// } - - func toggleArticleExtractor() { - - guard let article = article else { - return - } - - guard articleExtractor?.state != .processing else { - stopArticleExtractor() - loadWebView() - return - } - - guard !isShowingExtractedArticle else { - isShowingExtractedArticle = false - loadWebView() - articleExtractorButtonState = .off - return - } - - if let articleExtractor = articleExtractor { - if article.preferredLink == articleExtractor.articleLink { - isShowingExtractedArticle = true - loadWebView() - articleExtractorButtonState = .on - } - } else { - startArticleExtractor() - } - - } - - func stopArticleExtractorIfProcessing() { - if articleExtractor?.state == .processing { - stopArticleExtractor() - } - } - - func stopWebViewActivity() { - if let webView = webView { - stopMediaPlayback(webView) - cancelImageLoad(webView) - } - } - -} - -// MARK: ArticleExtractorDelegate - -extension WebViewController: ArticleExtractorDelegate { - - func articleExtractionDidFail(with: Error) { - stopArticleExtractor() - articleExtractorButtonState = .error - loadWebView() - } - - func articleExtractionDidComplete(extractedArticle: ExtractedArticle) { - if articleExtractor?.state != .cancelled { - self.extractedArticle = extractedArticle - isShowingExtractedArticle = true - loadWebView() - articleExtractorButtonState = .on - } - } - -} - -// MARK: UIContextMenuInteractionDelegate - -//extension WebViewController: UIContextMenuInteractionDelegate { -// func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { -// -// return UIContextMenuConfiguration(identifier: nil, previewProvider: contextMenuPreviewProvider) { [weak self] suggestedActions in -// guard let self = self else { return nil } -// var actions = [UIAction]() -// -// if let action = self.prevArticleAction() { -// actions.append(action) -// } -// if let action = self.nextArticleAction() { -// actions.append(action) -// } -// if let action = self.toggleReadAction() { -// actions.append(action) -// } -// actions.append(self.toggleStarredAction()) -// if let action = self.nextUnreadArticleAction() { -// actions.append(action) -// } -// actions.append(self.toggleArticleExtractorAction()) -// actions.append(self.shareAction()) -// -// return UIMenu(title: "", children: actions) -// } -// } -// -// func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { -// coordinator.showBrowserForCurrentArticle() -// } -// -//} - -// MARK: WKNavigationDelegate - -extension WebViewController: WKNavigationDelegate { - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - for (index, view) in view.subviews.enumerated() { - if index != 0, let oldWebView = view as? PreloadedWebView { - oldWebView.removeFromSuperview() - } - } - } - - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - - if navigationAction.navigationType == .linkActivated { - guard let url = navigationAction.request.url else { - decisionHandler(.allow) - return - } - - let components = URLComponents(url: url, resolvingAgainstBaseURL: false) - if components?.scheme == "http" || components?.scheme == "https" { - decisionHandler(.cancel) - - // If the resource cannot be opened with an installed app, present the web view. - UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { didOpen in - assert(Thread.isMainThread) - guard didOpen == false else { - return - } - let vc = SFSafariViewController(url: url) - self.present(vc, animated: true) - } - } else if components?.scheme == "mailto" { - decisionHandler(.cancel) - - guard let emailAddress = url.percentEncodedEmailAddress else { - return - } - - if UIApplication.shared.canOpenURL(emailAddress) { - UIApplication.shared.open(emailAddress, options: [.universalLinksOnly : false], completionHandler: nil) - } else { - let alert = UIAlertController(title: NSLocalizedString("Error", comment: "Error"), message: NSLocalizedString("This device cannot send emails.", comment: "This device cannot send emails."), preferredStyle: .alert) - alert.addAction(.init(title: NSLocalizedString("Dismiss", comment: "Dismiss"), style: .cancel, handler: nil)) - self.present(alert, animated: true, completion: nil) - } - } else if components?.scheme == "tel" { - decisionHandler(.cancel) - - if UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url, options: [.universalLinksOnly : false], completionHandler: nil) - } - - } else { - decisionHandler(.allow) - } - } else { - decisionHandler(.allow) - } - } - - func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { - fullReload() - } - -} - -// MARK: WKUIDelegate - -extension WebViewController: WKUIDelegate { - func webView(_ webView: WKWebView, contextMenuForElement elementInfo: WKContextMenuElementInfo, willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating) { - // We need to have at least an unimplemented WKUIDelegate assigned to the WKWebView. This makes the - // link preview launch Safari when the link preview is tapped. In theory, you shoud be able to get - // the link from the elementInfo above and transition to SFSafariViewController instead of launching - // Safari. As the time of this writing, the link in elementInfo is always nil. ¯\_(ツ)_/¯ - } -} - -// MARK: WKScriptMessageHandler - -extension WebViewController: WKScriptMessageHandler { - - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - switch message.name { - case MessageName.imageWasShown: - clickedImageCompletion?() - case MessageName.imageWasClicked: - imageWasClicked(body: message.body as? String) - case MessageName.showFeedInspector: - return -// if let webFeed = article?.webFeed { -// coordinator.showFeedInspector(for: webFeed) -// } - default: - return - } - } - -} - -// MARK: UIViewControllerTransitioningDelegate - -extension WebViewController: UIViewControllerTransitioningDelegate { - - func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - transition.presenting = true - return transition - } - - func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - transition.presenting = false - return transition - } -} - -// MARK: - -extension WebViewController: UIScrollViewDelegate { - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - scrollPositionQueue.add(self, #selector(scrollPositionDidChange)) - } - - @objc func scrollPositionDidChange() { - webView?.evaluateJavaScript("window.scrollY") { (scrollY, error) in - guard error == nil else { return } - let javascriptScrollY = scrollY as? Int ?? 0 - // I don't know why this value gets returned sometimes, but it is in error - guard javascriptScrollY != 33554432 else { return } - self.windowScrollY = javascriptScrollY - } - } - -} - -// MARK: JSON - -private struct ImageClickMessage: Codable { - let x: Float - let y: Float - let width: Float - let height: Float - let imageTitle: String? - let imageURL: String -} - -// MARK: Private - -private extension WebViewController { - - func loadWebView(replaceExistingWebView: Bool = false) { - guard isViewLoaded else { return } - - if !replaceExistingWebView, let webView = webView { - self.renderPage(webView) - return - } - - sceneModel?.webViewProvider?.dequeueWebView() { webView in - - webView.ready { - - // Add the webview - webView.translatesAutoresizingMaskIntoConstraints = false - self.view.insertSubview(webView, at: 0) - NSLayoutConstraint.activate([ - self.view.leadingAnchor.constraint(equalTo: webView.leadingAnchor), - self.view.trailingAnchor.constraint(equalTo: webView.trailingAnchor), - self.view.topAnchor.constraint(equalTo: webView.topAnchor), - self.view.bottomAnchor.constraint(equalTo: webView.bottomAnchor) - ]) - - // UISplitViewController reports the wrong size to WKWebView which can cause horizontal - // rubberbanding on the iPad. This interferes with our UIPageViewController preventing - // us from easily swiping between WKWebViews. This hack fixes that. - webView.scrollView.contentInset = UIEdgeInsets(top: 0, left: -1, bottom: 0, right: 0) - - webView.scrollView.setZoomScale(1.0, animated: false) - - self.view.setNeedsLayout() - self.view.layoutIfNeeded() - - // Configure the webview - webView.navigationDelegate = self - webView.uiDelegate = self - webView.scrollView.delegate = self - // self.configureContextMenuInteraction() - - webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked) - webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown) - webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.showFeedInspector) - - self.renderPage(webView) - - } - - } - - } - - func renderPage(_ webView: PreloadedWebView?) { - guard let webView = webView else { return } - - let theme = ArticleThemesManager.shared.currentTheme - let rendering: ArticleRenderer.Rendering - - if let articleExtractor = articleExtractor, articleExtractor.state == .processing { - rendering = ArticleRenderer.loadingHTML(theme: theme) - } else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = article { - rendering = ArticleRenderer.articleHTML(article: article, theme: theme) - } else if let article = article, let extractedArticle = extractedArticle { - if isShowingExtractedArticle { - rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme) - } else { - rendering = ArticleRenderer.articleHTML(article: article, theme: theme) - } - } else if let article = article { - rendering = ArticleRenderer.articleHTML(article: article, theme: theme) - } else { - rendering = ArticleRenderer.noSelectionHTML(theme: theme) - } - - let substitutions = [ - "title": rendering.title, - "baseURL": rendering.baseURL, - "style": rendering.style, - "body": rendering.html, - "windowScrollY": String(windowScrollY) - ] - - let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions) - webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL) - - } - - func finalScrollPosition() -> CGFloat { - guard let webView = webView else { return 0 } - return webView.scrollView.contentSize.height - webView.scrollView.bounds.height + webView.scrollView.safeAreaInsets.bottom - } - - func startArticleExtractor() { - if let link = article?.preferredLink, let extractor = ArticleExtractor(link) { - extractor.delegate = self - extractor.process() - articleExtractor = extractor - articleExtractorButtonState = .animated - } - } - - func stopArticleExtractor() { - articleExtractor?.cancel() - articleExtractor = nil - isShowingExtractedArticle = false - articleExtractorButtonState = .off - } - - func reloadArticleImage() { - guard let article = article else { return } - - var components = URLComponents() - components.scheme = ArticleRenderer.imageIconScheme - components.path = article.articleID - - if let imageSrc = components.string { - webView?.evaluateJavaScript("reloadArticleImage(\"\(imageSrc)\")") - } - } - - func imageWasClicked(body: String?) { - guard let webView = webView, - let body = body, - let data = body.data(using: .utf8), - let clickMessage = try? JSONDecoder().decode(ImageClickMessage.self, from: data), - let range = clickMessage.imageURL.range(of: ";base64,") - else { return } - - let base64Image = String(clickMessage.imageURL.suffix(from: range.upperBound)) - if let imageData = Data(base64Encoded: base64Image), let image = UIImage(data: imageData) { - - let y = CGFloat(clickMessage.y) + webView.safeAreaInsets.top - let rect = CGRect(x: CGFloat(clickMessage.x), y: y, width: CGFloat(clickMessage.width), height: CGFloat(clickMessage.height)) - transition.originFrame = webView.convert(rect, to: nil) - - if navigationController?.navigationBar.isHidden ?? false { - transition.maskFrame = webView.convert(webView.frame, to: nil) - } else { - transition.maskFrame = webView.convert(webView.safeAreaLayoutGuide.layoutFrame, to: nil) - } - - transition.originImage = image - -// coordinator.showFullScreenImage(image: image, imageTitle: clickMessage.imageTitle, transitioningDelegate: self) - } - } - - func stopMediaPlayback(_ webView: WKWebView) { - webView.evaluateJavaScript("stopMediaPlayback();") - } - - func cancelImageLoad(_ webView: WKWebView) { - webView.evaluateJavaScript("cancelImageLoad();") - } - -// func configureTopShowBarsView() { -// topShowBarsView = UIView() -// topShowBarsView.backgroundColor = .clear -// topShowBarsView.translatesAutoresizingMaskIntoConstraints = false -// view.addSubview(topShowBarsView) -// -// if AppDefaults.shared.articleFullscreenEnabled { -// topShowBarsViewConstraint = view.topAnchor.constraint(equalTo: topShowBarsView.bottomAnchor, constant: -44.0) -// } else { -// topShowBarsViewConstraint = view.topAnchor.constraint(equalTo: topShowBarsView.bottomAnchor, constant: 0.0) -// } -// -// NSLayoutConstraint.activate([ -// topShowBarsViewConstraint, -// view.leadingAnchor.constraint(equalTo: topShowBarsView.leadingAnchor), -// view.trailingAnchor.constraint(equalTo: topShowBarsView.trailingAnchor), -// topShowBarsView.heightAnchor.constraint(equalToConstant: 44.0) -// ]) -// topShowBarsView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showBars(_:)))) -// } -// -// func configureBottomShowBarsView() { -// bottomShowBarsView = UIView() -// topShowBarsView.backgroundColor = .clear -// bottomShowBarsView.translatesAutoresizingMaskIntoConstraints = false -// view.addSubview(bottomShowBarsView) -// if AppDefaults.shared.articleFullscreenEnabled { -// bottomShowBarsViewConstraint = view.bottomAnchor.constraint(equalTo: bottomShowBarsView.topAnchor, constant: 44.0) -// } else { -// bottomShowBarsViewConstraint = view.bottomAnchor.constraint(equalTo: bottomShowBarsView.topAnchor, constant: 0.0) -// } -// NSLayoutConstraint.activate([ -// bottomShowBarsViewConstraint, -// view.leadingAnchor.constraint(equalTo: bottomShowBarsView.leadingAnchor), -// view.trailingAnchor.constraint(equalTo: bottomShowBarsView.trailingAnchor), -// bottomShowBarsView.heightAnchor.constraint(equalToConstant: 44.0) -// ]) -// bottomShowBarsView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showBars(_:)))) -// } - -// func configureContextMenuInteraction() { -// if isFullScreenAvailable { -// if navigationController?.isNavigationBarHidden ?? false { -// webView?.addInteraction(contextMenuInteraction) -// } else { -// webView?.removeInteraction(contextMenuInteraction) -// } -// } -// } -// -// func contextMenuPreviewProvider() -> UIViewController { -// let previewProvider = UIStoryboard.main.instantiateController(ofType: ContextMenuPreviewViewController.self) -// previewProvider.article = article -// return previewProvider -// } -// -// func prevArticleAction() -> UIAction? { -// guard coordinator.isPrevArticleAvailable else { return nil } -// let title = NSLocalizedString("Previous Article", comment: "Previous Article") -// return UIAction(title: title, image: AppAssets.prevArticleImage) { [weak self] action in -// self?.coordinator.selectPrevArticle() -// } -// } -// -// func nextArticleAction() -> UIAction? { -// guard coordinator.isNextArticleAvailable else { return nil } -// let title = NSLocalizedString("Next Article", comment: "Next Article") -// return UIAction(title: title, image: AppAssets.nextArticleImage) { [weak self] action in -// self?.coordinator.selectNextArticle() -// } -// } -// -// func toggleReadAction() -> UIAction? { -// guard let article = article, !article.status.read || article.isAvailableToMarkUnread else { return nil } -// -// let title = article.status.read ? NSLocalizedString("Mark as Unread", comment: "Mark as Unread") : NSLocalizedString("Mark as Read", comment: "Mark as Read") -// let readImage = article.status.read ? AppAssets.circleClosedImage : AppAssets.circleOpenImage -// return UIAction(title: title, image: readImage) { [weak self] action in -// self?.coordinator.toggleReadForCurrentArticle() -// } -// } -// -// func toggleStarredAction() -> UIAction { -// let starred = article?.status.starred ?? false -// let title = starred ? NSLocalizedString("Mark as Unstarred", comment: "Mark as Unstarred") : NSLocalizedString("Mark as Starred", comment: "Mark as Starred") -// let starredImage = starred ? AppAssets.starOpenImage : AppAssets.starClosedImage -// return UIAction(title: title, image: starredImage) { [weak self] action in -// self?.coordinator.toggleStarredForCurrentArticle() -// } -// } -// -// func nextUnreadArticleAction() -> UIAction? { -// guard coordinator.isAnyUnreadAvailable else { return nil } -// let title = NSLocalizedString("Next Unread Article", comment: "Next Unread Article") -// return UIAction(title: title, image: AppAssets.nextUnreadArticleImage) { [weak self] action in -// self?.coordinator.selectNextUnread() -// } -// } -// -// func toggleArticleExtractorAction() -> UIAction { -// let extracted = articleExtractorButtonState == .on -// let title = extracted ? NSLocalizedString("Show Feed Article", comment: "Show Feed Article") : NSLocalizedString("Show Reader View", comment: "Show Reader View") -// let extractorImage = extracted ? AppAssets.articleExtractorOffSF : AppAssets.articleExtractorOnSF -// return UIAction(title: title, image: extractorImage) { [weak self] action in -// self?.toggleArticleExtractor() -// } -// } -// -// func shareAction() -> UIAction { -// let title = NSLocalizedString("Share", comment: "Share") -// return UIAction(title: title, image: AppAssets.shareImage) { [weak self] action in -// self?.showActivityDialog() -// } -// } - -} - -// MARK: Find in Article - -private struct FindInArticleOptions: Codable { - var text: String - var caseSensitive = false - var regex = false -} - -internal struct FindInArticleState: Codable { - struct WebViewClientRect: Codable { - let x: Double - let y: Double - let width: Double - let height: Double - } - - struct FindInArticleResult: Codable { - let rects: [WebViewClientRect] - let bounds: WebViewClientRect - let index: UInt - let matchGroups: [String] - } - - let index: UInt? - let results: [FindInArticleResult] - let count: UInt -} - -extension WebViewController { - - func searchText(_ searchText: String, completionHandler: @escaping (FindInArticleState) -> Void) { - guard let json = try? JSONEncoder().encode(FindInArticleOptions(text: searchText)) else { - return - } - let encoded = json.base64EncodedString() - - webView?.evaluateJavaScript("updateFind(\"\(encoded)\")") { - (result, error) in - guard error == nil, - let b64 = result as? String, - let rawData = Data(base64Encoded: b64), - let findState = try? JSONDecoder().decode(FindInArticleState.self, from: rawData) else { - return - } - - completionHandler(findState) - } - } - - func endSearch() { - webView?.evaluateJavaScript("endFind()") - } - - func selectNextSearchResult() { - webView?.evaluateJavaScript("selectNextResult()") - } - - func selectPreviousSearchResult() { - webView?.evaluateJavaScript("selectPreviousResult()") - } - -} - diff --git a/Multiplatform/iOS/AttributedStringView.swift b/Multiplatform/iOS/AttributedStringView.swift deleted file mode 100644 index 9aa730fb6..000000000 --- a/Multiplatform/iOS/AttributedStringView.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// AttributedStringView.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 9/16/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct AttributedStringView: UIViewRepresentable { - - let string: NSAttributedString - let preferredMaxLayoutWidth: CGFloat - - func makeUIView(context: Context) -> HackedTextView { - return HackedTextView() - } - - func updateUIView(_ view: HackedTextView, context: Context) { - view.attributedText = string - - view.preferredMaxLayoutWidth = preferredMaxLayoutWidth - view.isScrollEnabled = false - view.textContainer.lineBreakMode = .byWordWrapping - - view.isUserInteractionEnabled = true - view.adjustsFontForContentSizeCategory = true - view.font = .preferredFont(forTextStyle: .body) - view.textColor = UIColor.label - view.tintColor = AppAssets.accentColor - view.backgroundColor = UIColor.secondarySystemGroupedBackground - - view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - view.setContentCompressionResistancePriority(.required, for: .vertical) - } - -} - -class HackedTextView: UITextView { - var preferredMaxLayoutWidth = CGFloat.zero - override var intrinsicContentSize: CGSize { - return sizeThatFits(CGSize(width: preferredMaxLayoutWidth, height: .infinity)) - } -} diff --git a/Multiplatform/iOS/Info.plist b/Multiplatform/iOS/Info.plist deleted file mode 100644 index 5be8136b3..000000000 --- a/Multiplatform/iOS/Info.plist +++ /dev/null @@ -1,89 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSRequiresIPhoneOS - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - - UIApplicationSupportsIndirectInputEvents - - UILaunchScreen - - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - BGTaskSchedulerPermittedIdentifiers - - com.ranchero.NetNewsWire.FeedRefresh - - NSPhotoLibraryAddUsageDescription - Grant permission to save images from the article. - NSUserActivityTypes - - AddWebFeedIntent - NextUnread - ReadArticle - Restoration - SelectFeed - - UIBackgroundModes - - fetch - processing - remote-notification - - UserAgent - NetNewsWire (RSS Reader; https://netnewswire.com/) - OrganizationIdentifier - $(ORGANIZATION_IDENTIFIER) - DeveloperEntitlements - $(DEVELOPER_ENTITLEMENTS) - AppGroup - group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS - AppIdentifierPrefix - $(AppIdentifierPrefix) - LSApplicationQueriesSchemes - - mailto - - - diff --git a/Multiplatform/iOS/SafariView.swift b/Multiplatform/iOS/SafariView.swift deleted file mode 100644 index 62f1bc36c..000000000 --- a/Multiplatform/iOS/SafariView.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// SafariView.swift -// Multiplatform iOS -// -// Created by Stuart Breckenridge on 30/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import SafariServices - - -private final class Safari: UIViewControllerRepresentable { - - var urlToLoad: URL - - init(url: URL) { - self.urlToLoad = url - } - - func makeUIViewController(context: Context) -> SFSafariViewController { - let viewController = SFSafariViewController(url: urlToLoad) - viewController.delegate = context.coordinator - return viewController - } - - func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) { - - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, SFSafariViewControllerDelegate { - var parent: Safari - - init(_ parent: Safari) { - self.parent = parent - } - - func safariViewControllerDidFinish(_ controller: SFSafariViewController) { - - } - - } - -} - -struct SafariView: View { - - var url: URL - - var body: some View { - Safari(url: url) - } -} - -struct SafariView_Previews: PreviewProvider { - static var previews: some View { - SafariView(url: URL(string: "https://netnewswire.com/")!) - } -} diff --git a/Multiplatform/iOS/Settings/About/About.rtf b/Multiplatform/iOS/Settings/About/About.rtf deleted file mode 100644 index b8aa6f4e5..000000000 --- a/Multiplatform/iOS/Settings/About/About.rtf +++ /dev/null @@ -1,12 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2513 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande-Bold;} -{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red10\green96\blue255;} -{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cssrgb\c0\c47843\c100000\cname systemBlueColor;} -\margl1440\margr1440\vieww8340\viewh9300\viewkind0 -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li363\fi-364\pardirnatural\partightenfactor0 - -\f0\b\fs28 \cf2 By Brent Simmons and the Ranchero Software team -\fs22 \ -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0 -{\field{\*\fldinst{HYPERLINK "https://ranchero.com/netnewswire/"}}{\fldrslt -\fs28 \cf3 netnewswire.com}}} \ No newline at end of file diff --git a/Multiplatform/iOS/Settings/About/Credits.rtf b/Multiplatform/iOS/Settings/About/Credits.rtf deleted file mode 100644 index a4c868701..000000000 --- a/Multiplatform/iOS/Settings/About/Credits.rtf +++ /dev/null @@ -1,20 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2511 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} -{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} -{\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;} -\margl1440\margr1440\vieww14220\viewh13280\viewkind0 -\deftab720 -\pard\pardeftab720\li360\fi-360\sa60\partightenfactor0 - -\f0\fs22 \cf2 iOS app design: {\field{\*\fldinst{HYPERLINK "https://inessential.com/"}}{\fldrslt Brent Simmons}} and {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}\ -Lead iOS developer: {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}\ -App icon: {\field{\*\fldinst{HYPERLINK "https://twitter.com/BradEllis"}}{\fldrslt Brad Ellis}}\ -\pard\pardeftab720\li366\fi-367\sa60\partightenfactor0 -\cf2 Feedly syncing: {\field{\*\fldinst{HYPERLINK "https://twitter.com/kielgillard"}}{\fldrslt Kiel Gillard}}\ -Under-the-hood magic and CSS stylin\'92s: {\field{\*\fldinst{HYPERLINK "https://github.com/wevah"}}{\fldrslt Nate Weaver}}\ -\pard\pardeftab720\li362\fi-363\sa60\partightenfactor0 -\cf2 Newsfoot (JS footnote displayer): {\field{\*\fldinst{HYPERLINK "https://github.com/brehaut/"}}{\fldrslt Andrew Brehaut}}\ -\pard\pardeftab720\li355\fi-356\sa60\partightenfactor0 -\cf2 Help book: {\field{\*\fldinst{HYPERLINK "https://nostodnayr.net/"}}{\fldrslt Ryan Dotson}}\ -\pard\pardeftab720\li358\fi-359\sa60\partightenfactor0 -\cf2 And more {\field{\*\fldinst{HYPERLINK "https://github.com/brentsimmons/NetNewsWire/graphs/contributors"}}{\fldrslt contributors}}!} \ No newline at end of file diff --git a/Multiplatform/iOS/Settings/About/Dedication.rtf b/Multiplatform/iOS/Settings/About/Dedication.rtf deleted file mode 100644 index 974f1b818..000000000 --- a/Multiplatform/iOS/Settings/About/Dedication.rtf +++ /dev/null @@ -1,9 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2513 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} -{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} -{\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;} -\margl1440\margr1440\vieww9000\viewh8400\viewkind0 -\deftab720 -\pard\pardeftab720\sa60\partightenfactor0 - -\f0\fs22 \cf2 NetNewsWire 6 is dedicated to everyone working to save democracy around the world.} \ No newline at end of file diff --git a/Multiplatform/iOS/Settings/About/SettingsAboutModel.swift b/Multiplatform/iOS/Settings/About/SettingsAboutModel.swift deleted file mode 100644 index 4ef2565f0..000000000 --- a/Multiplatform/iOS/Settings/About/SettingsAboutModel.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// SettingsAboutModel.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -class SettingsAboutModel: ObservableObject { - - var about: NSAttributedString - var credits: NSAttributedString - var thanks: NSAttributedString - var dedication: NSAttributedString - - init() { - about = SettingsAboutModel.loadResource("About") - credits = SettingsAboutModel.loadResource("Credits") - thanks = SettingsAboutModel.loadResource("Thanks") - dedication = SettingsAboutModel.loadResource("Dedication") - } - - private static func loadResource(_ resource: String) -> NSAttributedString { - let url = Bundle.main.url(forResource: resource, withExtension: "rtf")! - return try! NSAttributedString(url: url, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil) - - } - -} diff --git a/Multiplatform/iOS/Settings/About/SettingsAboutView.swift b/Multiplatform/iOS/Settings/About/SettingsAboutView.swift deleted file mode 100644 index e4a02adb4..000000000 --- a/Multiplatform/iOS/Settings/About/SettingsAboutView.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// SettingsAboutView.swift -// NetNewsWire-iOS -// -// Created by Maurice Parker on 9/16/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Combine - -struct SettingsAboutView: View { - - @StateObject var viewModel = SettingsAboutModel() - - var body: some View { - GeometryReader { geometry in - List { - Text("NetNewsWire").font(.largeTitle) - AttributedStringView(string: self.viewModel.about, preferredMaxLayoutWidth: geometry.size.width - 20) - Section(header: Text("CREDITS")) { - AttributedStringView(string: self.viewModel.credits, preferredMaxLayoutWidth: geometry.size.width - 20) - } - Section(header: Text("THANKS")) { - AttributedStringView(string: self.viewModel.thanks, preferredMaxLayoutWidth: geometry.size.width - 20) - } - Section(header: Text("DEDICATION"), footer: Text("Copyright © 2002-2021 Brent Simmons").font(.footnote)) { - AttributedStringView(string: self.viewModel.dedication, preferredMaxLayoutWidth: geometry.size.width - 20) - } - }.listStyle(InsetGroupedListStyle()) - } - .navigationTitle(Text("About")) - } - -} - -struct SettingsAboutView_Previews: PreviewProvider { - static var previews: some View { - SettingsAboutView() - } -} diff --git a/Multiplatform/iOS/Settings/About/Thanks.rtf b/Multiplatform/iOS/Settings/About/Thanks.rtf deleted file mode 100644 index c3cde3b4c..000000000 --- a/Multiplatform/iOS/Settings/About/Thanks.rtf +++ /dev/null @@ -1,11 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2511 -\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} -{\colortbl;\red255\green255\blue255;\red0\green0\blue0;} -{\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;} -\margl1440\margr1440\vieww11780\viewh11640\viewkind0 -\deftab720 -\pard\pardeftab720\li365\fi-366\sa60\partightenfactor0 - -\f0\fs22 \cf2 Thanks to Sheila and my family; thanks to my friends in Seattle and around the globe; thanks to the ever-patient and ever-awesome NetNewsWire beta testers. \ -\pard\tx0\pardeftab720\li360\fi-361\sa60\partightenfactor0 -\cf2 Thanks to {\field{\*\fldinst{HYPERLINK "https://shapeof.com/"}}{\fldrslt Gus Mueller}} for {\field{\*\fldinst{HYPERLINK "https://github.com/ccgus/fmdb"}}{\fldrslt FMDB}} by {\field{\*\fldinst{HYPERLINK "http://flyingmeat.com/"}}{\fldrslt Flying Meat Software}}. Thanks to {\field{\*\fldinst{HYPERLINK "https://github.com"}}{\fldrslt GitHub}} and {\field{\*\fldinst{HYPERLINK "https://slack.com"}}{\fldrslt Slack}} for making open source collaboration easy and fun. Thanks to {\field{\*\fldinst{HYPERLINK "https://benubois.com/"}}{\fldrslt Ben Ubois}} at {\field{\*\fldinst{HYPERLINK "https://feedbin.com/"}}{\fldrslt Feedbin}} for all the extra help with syncing and article rendering \'97\'a0and for {\field{\*\fldinst{HYPERLINK "https://feedbin.com/blog/2019/03/11/the-future-of-full-content/"}}{\fldrslt hosting the server for the Reader view}}.} \ No newline at end of file diff --git a/Multiplatform/iOS/Settings/Accounts/AccountCredentialsError.swift b/Multiplatform/iOS/Settings/Accounts/AccountCredentialsError.swift deleted file mode 100644 index 87638aa23..000000000 --- a/Multiplatform/iOS/Settings/Accounts/AccountCredentialsError.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// AccountCredentialsError.swift -// Multiplatform iOS -// -// Created by Rizwan on 21/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation - -enum AccountCredentialsError: CustomStringConvertible, Equatable { - case none, keyChain, invalidCredentials, noNetwork, other(error: Error) - - var description: String { - switch self { - case .keyChain: - return NSLocalizedString("Keychain error while storing credentials.", comment: "") - case .invalidCredentials: - return NSLocalizedString("Invalid email/password combination.", comment: "") - case .noNetwork: - return NSLocalizedString("Network error. Try again later.", comment: "") - case .other(let error): - return NSLocalizedString(error.localizedDescription, comment: "Other add account error") - default: - return "" - } - } - - static func ==(lhs: AccountCredentialsError, rhs: AccountCredentialsError) -> Bool { - switch (lhs, rhs) { - case (.other(let lhsError), .other(let rhsError)): - return lhsError.localizedDescription == rhsError.localizedDescription - default: - return false - } - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/AccountHeaderImageView.swift b/Multiplatform/iOS/Settings/Accounts/AccountHeaderImageView.swift deleted file mode 100644 index 028df300f..000000000 --- a/Multiplatform/iOS/Settings/Accounts/AccountHeaderImageView.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// AccountHeaderImageView.swift -// Multiplatform iOS -// -// Created by Rizwan on 08/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import RSCore - -struct AccountHeaderImageView: View { - var image: RSImage - - var body: some View { - HStack(alignment: .center) { - Spacer() - Image(rsImage: image) - .resizable() - .scaledToFit() - .frame(height: 48, alignment: .center) - .foregroundColor(Color.primary) - Spacer() - } - .padding(16) - } -} - -struct AccountHeaderImageView_Previews: PreviewProvider { - static var previews: some View { - Group { - AccountHeaderImageView(image: AppAssets.image(for: .onMyMac)!) - AccountHeaderImageView(image: AppAssets.image(for: .feedbin)!) - AccountHeaderImageView(image: AppAssets.accountLocalPadImage) - } - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsAccountLabelView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsAccountLabelView.swift deleted file mode 100644 index 29e3faa16..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsAccountLabelView.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// SettingsAccountLabelView.swift -// Multiplatform iOS -// -// Created by Rizwan on 07/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import RSCore - -struct SettingsAccountLabelView: View { - let accountImage: RSImage? - let accountLabel: String - - var body: some View { - HStack { - Image(rsImage: accountImage!) - .resizable() - .scaledToFit() - .frame(width: 32, height: 32) - Text(verbatim: accountLabel).font(.title) - } - .foregroundColor(.primary).padding(4.0) - } -} - -struct SettingsAccountLabelView_Previews: PreviewProvider { - static var previews: some View { - List { - SettingsAccountLabelView( - accountImage: AppAssets.image(for: .onMyMac), - accountLabel: "On My Device" - ) - SettingsAccountLabelView( - accountImage: AppAssets.image(for: .feedbin), - accountLabel: "Feedbin" - ) - SettingsAccountLabelView( - accountImage: AppAssets.accountLocalPadImage, - accountLabel: "On My iPad" - ) - SettingsAccountLabelView( - accountImage: AppAssets.accountLocalPhoneImage, - accountLabel: "On My iPhone" - ) - } - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountModel.swift b/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountModel.swift deleted file mode 100644 index 5977bd775..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountModel.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// SettingsAddAccountModel.swift -// Multiplatform iOS -// -// Created by Rizwan on 09/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore - -class SettingsAddAccountModel: ObservableObject { - - struct SettingsAddAccount: Identifiable { - var id: Int { accountType.rawValue } - - let name: String - let accountType: AccountType - - var image: RSImage { - AppAssets.image(for: accountType)! - } - } - - @Published var accounts: [SettingsAddAccount] = [] - @Published var isAddPresented = false - @Published var selectedAccountType: AccountType? = nil { - didSet { - selectedAccountType != nil ? (isAddPresented = true) : (isAddPresented = false) - } - } - - init() { - self.accounts = [ - SettingsAddAccount(name: Account.defaultLocalAccountName, accountType: .onMyMac), - SettingsAddAccount(name: "Feedbin", accountType: .feedbin), - SettingsAddAccount(name: "Feedly", accountType: .feedly), - SettingsAddAccount(name: "Feed Wrangler", accountType: .feedWrangler), - SettingsAddAccount(name: "iCloud", accountType: .cloudKit), - SettingsAddAccount(name: "NewsBlur", accountType: .newsBlur), - SettingsAddAccount(name: "Fresh RSS", accountType: .freshRSS) - ] - } - -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountView.swift deleted file mode 100644 index c8855409e..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsAddAccountView.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SettingsAddAccountView.swift -// Multiplatform iOS -// -// Created by Rizwan on 07/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct SettingsAddAccountView: View { - @StateObject private var model = SettingsAddAccountModel() - - var body: some View { - List { - ForEach(model.accounts) { account in - Button(action: { - model.selectedAccountType = account.accountType - }) { - SettingsAccountLabelView( - accountImage: account.image, - accountLabel: account.name - ) - } - } - } - .listStyle(InsetGroupedListStyle()) - .sheet(isPresented: $model.isAddPresented) { - switch model.selectedAccountType! { - case .onMyMac: - AddLocalAccountView() - case .feedbin: - AddFeedbinAccountView() - case .cloudKit: - AddCloudKitAccountView() - case .feedWrangler: - AddFeedWranglerAccountView() - case .newsBlur: - AddNewsBlurAccountView() - case .feedly: - AddFeedlyAccountView() - default: - AddReaderAPIAccountView(accountType: model.selectedAccountType!) - } - } - .navigationBarTitle(Text("Add Account"), displayMode: .inline) - } -} - -struct SettingsAddAccountView_Previews: PreviewProvider { - static var previews: some View { - SettingsAddAccountView() - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsCloudKitAccountView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsCloudKitAccountView.swift deleted file mode 100644 index 814a9e0c7..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsCloudKitAccountView.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// SettingsCloudKitAccountView.swift -// Multiplatform iOS -// -// Created by Rizwan on 13/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct SettingsCloudKitAccountView: View { - @Environment(\.presentationMode) var presentationMode - - var body: some View { - NavigationView { - List { - Section(header: AccountHeaderImageView(image: AppAssets.image(for: .cloudKit)!)) { } - Section { - HStack { - Spacer() - Button(action: { self.addAccount() }) { - Text("Add Account") - } - Spacer() - } - } - } - .listStyle(InsetGroupedListStyle()) - .navigationBarTitle(Text(verbatim: "iCloud"), displayMode: .inline) - .navigationBarItems(leading: Button(action: { self.dismiss() }) { Text("Cancel") } ) - } - } - - private func addAccount() { - _ = AccountManager.shared.createAccount(type: .cloudKit) - dismiss() - } - - private func dismiss() { - presentationMode.wrappedValue.dismiss() - } -} - -struct SettingsCloudKitAccountView_Previews: PreviewProvider { - static var previews: some View { - SettingsCloudKitAccountView() - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountModel.swift b/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountModel.swift deleted file mode 100644 index d3bb0671a..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountModel.swift +++ /dev/null @@ -1,281 +0,0 @@ -// -// SettingsCredentialsAccountModel.swift -// Multiplatform iOS -// -// Created by Rizwan on 21/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import Account -import Secrets - -class SettingsCredentialsAccountModel: ObservableObject { - var account: Account? = nil - var accountType: AccountType - @Published var shouldDismiss: Bool = false - @Published var email: String = "" - @Published var password: String = "" - @Published var apiUrl: String = "" - @Published var busy: Bool = false - @Published var accountCredentialsError: AccountCredentialsError? { - didSet { - accountCredentialsError != AccountCredentialsError.none ? (showError = true) : (showError = false) - } - } - @Published var showError: Bool = false - @Published var showPassword: Bool = false - - init(account: Account) { - self.account = account - self.accountType = account.type - if let credentials = try? account.retrieveCredentials(type: .basic) { - self.email = credentials.username - self.password = credentials.secret - } - } - - init(accountType: AccountType) { - self.accountType = accountType - } - - var isUpdate: Bool { - return account != nil - } - - var isValid: Bool { - if apiUrlEnabled { - return !email.isEmpty && !password.isEmpty && !apiUrl.isEmpty - } - return !email.isEmpty && !password.isEmpty - } - - var accountName: String { - switch accountType { - case .onMyMac: - return Account.defaultLocalAccountName - case .cloudKit: - return "iCloud" - case .feedbin: - return "Feedbin" - case .feedly: - return "Feedly" - case .feedWrangler: - return "Feed Wrangler" - case .newsBlur: - return "NewsBlur" - default: - return "" - } - } - - var emailText: String { - return accountType == .newsBlur ? NSLocalizedString("Username or Email", comment: "") : NSLocalizedString("Email", comment: "") - } - - var apiUrlEnabled: Bool { - return accountType == .freshRSS - } - - func addAccount() { - switch accountType { - case .feedbin: - addFeedbinAccount() - case .feedWrangler: - addFeedWranglerAccount() - case .newsBlur: - addNewsBlurAccount() - case .freshRSS: - addFreshRSSAccount() - default: - return - } - } -} - -extension SettingsCredentialsAccountModel { - // MARK:- Feedbin - - func addFeedbinAccount() { - busy = true - accountCredentialsError = AccountCredentialsError.none - - let emailAddress = email.trimmingCharacters(in: .whitespaces) - let credentials = Credentials(type: .basic, username: emailAddress, secret: password) - - Account.validateCredentials(type: .feedbin, credentials: credentials) { (result) in - self.busy = false - - switch result { - case .success(let authenticated): - if (authenticated != nil) { - var newAccount = false - let workAccount: Account - if self.account == nil { - workAccount = AccountManager.shared.createAccount(type: .feedbin) - newAccount = true - } else { - workAccount = self.account! - } - - do { - do { - try workAccount.removeCredentials(type: .basic) - } catch {} - try workAccount.storeCredentials(credentials) - - if newAccount { - workAccount.refreshAll() { result in } - } - - self.shouldDismiss = true - } catch { - self.accountCredentialsError = AccountCredentialsError.keyChain - } - - } else { - self.accountCredentialsError = AccountCredentialsError.invalidCredentials - } - case .failure: - self.accountCredentialsError = AccountCredentialsError.noNetwork - } - } - } - - // MARK: FeedWrangler - - func addFeedWranglerAccount() { - busy = true - let credentials = Credentials(type: .feedWranglerBasic, username: email, secret: password) - - Account.validateCredentials(type: .feedWrangler, credentials: credentials) { [weak self] result in - guard let self = self else { return } - - self.busy = false - switch result { - case .success(let validatedCredentials): - guard let validatedCredentials = validatedCredentials else { - self.accountCredentialsError = .invalidCredentials - 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) - self.shouldDismiss = true - account.refreshAll(completion: { result in - switch result { - case .success: - break - case .failure(let error): - self.accountCredentialsError = .other(error: error) - } - }) - - } catch { - self.accountCredentialsError = .keyChain - } - - case .failure: - self.accountCredentialsError = .noNetwork - } - } - } - - // MARK:- NewsBlur - - func addNewsBlurAccount() { - busy = true - let credentials = Credentials(type: .newsBlurBasic, username: email, secret: password) - - Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in - - guard let self = self else { return } - - self.busy = false - - switch result { - case .success(let validatedCredentials): - - guard let validatedCredentials = validatedCredentials else { - self.accountCredentialsError = .invalidCredentials - 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) - self.shouldDismiss = true - account.refreshAll(completion: { result in - switch result { - case .success: - break - case .failure(let error): - self.accountCredentialsError = .other(error: error) - } - }) - - } catch { - self.accountCredentialsError = .keyChain - } - - case .failure: - self.accountCredentialsError = .noNetwork - } - } - } - - // MARK:- Fresh RSS - - func addFreshRSSAccount() { - busy = true - let credentials = Credentials(type: .readerBasic, username: email, secret: password) - - Account.validateCredentials(type: .freshRSS, credentials: credentials, endpoint: URL(string: apiUrl)!) { [weak self] result in - - guard let self = self else { return } - - self.busy = false - - switch result { - case .success(let validatedCredentials): - - guard let validatedCredentials = validatedCredentials else { - self.accountCredentialsError = .invalidCredentials - return - } - - let account = AccountManager.shared.createAccount(type: .freshRSS) - - do { - try account.removeCredentials(type: .readerBasic) - try account.removeCredentials(type: .readerAPIKey) - try account.storeCredentials(credentials) - try account.storeCredentials(validatedCredentials) - self.shouldDismiss = true - account.refreshAll(completion: { result in - switch result { - case .success: - break - case .failure(let error): - self.accountCredentialsError = .other(error: error) - } - }) - - } catch { - self.accountCredentialsError = .keyChain - } - - case .failure: - self.accountCredentialsError = .noNetwork - } - } - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountView.swift deleted file mode 100644 index 25b475c22..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsCredentialsAccountView.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// SettingsCredentialsAccountView.swift -// Multiplatform iOS -// -// Created by Rizwan on 21/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct SettingsCredentialsAccountView: View { - @Environment(\.presentationMode) var presentationMode - @ObservedObject var settingsModel: SettingsCredentialsAccountModel - - init(account: Account) { - self.settingsModel = SettingsCredentialsAccountModel(account: account) - } - - init(accountType: AccountType) { - self.settingsModel = SettingsCredentialsAccountModel(accountType: accountType) - } - - var body: some View { - NavigationView { - List { - Section(header: AccountHeaderImageView(image: AppAssets.image(for: settingsModel.accountType)!)) { - TextField(settingsModel.emailText, text: $settingsModel.email).textContentType(.emailAddress) - HStack { - if settingsModel.showPassword { - TextField("Password", text:$settingsModel.password) - } - else { - SecureField("Password", text: $settingsModel.password) - } - Button(action: { - settingsModel.showPassword.toggle() - }) { - Text(settingsModel.showPassword ? "Hide" : "Show") - } - } - if settingsModel.apiUrlEnabled { - TextField("API URL", text: $settingsModel.apiUrl) - } - } - Section(footer: errorFooter) { - HStack { - Spacer() - Button(action: { settingsModel.addAccount() }) { - if settingsModel.isUpdate { - Text("Update Account") - } else { - Text("Add Account") - } - } - .disabled(!settingsModel.isValid) - Spacer() - if settingsModel.busy { - ProgressView() - } - } - } - } - .listStyle(InsetGroupedListStyle()) - .disabled(settingsModel.busy) - .onReceive(settingsModel.$shouldDismiss, perform: { dismiss in - if dismiss == true { - presentationMode.wrappedValue.dismiss() - } - }) - .navigationBarTitle(Text(verbatim: settingsModel.accountName), displayMode: .inline) - .navigationBarItems(leading: - Button(action: { self.dismiss() }) { Text("Cancel") } - ) - } - } - - var errorFooter: some View { - HStack { - Spacer() - if settingsModel.showError { - Text(verbatim: settingsModel.accountCredentialsError!.description).foregroundColor(.red) - } - Spacer() - } - } - - private func dismiss() { - presentationMode.wrappedValue.dismiss() - } -} - -struct SettingsCredentialsAccountView_Previews: PreviewProvider { - static var previews: some View { - SettingsCredentialsAccountView(accountType: .feedbin) - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountModel.swift b/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountModel.swift deleted file mode 100644 index de4965ec5..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountModel.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SettingsDetailAccountModel.swift -// Multiplatform iOS -// -// Created by Rizwan on 08/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account -import RSCore - -class SettingsDetailAccountModel: ObservableObject { - let account: Account - @Published var name: String { - didSet { - account.name = name.isEmpty ? nil : name - } - } - @Published var isActive: Bool { - didSet { - account.isActive = isActive - } - } - - init(_ account: Account) { - self.account = account - self.name = account.name ?? "" - self.isActive = account.isActive - } - - var defaultName: String { - account.defaultName - } - - var nameForDisplay: String { - account.nameForDisplay - } - - var accountImage: RSImage { - AppAssets.image(for: account.type)! - } - - var isCredentialsAvailable: Bool { - return account.type != .onMyMac - } - - var isDeletable: Bool { - return AccountManager.shared.defaultAccount != account - } - - func delete() { - AccountManager.shared.deleteAccount(account) - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountView.swift deleted file mode 100644 index 0e2850526..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsDetailAccountView.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// SettingsDetailAccountView.swift -// Multiplatform iOS -// -// Created by Rizwan on 08/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Combine -import Account -import RSWeb -import RSCore - -struct SettingsDetailAccountView: View { - @Environment(\.presentationMode) var presentationMode - @ObservedObject var settingsModel: SettingsDetailAccountModel - @State private var isFeedbinCredentialsPresented = false - @State private var isDeleteAlertPresented = false - - init(_ account: Account) { - settingsModel = SettingsDetailAccountModel.init(account) - } - - var body: some View { - List { - Section(header:AccountHeaderImageView(image: settingsModel.accountImage)) { - HStack { - TextField(settingsModel.defaultName, text: $settingsModel.name) - } - Toggle(isOn: $settingsModel.isActive) { - Text("Active") - } - } - if settingsModel.isCredentialsAvailable { - Section { - HStack { - Spacer() - Button(action: { - self.isFeedbinCredentialsPresented.toggle() - }) { - Text("Credentials") - } - Spacer() - } - } - .sheet(isPresented: $isFeedbinCredentialsPresented) { - self.settingsCredentialsAccountView - } - } - if settingsModel.isDeletable { - Section { - HStack { - Spacer() - Button(action: { - self.isDeleteAlertPresented.toggle() - }) { - Text("Delete Account").foregroundColor(.red) - } - Spacer() - } - .alert(isPresented: $isDeleteAlertPresented) { - Alert( - title: Text("Are you sure you want to delete \"\(settingsModel.nameForDisplay)\"?"), - primaryButton: Alert.Button.default( - Text("Delete"), - action: { - self.settingsModel.delete() - self.dismiss() - }), - secondaryButton: Alert.Button.cancel() - ) - } - } - } - } - .listStyle(InsetGroupedListStyle()) - .navigationBarTitle(Text(verbatim: settingsModel.nameForDisplay), displayMode: .inline) - } - - var settingsCredentialsAccountView: SettingsCredentialsAccountView { - return SettingsCredentialsAccountView(account: settingsModel.account) - } - - func dismiss() { - presentationMode.wrappedValue.dismiss() - } -} - -struct SettingsDetailAccountView_Previews: PreviewProvider { - static var previews: some View { - return SettingsDetailAccountView(AccountManager.shared.defaultAccount) - } -} diff --git a/Multiplatform/iOS/Settings/Accounts/SettingsLocalAccountView.swift b/Multiplatform/iOS/Settings/Accounts/SettingsLocalAccountView.swift deleted file mode 100644 index c7753d6ac..000000000 --- a/Multiplatform/iOS/Settings/Accounts/SettingsLocalAccountView.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SettingsLocalAccountView.swift -// Multiplatform iOS -// -// Created by Rizwan on 07/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct SettingsLocalAccountView: View { - @Environment(\.presentationMode) var presentation - @State var name: String = "" - - var body: some View { - NavigationView { - List { - Section(header: AccountHeaderImageView(image: AppAssets.image(for: .onMyMac)!)) { - HStack { - TextField("Name", text: $name) - } - } - Section { - HStack { - Spacer() - Button(action: { self.addAccount() }) { - Text("Add Account") - } - Spacer() - } - } - } - .listStyle(InsetGroupedListStyle()) - .navigationBarTitle(Text(verbatim: Account.defaultLocalAccountName), displayMode: .inline) - .navigationBarItems(leading: Button(action: { self.dismiss() }) { Text("Cancel") } ) - } - } - - private func addAccount() { - let account = AccountManager.shared.createAccount(type: .onMyMac) - account.name = name - dismiss() - } - - private func dismiss() { - presentation.wrappedValue.dismiss() - } -} - -struct SettingsLocalAccountView_Previews: PreviewProvider { - static var previews: some View { - SettingsLocalAccountView() - } -} diff --git a/Multiplatform/iOS/Settings/ColorPaletteContainerView.swift b/Multiplatform/iOS/Settings/ColorPaletteContainerView.swift deleted file mode 100644 index 3cf4ee25a..000000000 --- a/Multiplatform/iOS/Settings/ColorPaletteContainerView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// ColorPaletteContainerView.swift -// Multiplatform iOS -// -// Created by Rizwan on 02/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct ColorPaletteContainerView: View { - private let colorPalettes = UserInterfaceColorPalette.allCases - @EnvironmentObject private var appSettings: AppDefaults - @Environment(\.presentationMode) var presentationMode - - var body: some View { - List { - ForEach.init(0 ..< colorPalettes.count) { index in - Button(action: { - onTapColorPalette(at:index) - }) { - ColorPaletteView(colorPalette: colorPalettes[index]) - } - } - } - .listStyle(InsetGroupedListStyle()) - .navigationBarTitle("Color Palette", displayMode: .inline) - } - - func onTapColorPalette(at index: Int) { - if let colorPalette = UserInterfaceColorPalette(rawValue: index) { - appSettings.userInterfaceColorPalette = colorPalette - } - self.presentationMode.wrappedValue.dismiss() - } -} - -struct ColorPaletteView: View { - var colorPalette: UserInterfaceColorPalette - @EnvironmentObject private var appSettings: AppDefaults - - var body: some View { - HStack { - Text(colorPalette.description).foregroundColor(.primary) - Spacer() - if colorPalette == appSettings.userInterfaceColorPalette { - Image(systemName: "checkmark") - .foregroundColor(.blue) - } - } - } -} - -struct ColorPaletteContainerView_Previews: PreviewProvider { - static var previews: some View { - NavigationView { - ColorPaletteContainerView() - } - } -} diff --git a/Multiplatform/iOS/Settings/FeedsSettingsModel.swift b/Multiplatform/iOS/Settings/FeedsSettingsModel.swift deleted file mode 100644 index 70da469d0..000000000 --- a/Multiplatform/iOS/Settings/FeedsSettingsModel.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// FeedsSettingsModel.swift -// Multiplatform iOS -// -// Created by Rizwan on 04/07/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import SwiftUI -import Account -import UniformTypeIdentifiers - -enum FeedsSettingsError: LocalizedError, Equatable { - case none, noActiveAccount, exportFailed(reason: String?), importFailed - - var errorDescription: String? { - switch self { - case .noActiveAccount: - return NSLocalizedString("You must have at least one active account.", comment: "Missing active account") - case .exportFailed(let reason): - return reason - case .importFailed: - return NSLocalizedString( - "We were unable to process the selected file. Please ensure that it is a properly formatted OPML file.", - comment: "Import Failed Message" - ) - default: - return nil - } - } - - var title: String? { - switch self { - case .noActiveAccount: - return NSLocalizedString("Error", comment: "Error Title") - case .exportFailed: - return NSLocalizedString("OPML Export Error", comment: "Export Failed") - case .importFailed: - return NSLocalizedString("Import Failed", comment: "Import Failed") - default: - return nil - } - } -} - -class FeedsSettingsModel: ObservableObject { - @Published var exportingFilePath = "" - @Published var feedsSettingsError: FeedsSettingsError? { - didSet { - feedsSettingsError != FeedsSettingsError.none ? (showError = true) : (showError = false) - } - } - @Published var showError: Bool = false - @Published var isImporting: Bool = false - @Published var isExporting: Bool = false - @Published var selectedAccount: Account? = nil - - let importingContentTypes: [UTType] = [UTType(filenameExtension: "opml"), UTType("public.xml")].compactMap { $0 } - - func checkForActiveAccount() -> Bool { - if AccountManager.shared.activeAccounts.count == 0 { - feedsSettingsError = .noActiveAccount - return false - } - return true - } - - func importOPML(account: Account?) { - selectedAccount = account - isImporting = true - } - - func exportOPML(account: Account?) { - selectedAccount = account - isExporting = true - } - - func generateExportURL() -> URL? { - guard let account = selectedAccount else { return nil } - let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) - let filename = "Subscriptions-\(accountName).opml" - let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(filename) - let opmlString = OPMLExporter.OPMLString(with: account, title: filename) - do { - try opmlString.write(to: tempFile, atomically: true, encoding: String.Encoding.utf8) - } catch { - feedsSettingsError = .exportFailed(reason: error.localizedDescription) - return nil - } - - return tempFile - } - - func processImportedFiles(_ urls: [URL]) { - urls.forEach{ - selectedAccount?.importOPML($0, completion: { [weak self] result in - switch result { - case .success: - break - case .failure: - self?.feedsSettingsError = .importFailed - break - } - }) - } - } -} - diff --git a/Multiplatform/iOS/Settings/SettingsModel.swift b/Multiplatform/iOS/Settings/SettingsModel.swift deleted file mode 100644 index 3c8218ae1..000000000 --- a/Multiplatform/iOS/Settings/SettingsModel.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// SettingsModel.swift -// Multiplatform iOS -// -// Created by Maurice Parker on 7/4/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import Foundation -import Account - -class SettingsModel: ObservableObject { - - enum HelpSites { - case netNewsWireHelp, netNewsWire, supportNetNewsWire, github, bugTracker, technotes, netNewsWireSlack, releaseNotes, none - - var url: URL? { - switch self { - case .netNewsWireHelp: - return URL(string: "https://netnewswire.com/help/ios/5.0/en/")! - case .netNewsWire: - return URL(string: "https://netnewswire.com/")! - case .supportNetNewsWire: - return URL(string: "https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown")! - case .github: - return URL(string: "https://github.com/brentsimmons/NetNewsWire")! - case .bugTracker: - return URL(string: "https://github.com/brentsimmons/NetNewsWire/issues")! - case .technotes: - return URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes")! - case .netNewsWireSlack: - return URL(string: "https://netnewswire.com/slack")! - case .releaseNotes: - return URL.releaseNotes - case .none: - return nil - } - } - } - - @Published var presentSheet: Bool = false - var accounts: [Account] { - get { - AccountManager.shared.sortedAccounts - } - set { - - } - } - - var activeAccounts: [Account] { - get { - AccountManager.shared.sortedActiveAccounts - } - set { - - } - } - - // MARK: Init - - init() { - NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange), name: .DisplayNameDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(userDidAddAccount), name: .UserDidAddAccount, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(userDidDeleteAccount), name: .UserDidDeleteAccount, object: nil) - } - - var selectedWebsite: HelpSites = .none { - didSet { - if selectedWebsite == .none { - presentSheet = false - } else { - presentSheet = true - } - } - } - - func refreshAccounts() { - objectWillChange.self.send() - } - - // MARK:- Notifications - - @objc func displayNameDidChange() { - refreshAccounts() - } - - @objc func userDidAddAccount() { - refreshAccounts() - } - - @objc func userDidDeleteAccount() { - refreshAccounts() - } -} diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift deleted file mode 100644 index 33c94ae25..000000000 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ /dev/null @@ -1,244 +0,0 @@ -// -// SettingsView.swift -// Multiplatform iOS -// -// Created by Stuart Breckenridge on 30/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Account - -struct SettingsView: View { - - @Environment(\.presentationMode) var presentationMode - - @StateObject private var viewModel = SettingsModel() - @StateObject private var feedsSettingsModel = FeedsSettingsModel() - @StateObject private var settings = AppDefaults.shared - - var body: some View { - NavigationView { - List { - systemSettings - accounts - importExport - timeline - articles - appearance - help - } - .listStyle(InsetGroupedListStyle()) - .navigationBarTitle("Settings", displayMode: .inline) - .navigationBarItems(leading: - HStack { - Button("Done") { - presentationMode.wrappedValue.dismiss() - } - } - ) - } - .fileImporter( - isPresented: $feedsSettingsModel.isImporting, - allowedContentTypes: feedsSettingsModel.importingContentTypes, - allowsMultipleSelection: true, - onCompletion: { result in - if let urls = try? result.get() { - feedsSettingsModel.processImportedFiles(urls) - } - } - ) - .fileMover(isPresented: $feedsSettingsModel.isExporting, - file: feedsSettingsModel.generateExportURL()) { _ in } - .sheet(isPresented: $viewModel.presentSheet, content: { - SafariView(url: viewModel.selectedWebsite.url!) - }) - } - - var systemSettings: some View { - Section(header: Text("Notifications, Badge, Data, & More"), content: { - Button(action: { - UIApplication.shared.open(URL(string: "\(UIApplication.openSettingsURLString)")!) - }, label: { - Text("Open System Settings").foregroundColor(.primary) - }) - }) - } - - var accounts: some View { - Section(header: Text("Accounts"), content: { - ForEach(0.. 1 { - NavigationLink("Import Subscriptions", destination: importOptions) - } - else { - Button(action:{ - if feedsSettingsModel.checkForActiveAccount() { - feedsSettingsModel.importOPML(account: viewModel.activeAccounts.first) - } - }) { - Text("Import Subscriptions") - .foregroundColor(.primary) - } - } - - if viewModel.accounts.count > 1 { - NavigationLink("Export Subscriptions", destination: exportOptions) - } - else { - Button(action:{ - feedsSettingsModel.exportOPML(account: viewModel.accounts.first) - }) { - Text("Export Subscriptions") - .foregroundColor(.primary) - } - } - Toggle("Confirm When Deleting", isOn: $settings.sidebarConfirmDelete) - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - }) - .alert(isPresented: $feedsSettingsModel.showError) { - Alert( - title: Text(feedsSettingsModel.feedsSettingsError!.title ?? "Oops"), - message: Text(feedsSettingsModel.feedsSettingsError!.localizedDescription), - dismissButton: Alert.Button.cancel({ - feedsSettingsModel.feedsSettingsError = FeedsSettingsError.none - })) - } - } - - var importOptions: some View { - List { - Section(header: Text("Choose an account to receive the imported feeds and folders"), content: { - ForEach(0.. String { - let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" - let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "" - return "NetNewsWire \(version) (Build \(build))" - } - -} - -struct SettingsView_Previews: PreviewProvider { - static var previews: some View { - SettingsView() - } -} diff --git a/Multiplatform/iOS/Settings/TimelineLayoutView.swift b/Multiplatform/iOS/Settings/TimelineLayoutView.swift deleted file mode 100644 index c0c47ccdb..000000000 --- a/Multiplatform/iOS/Settings/TimelineLayoutView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// TimelineLayoutView.swift -// Multiplatform iOS -// -// Created by Stuart Breckenridge on 1/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI - -struct TimelineLayoutView: View { - - @EnvironmentObject private var defaults: AppDefaults - - 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(spacing: 0) { - List { - Section(header: Text("Icon Size"), content: { - iconSize - }) - Section(header: Text("Number of Lines"), content: { - numberOfLines - }) } - .listStyle(InsetGroupedListStyle()) - - Divider() - timelineRowPreview.padding() - Divider() - } - .navigationBarTitle("Timeline Layout") - } - - var iconSize: some View { - Slider(value: $defaults.timelineIconDimensions, in: 20...60, step: 10, minimumValueLabel: Text("Small"), maximumValueLabel: Text("Large"), label: { - Text(String(defaults.timelineIconDimensions)) - }) - } - - var numberOfLines: some View { - Slider(value: $defaults.timelineNumberOfLines, in: 1...5, step: 1, minimumValueLabel: Text("1"), maximumValueLabel: Text("5"), label: { - Text("Article Title") - }) - - - } - - 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 TimelineLayout_Previews: PreviewProvider { - static var previews: some View { - TimelineLayoutView() - } -} diff --git a/Multiplatform/iOS/iOS-dev.entitlements b/Multiplatform/iOS/iOS-dev.entitlements deleted file mode 100644 index 05d04e805..000000000 --- a/Multiplatform/iOS/iOS-dev.entitlements +++ /dev/null @@ -1,14 +0,0 @@ - - - - - com.apple.security.application-groups - - group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS - - keychain-access-groups - - $(AppIdentifierPrefix)$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS - - - diff --git a/Multiplatform/iOS/iOS.entitlements b/Multiplatform/iOS/iOS.entitlements deleted file mode 100644 index 028d33157..000000000 --- a/Multiplatform/iOS/iOS.entitlements +++ /dev/null @@ -1,24 +0,0 @@ - - - - - aps-environment - development - com.apple.developer.icloud-container-identifiers - - iCloud.$(ORGANIZATION_IDENTIFIER).NetNewsWire - - com.apple.developer.icloud-services - - CloudKit - - com.apple.security.application-groups - - group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS - - keychain-access-groups - - $(AppIdentifierPrefix)$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS - - - diff --git a/Multiplatform/macOS/AppDelegate.swift b/Multiplatform/macOS/AppDelegate.swift deleted file mode 100644 index 8a41559df..000000000 --- a/Multiplatform/macOS/AppDelegate.swift +++ /dev/null @@ -1,285 +0,0 @@ -// -// AppDelegate.swift -// Multiplatform macOS -// -// Created by Maurice Parker on 6/28/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import AppKit -import os.log -import UserNotifications -import Articles -import RSWeb -import Account -import RSCore -import Secrets - -// If we're not going to import Sparkle, provide dummy protocols to make it easy -// for AppDelegate to comply -#if MAC_APP_STORE || TEST -protocol SPUStandardUserDriverDelegate {} -protocol SPUUpdaterDelegate {} -#else -import Sparkle -#endif - -var appDelegate: AppDelegate! - -class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate -{ - - private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") - - var userNotificationManager: UserNotificationManager! - var faviconDownloader: FaviconDownloader! - var imageDownloader: ImageDownloader! - var authorAvatarDownloader: AuthorAvatarDownloader! - var webFeedIconDownloader: WebFeedIconDownloader! - - var refreshTimer: AccountRefreshTimer? - var syncTimer: ArticleStatusSyncTimer? - - var shuttingDown = false { - didSet { - if shuttingDown { - refreshTimer?.shuttingDown = shuttingDown - refreshTimer?.invalidate() - syncTimer?.shuttingDown = shuttingDown - syncTimer?.invalidate() - } - } - } - - var unreadCount = 0 { - didSet { - if unreadCount != oldValue { - CoalescingQueue.standard.add(self, #selector(updateDockBadge)) - postUnreadCountDidChangeNotification() - } - } - } - - var appName: String! - - private let appMovementMonitor = RSAppMovementMonitor() - #if !MAC_APP_STORE && !TEST - var softwareUpdater: SPUUpdater! - #endif - - override init() { - super.init() - - SecretsManager.provider = Secrets() - AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!) - ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!) - FeedProviderManager.shared.delegate = ExtensionPointManager.shared - - NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) - NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(didWakeNotification(_:)), name: NSWorkspace.didWakeNotification, object: nil) - - appDelegate = self - } - - // MARK: - NSApplicationDelegate - - func applicationWillFinishLaunching(_ notification: Notification) { - // TODO: add Apple Events back in -// installAppleEventHandlers() - - CacheCleaner.purgeIfNecessary() - - // Try to establish a cache in the Caches folder, but if it fails for some reason fall back to a temporary dir - let cacheFolder: String - if let userCacheFolder = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path { - cacheFolder = userCacheFolder - } - else { - let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String) - cacheFolder = (NSTemporaryDirectory() as NSString).appendingPathComponent(bundleIdentifier) - } - - let faviconsFolder = (cacheFolder as NSString).appendingPathComponent("Favicons") - let faviconsFolderURL = URL(fileURLWithPath: faviconsFolder) - try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil) - faviconDownloader = FaviconDownloader(folder: faviconsFolder) - - let imagesFolder = (cacheFolder as NSString).appendingPathComponent("Images") - let imagesFolderURL = URL(fileURLWithPath: imagesFolder) - try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil) - imageDownloader = ImageDownloader(folder: imagesFolder) - - authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader) - webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder) - - appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String) - } - - func applicationDidFinishLaunching(_ note: Notification) { - - #if MAC_APP_STORE || TEST - checkForUpdatesMenuItem.isHidden = true - #else - // Initialize Sparkle... - let hostBundle = Bundle.main - let updateDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: self) - self.softwareUpdater = SPUUpdater(hostBundle: hostBundle, applicationBundle: hostBundle, userDriver: updateDriver, delegate: self) - - do { - try self.softwareUpdater.start() - } - catch { - NSLog("Failed to start software updater with error: \(error)") - } - #endif - - AppDefaults.registerDefaults() - let isFirstRun = AppDefaults.shared.isFirstRun() - if isFirstRun { - os_log(.debug, log: log, "Is first run.") - } - let localAccount = AccountManager.shared.defaultAccount - - if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() { - DefaultFeedsImporter.importDefaultFeeds(account: localAccount) - } - - - NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) - - DispatchQueue.main.async { - self.unreadCount = AccountManager.shared.unreadCount - } - - refreshTimer = AccountRefreshTimer() - syncTimer = ArticleStatusSyncTimer() - - NSApplication.shared.registerForRemoteNotifications() - - UNUserNotificationCenter.current().delegate = self - userNotificationManager = UserNotificationManager() - - // TODO: Add a debug menu -// if AppDefaults.showDebugMenu { -// refreshTimer!.update() -// syncTimer!.update() -// -// // The Web Inspector uses SPI and can never appear in a MAC_APP_STORE build. -// #if MAC_APP_STORE -// let debugMenu = debugMenuItem.submenu! -// let toggleWebInspectorItemIndex = debugMenu.indexOfItem(withTarget: self, andAction: #selector(toggleWebInspectorEnabled(_:))) -// if toggleWebInspectorItemIndex != -1 { -// debugMenu.removeItem(at: toggleWebInspectorItemIndex) -// } -// #endif -// } else { -// debugMenuItem.menu?.removeItem(debugMenuItem) -// DispatchQueue.main.async { -// self.refreshTimer!.timedRefresh(nil) -// self.syncTimer!.timedRefresh(nil) -// } -// } - - // TODO: Add back in crash reporter -// #if !MAC_APP_STORE -// DispatchQueue.main.async { -// CrashReporter.check(appName: "NetNewsWire") -// } -// #endif - - } - - func applicationDidBecomeActive(_ notification: Notification) { - fireOldTimers() - } - - func applicationDidResignActive(_ notification: Notification) { - ArticleStringFormatter.emptyCaches() - } - - func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) { - AccountManager.shared.receiveRemoteNotification(userInfo: userInfo) - } - - func applicationWillTerminate(_ notification: Notification) { - shuttingDown = true - } - - // MARK: Notifications - @objc func unreadCountDidChange(_ note: Notification) { - if note.object is AccountManager { - unreadCount = AccountManager.shared.unreadCount - } - } - - @objc func webFeedSettingDidChange(_ note: Notification) { - guard let feed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else { - return - } - if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL { - let _ = faviconDownloader.favicon(for: feed) - } - } - - @objc func userDefaultsDidChange(_ note: Notification) { - refreshTimer?.update() - updateDockBadge() - } - - @objc func didWakeNotification(_ note: Notification) { - fireOldTimers() - } - - // MARK: UNUserNotificationCenterDelegate - - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - completionHandler([.banner, .badge, .sound]) - } - - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { -// TODO: Add back in Notification handling -// mainWindowController?.handle(response) - completionHandler() - } - - // MARK: - Dock Badge - @objc func updateDockBadge() { - let label = unreadCount > 0 && !AppDefaults.shared.hideDockUnreadCount ? "\(unreadCount)" : "" - NSApplication.shared.dockTile.badgeLabel = label - } - -} - -private extension AppDelegate { - - func fireOldTimers() { - // It’s possible there’s a refresh timer set to go off in the past. - // In that case, refresh now and update the timer. - refreshTimer?.fireOldTimer() - syncTimer?.fireOldTimer() - } - -} - -/* - the ScriptingAppDelegate protocol exposes a narrow set of accessors with - internal visibility which are very similar to some private vars. - - These would be unnecessary if the similar accessors were marked internal rather than private, - but for now, we'll keep the stratification of visibility -*/ -//extension AppDelegate : ScriptingAppDelegate { -// -// internal var scriptingMainWindowController: ScriptingMainWindowController? { -// return mainWindowController -// } -// -// internal var scriptingCurrentArticle: Article? { -// return self.scriptingMainWindowController?.scriptingCurrentArticle -// } -// -// internal var scriptingSelectedArticles: [Article] { -// return self.scriptingMainWindowController?.scriptingSelectedArticles ?? [] -// } -//} diff --git a/Multiplatform/macOS/Article/ArticleView.swift b/Multiplatform/macOS/Article/ArticleView.swift deleted file mode 100644 index 71f0704aa..000000000 --- a/Multiplatform/macOS/Article/ArticleView.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// ArticleView.swift -// Multiplatform macOS -// -// Created by Maurice Parker on 7/8/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import Articles - -struct ArticleView: NSViewControllerRepresentable { - - @EnvironmentObject private var sceneModel: SceneModel - - func makeNSViewController(context: Context) -> WebViewController { - let controller = WebViewController() - controller.sceneModel = sceneModel - return controller - } - - func updateNSViewController(_ controller: WebViewController, context: Context) { - } - -} diff --git a/Multiplatform/macOS/Article/IconView.swift b/Multiplatform/macOS/Article/IconView.swift deleted file mode 100644 index 9a72b5dd5..000000000 --- a/Multiplatform/macOS/Article/IconView.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// IconView.swift -// Multiplatform macOS -// -// Created by Maurice Parker on 7/7/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import AppKit - -final class IconView: NSView { - - var iconImage: IconImage? = nil { - didSet { - if iconImage !== oldValue { - imageView.image = iconImage?.image - - if NSApplication.shared.effectiveAppearance.isDarkMode { - if self.iconImage?.isDark ?? false { - self.isDiscernable = false - } else { - self.isDiscernable = true - } - } else { - if self.iconImage?.isBright ?? false { - self.isDiscernable = false - } else { - self.isDiscernable = true - } - } - - needsDisplay = true - needsLayout = true - } - } - } - - private var isDiscernable = true - - override var isFlipped: Bool { - return true - } - - private let imageView: NSImageView = { - let imageView = NSImageView(frame: NSRect.zero) - imageView.animates = false - imageView.imageAlignment = .alignCenter - imageView.imageScaling = .scaleProportionallyUpOrDown - return imageView - }() - - private var hasExposedVerticalBackground: Bool { - return imageView.frame.size.height < bounds.size.height - } - - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - commonInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() - } - - convenience init() { - self.init(frame: NSRect.zero) - } - - override func viewDidMoveToSuperview() { - needsLayout = true - needsDisplay = true - } - - override func layout() { - resizeSubviews(withOldSize: NSZeroSize) - } - - override func resizeSubviews(withOldSize oldSize: NSSize) { - imageView.setFrame(ifNotEqualTo: rectForImageView()) - } - - override func draw(_ dirtyRect: NSRect) { - guard hasExposedVerticalBackground || !isDiscernable else { - return - } - - let color = AppAssets.nsIconBackgroundColor - color.set() - dirtyRect.fill() - } -} - -private extension IconView { - - func commonInit() { - addSubview(imageView) - wantsLayer = true - layer?.cornerRadius = 4.0 - } - - func rectForImageView() -> NSRect { - guard !(iconImage?.isSymbol ?? false) else { - return NSMakeRect(0.0, 0.0, bounds.size.width, bounds.size.height) - } - - guard let image = iconImage?.image else { - return NSRect.zero - } - - let imageSize = image.size - let viewSize = bounds.size - if imageSize.height == imageSize.width { - if imageSize.height >= viewSize.height { - return NSMakeRect(0.0, 0.0, viewSize.width, viewSize.height) - } - let offset = floor((viewSize.height - imageSize.height) / 2.0) - return NSMakeRect(offset, offset, imageSize.width, imageSize.height) - } - else if imageSize.height > imageSize.width { - let factor = viewSize.height / imageSize.height - let width = imageSize.width * factor - let originX = floor((viewSize.width - width) / 2.0) - return NSMakeRect(originX, 0.0, width, viewSize.height) - } - - // Wider than tall: imageSize.width > imageSize.height - let factor = viewSize.width / imageSize.width - let height = imageSize.height * factor - let originY = floor((viewSize.height - height) / 2.0) - return NSMakeRect(0.0, originY, viewSize.width, height) - } -} - diff --git a/Multiplatform/macOS/Article/SharingServiceDelegate.swift b/Multiplatform/macOS/Article/SharingServiceDelegate.swift deleted file mode 100644 index 01e78fc3f..000000000 --- a/Multiplatform/macOS/Article/SharingServiceDelegate.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// SharingServiceDelegate.swift -// NetNewsWire -// -// Created by Maurice Parker on 9/7/18. -// Copyright © 2018 Ranchero Software. All rights reserved. -// - -import AppKit - -@objc final class SharingServiceDelegate: NSObject, NSSharingServiceDelegate { - - weak var window: NSWindow? - - init(_ window: NSWindow?) { - self.window = window - } - - func sharingService(_ sharingService: NSSharingService, willShareItems items: [Any]) { - sharingService.subject = items - .compactMap { item in - let writer = item as? ArticlePasteboardWriter - return writer?.article.title - } - .joined(separator: ", ") - } - - func sharingService(_ sharingService: NSSharingService, sourceWindowForShareItems items: [Any], sharingContentScope: UnsafeMutablePointer) -> NSWindow? { - return window - } - -} diff --git a/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift b/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift deleted file mode 100644 index bc6659530..000000000 --- a/Multiplatform/macOS/Article/SharingServicePickerDelegate.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SharingServicePickerDelegate.swift -// NetNewsWire -// -// Created by Brent Simmons on 2/17/18. -// Copyright © 2018 Ranchero Software. All rights reserved. -// - -import AppKit -import RSCore - -@objc final class SharingServicePickerDelegate: NSObject, NSSharingServicePickerDelegate { - - private let sharingServiceDelegate: SharingServiceDelegate - private let completion: (() -> Void)? - - init(_ window: NSWindow?, completion: (() -> Void)?) { - self.sharingServiceDelegate = SharingServiceDelegate(window) - self.completion = completion - } - - func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] { - let filteredServices = proposedServices.filter { $0.menuItemTitle != "NetNewsWire" } - return filteredServices + SharingServicePickerDelegate.customSharingServices(for: items) - } - - func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, delegateFor sharingService: NSSharingService) -> NSSharingServiceDelegate? { - return sharingServiceDelegate - } - - func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, didChoose service: NSSharingService?) { - completion?() - } - - static func customSharingServices(for items: [Any]) -> [NSSharingService] { - let customServices = ExtensionPointManager.shared.activeSendToCommands.compactMap { (sendToCommand) -> NSSharingService? in - - guard let object = items.first else { - return nil - } - - guard sendToCommand.canSendObject(object, selectedText: nil) else { - return nil - } - - let image = sendToCommand.image ?? NSImage() - return NSSharingService(title: sendToCommand.title, image: image, alternateImage: nil) { - sendToCommand.sendObject(object, selectedText: nil) - } - } - return customServices - } -} - - diff --git a/Multiplatform/macOS/Article/SharingServiceView.swift b/Multiplatform/macOS/Article/SharingServiceView.swift deleted file mode 100644 index 71eb7e7a0..000000000 --- a/Multiplatform/macOS/Article/SharingServiceView.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// SharingServiceView.swift -// Multiplatform macOS -// -// Created by Maurice Parker on 7/14/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import SwiftUI -import AppKit -import Articles - -class SharingServiceController: NSViewController { - - var sharingServicePickerDelegate: SharingServicePickerDelegate? = nil - var articles = [Article]() - var completion: (() -> Void)? = nil - - override func loadView() { - view = NSView() - } - - override func viewDidAppear() { - guard let anchor = view.superview?.superview else { return } - - sharingServicePickerDelegate = SharingServicePickerDelegate(self.view.window, completion: completion) - - let sortedArticles = articles.sortedByDate(.orderedAscending) - let items = sortedArticles.map { ArticlePasteboardWriter(article: $0) } - - let sharingServicePicker = NSSharingServicePicker(items: items) - sharingServicePicker.delegate = sharingServicePickerDelegate - - sharingServicePicker.show(relativeTo: anchor.bounds, of: anchor, preferredEdge: .minY) - } - -} - -struct SharingServiceView: NSViewControllerRepresentable { - - var articles: [Article] - @Binding var showing: Bool - - func makeNSViewController(context: Context) -> SharingServiceController { - let controller = SharingServiceController() - controller.articles = articles - controller.completion = { - showing = false - } - return controller - } - - func updateNSViewController(_ nsViewController: SharingServiceController, context: Context) { - } - -} diff --git a/Multiplatform/macOS/Article/WebStatusBarView.swift b/Multiplatform/macOS/Article/WebStatusBarView.swift deleted file mode 100644 index 3b14f0484..000000000 --- a/Multiplatform/macOS/Article/WebStatusBarView.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// WebStatusBarView.swift -// Multiplatform macOS -// -// Created by Maurice Parker on 7/8/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import AppKit -import Articles - -final class WebStatusBarView: NSView { - - var urlLabel = NSTextField(labelWithString: "") - - var mouseoverLink: String? { - didSet { - updateLinkForDisplay() - } - } - - private var linkForDisplay: String? { - didSet { - needsLayout = true - if let link = linkForDisplay { - urlLabel.stringValue = link - self.isHidden = false - } - else { - urlLabel.stringValue = "" - self.isHidden = true - } - } - } - - private var didConfigureLayerRadius = false - - override var isOpaque: Bool { - return false - } - - override var isFlipped: Bool { - return true - } - - override var wantsUpdateLayer: Bool { - return true - } - - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - commonInit() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - commonInit() - } - - override func updateLayer() { - guard let layer = layer else { - return - } - if !didConfigureLayerRadius { - layer.cornerRadius = 4.0 - didConfigureLayerRadius = true - } - - layer.backgroundColor = AppAssets.webStatusBarBackground.cgColor - } -} - -// MARK: - Private - -private extension WebStatusBarView { - - func commonInit() { - self.isHidden = true - urlLabel.translatesAutoresizingMaskIntoConstraints = false - urlLabel.lineBreakMode = .byTruncatingMiddle - urlLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - - addSubview(urlLabel) - NSLayoutConstraint.activate([ - leadingAnchor.constraint(equalTo: urlLabel.leadingAnchor, constant: -6), - trailingAnchor.constraint(equalTo: urlLabel.trailingAnchor, constant: 6), - centerYAnchor.constraint(equalTo: urlLabel.centerYAnchor) - ]) - } - - func updateLinkForDisplay() { - if let mouseoverLink = mouseoverLink, !mouseoverLink.isEmpty { - linkForDisplay = mouseoverLink.strippingHTTPOrHTTPSScheme - } - else { - linkForDisplay = nil - } - } -} - - diff --git a/Multiplatform/macOS/Article/WebViewController.swift b/Multiplatform/macOS/Article/WebViewController.swift deleted file mode 100644 index 0a9c9c3ff..000000000 --- a/Multiplatform/macOS/Article/WebViewController.swift +++ /dev/null @@ -1,373 +0,0 @@ -// -// WebViewController.swift -// Multiplatform macOS -// -// Created by Maurice Parker on 7/8/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import AppKit -import Combine -import RSCore -import Articles - -protocol WebViewControllerDelegate: AnyObject { - func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState) -} - -class WebViewController: NSViewController { - - private struct MessageName { - static let imageWasClicked = "imageWasClicked" - static let imageWasShown = "imageWasShown" - static let mouseDidEnter = "mouseDidEnter" - static let mouseDidExit = "mouseDidExit" - static let showFeedInspector = "showFeedInspector" - } - - var statusBarView: WebStatusBarView! - - private var webView: PreloadedWebView? - - private var articleExtractor: ArticleExtractor? = nil - var extractedArticle: ExtractedArticle? - var isShowingExtractedArticle = false - - var articleExtractorButtonState: ArticleExtractorButtonState = .off { - didSet { - delegate?.webViewController(self, articleExtractorButtonStateDidUpdate: articleExtractorButtonState) - } - } - - var sceneModel: SceneModel? - weak var delegate: WebViewControllerDelegate? - - var articles: [Article]? { - didSet { - if oldValue != articles { - loadWebView() - } - } - } - - private var cancellables = Set() - - override func loadView() { - view = NSView() - } - - override func viewDidLoad() { - super.viewDidLoad() - - NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) - - statusBarView = WebStatusBarView() - statusBarView.translatesAutoresizingMaskIntoConstraints = false - self.view.addSubview(statusBarView) - NSLayoutConstraint.activate([ - self.view.leadingAnchor.constraint(equalTo: statusBarView.leadingAnchor, constant: -6), - self.view.trailingAnchor.constraint(greaterThanOrEqualTo: statusBarView.trailingAnchor, constant: 6), - self.view.bottomAnchor.constraint(equalTo: statusBarView.bottomAnchor, constant: 2), - statusBarView.heightAnchor.constraint(equalToConstant: 20) - ]) - - sceneModel?.timelineModel.selectedArticlesPublisher?.sink { [weak self] articles in - self?.articles = articles - } - .store(in: &cancellables) - } - - // MARK: Notifications - - @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { - reloadArticleImage() - } - - @objc func avatarDidBecomeAvailable(_ note: Notification) { - reloadArticleImage() - } - - @objc func faviconDidBecomeAvailable(_ note: Notification) { - reloadArticleImage() - } - - // MARK: API - - func focus() { - webView?.becomeFirstResponder() - } - - func canScrollDown(_ completion: @escaping (Bool) -> Void) { - fetchScrollInfo { (scrollInfo) in - completion(scrollInfo?.canScrollDown ?? false) - } - } - - override func scrollPageDown(_ sender: Any?) { - webView?.scrollPageDown(sender) - } - - func toggleArticleExtractor() { - - guard let article = articles?.first else { - return - } - - guard articleExtractor?.state != .processing else { - stopArticleExtractor() - loadWebView() - return - } - - guard !isShowingExtractedArticle else { - isShowingExtractedArticle = false - loadWebView() - articleExtractorButtonState = .off - return - } - - if let articleExtractor = articleExtractor { - if article.preferredLink == articleExtractor.articleLink { - isShowingExtractedArticle = true - loadWebView() - articleExtractorButtonState = .on - } - } else { - startArticleExtractor() - } - - } - - func stopArticleExtractorIfProcessing() { - if articleExtractor?.state == .processing { - stopArticleExtractor() - } - } - - func stopWebViewActivity() { - if let webView = webView { - stopMediaPlayback(webView) - } - } - -} - -// MARK: ArticleExtractorDelegate - -extension WebViewController: ArticleExtractorDelegate { - - func articleExtractionDidFail(with: Error) { - stopArticleExtractor() - articleExtractorButtonState = .error - loadWebView() - } - - func articleExtractionDidComplete(extractedArticle: ExtractedArticle) { - if articleExtractor?.state != .cancelled { - self.extractedArticle = extractedArticle - isShowingExtractedArticle = true - loadWebView() - articleExtractorButtonState = .on - } - } - -} - - -// MARK: WKScriptMessageHandler - -extension WebViewController: WKScriptMessageHandler { - - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - switch message.name { - case MessageName.imageWasShown: - return - case MessageName.imageWasClicked: - return - case MessageName.mouseDidEnter: - if let link = message.body as? String { - statusBarView.mouseoverLink = link - } - case MessageName.mouseDidExit: - statusBarView.mouseoverLink = nil - case MessageName.showFeedInspector: - return - default: - return - } - } - -} - -extension WebViewController: WKNavigationDelegate { - - public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if navigationAction.navigationType == .linkActivated { - if let url = navigationAction.request.url { - let flags = navigationAction.modifierFlags - let invert = flags.contains(.shift) || flags.contains(.command) - Browser.open(url.absoluteString, invertPreference: invert) - } - decisionHandler(.cancel) - return - } - - decisionHandler(.allow) - } - -} - -// MARK: Private - -private extension WebViewController { - - func loadWebView() { - if let webView = webView { - self.renderPage(webView) - return - } - - sceneModel?.webViewProvider?.dequeueWebView() { webView in - - webView.ready { - - // Add the webview - self.webView = webView - - webView.translatesAutoresizingMaskIntoConstraints = false - self.view.addSubview(webView, positioned: .below, relativeTo: self.statusBarView) - NSLayoutConstraint.activate([ - self.view.leadingAnchor.constraint(equalTo: webView.leadingAnchor), - self.view.trailingAnchor.constraint(equalTo: webView.trailingAnchor), - self.view.topAnchor.constraint(equalTo: webView.topAnchor), - self.view.bottomAnchor.constraint(equalTo: webView.bottomAnchor) - ]) - - webView.navigationDelegate = self - - webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked) - webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown) - webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.mouseDidEnter) - webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.mouseDidExit) - webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.showFeedInspector) - - self.renderPage(webView) - - } - } - - } - - func renderPage(_ webView: PreloadedWebView) { - let theme = ArticleThemesManager.shared.currentTheme - let rendering: ArticleRenderer.Rendering - - if articles?.count ?? 0 > 1 { - rendering = ArticleRenderer.multipleSelectionHTML(theme: theme) - } else if let articleExtractor = articleExtractor, articleExtractor.state == .processing { - rendering = ArticleRenderer.loadingHTML(theme: theme) - } else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = articles?.first { - rendering = ArticleRenderer.articleHTML(article: article, theme: theme) - } else if let article = articles?.first, let extractedArticle = extractedArticle { - if isShowingExtractedArticle { - rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme) - } else { - rendering = ArticleRenderer.articleHTML(article: article, theme: theme) - } - } else if let article = articles?.first { - rendering = ArticleRenderer.articleHTML(article: article, theme: theme) - } else { - rendering = ArticleRenderer.noSelectionHTML(theme: theme) - } - - let substitutions = [ - "title": rendering.title, - "baseURL": rendering.baseURL, - "style": rendering.style, - "body": rendering.html - ] - - let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions) - webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL) - - } - - func fetchScrollInfo(_ completion: @escaping (ScrollInfo?) -> Void) { - guard let webView = webView else { - completion(nil) - return - } - - let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: window.pageYOffset}; x" - - webView.evaluateJavaScript(javascriptString) { (info, error) in - guard let info = info as? [String: Any] else { - completion(nil) - return - } - guard let contentHeight = info["contentHeight"] as? CGFloat, let offsetY = info["offsetY"] as? CGFloat else { - completion(nil) - return - } - - let scrollInfo = ScrollInfo(contentHeight: contentHeight, viewHeight: webView.frame.height, offsetY: offsetY) - completion(scrollInfo) - } - } - - func startArticleExtractor() { - if let link = articles?.first?.preferredLink, let extractor = ArticleExtractor(link) { - extractor.delegate = self - extractor.process() - articleExtractor = extractor - articleExtractorButtonState = .animated - } - } - - func stopArticleExtractor() { - articleExtractor?.cancel() - articleExtractor = nil - isShowingExtractedArticle = false - articleExtractorButtonState = .off - } - - func reloadArticleImage() { - guard let article = articles?.first else { return } - - var components = URLComponents() - components.scheme = ArticleRenderer.imageIconScheme - components.path = article.articleID - - if let imageSrc = components.string { - webView?.evaluateJavaScript("reloadArticleImage(\"\(imageSrc)\")") - } - } - - func stopMediaPlayback(_ webView: WKWebView) { - webView.evaluateJavaScript("stopMediaPlayback();") - } - -} - -// MARK: - ScrollInfo - -private struct ScrollInfo { - - let contentHeight: CGFloat - let viewHeight: CGFloat - let offsetY: CGFloat - let canScrollDown: Bool - let canScrollUp: Bool - - init(contentHeight: CGFloat, viewHeight: CGFloat, offsetY: CGFloat) { - self.contentHeight = contentHeight - self.viewHeight = viewHeight - self.offsetY = offsetY - - self.canScrollDown = viewHeight + offsetY < contentHeight - self.canScrollUp = offsetY > 0.1 - } - -} diff --git a/Multiplatform/macOS/Browser.swift b/Multiplatform/macOS/Browser.swift deleted file mode 100644 index dfcbb1571..000000000 --- a/Multiplatform/macOS/Browser.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// Browser.swift -// Evergren -// -// Created by Brent Simmons on 2/23/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -import RSWeb - -struct Browser { - - /// The user-specified default browser for opening web pages. - /// - /// The user-assigned default browser, or `nil` if none was assigned - /// (i.e., the system default should be used). - static var defaultBrowser: MacWebBrowser? { - if let bundleID = AppDefaults.shared.defaultBrowserID, let browser = MacWebBrowser(bundleIdentifier: bundleID) { - return browser - } - - return nil - } - - - /// Opens a URL in the default browser. - /// - /// - Parameters: - /// - urlString: The URL to open. - /// - invert: Whether to invert the "open in background in browser" preference - static func open(_ urlString: String, invertPreference invert: Bool = false) { - // Opens according to prefs. - open(urlString, inBackground: invert ? !AppDefaults.shared.openInBrowserInBackground : AppDefaults.shared.openInBrowserInBackground) - } - - - /// Opens a URL in the default browser. - /// - /// - Parameters: - /// - urlString: The URL to open. - /// - inBackground: Whether to open the URL in the background or not. - /// - Note: Some browsers (specifically Chromium-derived ones) will ignore the request - /// to open in the background. - static func open(_ urlString: String, inBackground: Bool) { - if let url = URL(string: urlString) { - if let defaultBrowser = defaultBrowser { - defaultBrowser.openURL(url, inBackground: inBackground) - } else { - MacWebBrowser.openURL(url, inBackground: inBackground) - } - } - } -} - -extension Browser { - - static var titleForOpenInBrowserInverted: String { - let openInBackgroundPref = AppDefaults.shared.openInBrowserInBackground - - return openInBackgroundPref ? - NSLocalizedString("Open in Browser in Foreground", comment: "Open in Browser in Foreground menu item title") : - NSLocalizedString("Open in Browser in Background", comment: "Open in Browser in Background menu item title") - } -} diff --git a/Multiplatform/macOS/Info.plist b/Multiplatform/macOS/Info.plist deleted file mode 100644 index 58377c4e4..000000000 --- a/Multiplatform/macOS/Info.plist +++ /dev/null @@ -1,57 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - Copyright © 2002-2021 Ranchero Software. All rights reserved. - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - UserAgent - NetNewsWire (RSS Reader; https://netnewswire.com/) - OrganizationIdentifier - $(ORGANIZATION_IDENTIFIER) - DeveloperEntitlements - $(DEVELOPER_ENTITLEMENTS) - CFBundleURLTypes - - - CFBundleTypeRole - Viewer - CFBundleURLName - RSS Feed - CFBundleURLSchemes - - feed - feeds - - - - SUFeedURL - https://ranchero.com/downloads/netnewswire-release.xml - FeedURLForTestBuilds - https://ranchero.com/downloads/netnewswire-beta.xml - - diff --git a/Multiplatform/macOS/MacSearchField.swift b/Multiplatform/macOS/MacSearchField.swift deleted file mode 100644 index 2ae870735..000000000 --- a/Multiplatform/macOS/MacSearchField.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// MacSearchField.swift -// Multiplatform macOS -// -// Created by Stuart Breckenridge on 29/6/20. -// Copyright © 2020 Ranchero Software. All rights reserved. -// - -import AppKit -import SwiftUI - - -final class MacSearchField: NSViewRepresentable { - - typealias NSViewType = NSSearchField - - - func makeNSView(context: Context) -> NSSearchField { - let searchField = NSSearchField() - searchField.delegate = context.coordinator - return searchField - } - - func updateNSView(_ nsView: NSSearchField, context: Context) { - - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, NSSearchFieldDelegate { - var parent: MacSearchField - - init(_ parent: MacSearchField) { - self.parent = parent - } - - func searchFieldDidStartSearching(_ sender: NSSearchField) { - // - } - - func searchFieldDidEndSearching(_ sender: NSSearchField) { - // - } - - } - -} diff --git a/Multiplatform/macOS/Preferences/MacPreferencePanes.swift b/Multiplatform/macOS/Preferences/MacPreferencePanes.swift deleted file mode 100644 index 6a5624365..000000000 --- a/Multiplatform/macOS/Preferences/MacPreferencePanes.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// MacPreferencesView.swift -// macOS -// -// Created by Stuart Breckenridge on 27/6/20. -// - -import SwiftUI - -enum MacPreferencePane: Int, CaseIterable { - case general = 1 - case accounts = 2 - case viewing = 3 - case advanced = 4 - - var description: String { - switch self { - case .general: - return "General" - case .accounts: - return "Accounts" - case .viewing: - return "Appearance" - case .advanced: - return "Advanced" - } - } -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift deleted file mode 100644 index b3853d473..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesModel.swift +++ /dev/null @@ -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() - - 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) - } - -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesView.swift deleted file mode 100644 index bd72a621c..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/AccountsPreferencesView.swift +++ /dev/null @@ -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)) - } - } - -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift deleted file mode 100644 index 4f1001451..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Add Account/AddAccountView.swift +++ /dev/null @@ -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 - } -} - - diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/ConfiguredAccountRow.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/ConfiguredAccountRow.swift deleted file mode 100644 index baee404ba..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/ConfiguredAccountRow.swift +++ /dev/null @@ -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) - } - -} - diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/AccountDetailView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/AccountDetailView.swift deleted file mode 100644 index f979159ec..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/AccountDetailView.swift +++ /dev/null @@ -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()) - } -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift deleted file mode 100644 index a96b8e27e..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsModel.swift +++ /dev/null @@ -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) - } -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift deleted file mode 100644 index 34b5dbe04..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/Account Preferences/Edit Account/EditAccountCredentialsView.swift +++ /dev/null @@ -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()) - } -} - diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountUpdateErrors.swift b/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountUpdateErrors.swift deleted file mode 100644 index 0ecec3500..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Accounts/AccountUpdateErrors.swift +++ /dev/null @@ -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 - } - } -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesModel.swift deleted file mode 100644 index 5e6688172..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesModel.swift +++ /dev/null @@ -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) - } - } -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesView.swift deleted file mode 100644 index f065eeefb..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Advanced/AdvancedPreferencesView.swift +++ /dev/null @@ -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) - } - -} - diff --git a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesModel.swift b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesModel.swift deleted file mode 100644 index 886d58169..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesModel.swift +++ /dev/null @@ -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 - static let feedURLScheme = "feed:" - - init() { - self.rssReaders = RSSReaderInfo.fetchRSSReaders() - } - - static func fetchRSSReaders() -> Set { - let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: feedURLScheme) - - var rssReaders = Set() - 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 - } -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift deleted file mode 100644 index 2ad76de9e..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift +++ /dev/null @@ -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.. 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) - } - -} diff --git a/Multiplatform/macOS/Preferences/Preference Panes/Viewing/LayoutPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/Viewing/LayoutPreferencesView.swift deleted file mode 100644 index abc9e5a2c..000000000 --- a/Multiplatform/macOS/Preferences/Preference Panes/Viewing/LayoutPreferencesView.swift +++ /dev/null @@ -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() - } -} diff --git a/Multiplatform/macOS/macOS-dev.entitlements b/Multiplatform/macOS/macOS-dev.entitlements deleted file mode 100644 index 4b0c04750..000000000 --- a/Multiplatform/macOS/macOS-dev.entitlements +++ /dev/null @@ -1,18 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.automation.apple-events - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.temporary-exception.apple-events - - com.red-sweater.marsedit4 - - - diff --git a/Multiplatform/macOS/macOS.entitlements b/Multiplatform/macOS/macOS.entitlements deleted file mode 100644 index cfbbe8b53..000000000 --- a/Multiplatform/macOS/macOS.entitlements +++ /dev/null @@ -1,28 +0,0 @@ - - - - - com.apple.developer.aps-environment - development - com.apple.developer.icloud-container-identifiers - - iCloud.$(ORGANIZATION_IDENTIFIER).NetNewsWire - - com.apple.developer.icloud-services - - CloudKit - - com.apple.security.app-sandbox - - com.apple.security.automation.apple-events - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.temporary-exception.apple-events - - com.red-sweater.marsedit4 - - - diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index fa57611be..f32bb0df6 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -9,71 +9,19 @@ /* Begin PBXBuildFile section */ 1701E1B52568983D009453D8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1701E1B72568983D009453D8 /* Localizable.strings */; }; 1701E1E725689D1E009453D8 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1701E1E625689D1E009453D8 /* Localized.swift */; }; - 1704053424E5985A00A00787 /* SceneNavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1704053324E5985A00A00787 /* SceneNavigationModel.swift */; }; - 1704053524E5985A00A00787 /* SceneNavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1704053324E5985A00A00787 /* SceneNavigationModel.swift */; }; 17071EF026F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */; }; 17071EF126F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */; }; 1710B9132552354E00679C0D /* AddAccountHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B9122552354E00679C0D /* AddAccountHelpView.swift */; }; 1710B9142552354E00679C0D /* AddAccountHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B9122552354E00679C0D /* AddAccountHelpView.swift */; }; 1710B929255246F900679C0D /* EnableExtensionPointHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */; }; 1710B92A255246F900679C0D /* EnableExtensionPointHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */; }; - 1717535624BADF33004498C6 /* GeneralPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */; }; 17192ADA2567B3D500AAEACA /* RSSparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 17192AD92567B3D500AAEACA /* RSSparkle */; }; 17192AE52567B3FE00AAEACA /* org.sparkle-project.Downloader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 17192AE12567B3FE00AAEACA /* org.sparkle-project.Downloader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 17192AE62567B3FE00AAEACA /* org.sparkle-project.InstallerConnection.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 17192AE22567B3FE00AAEACA /* org.sparkle-project.InstallerConnection.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 17192AE72567B3FE00AAEACA /* org.sparkle-project.InstallerLauncher.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 17192AE32567B3FE00AAEACA /* org.sparkle-project.InstallerLauncher.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 17192AE82567B3FE00AAEACA /* org.sparkle-project.InstallerStatus.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 17192AE42567B3FE00AAEACA /* org.sparkle-project.InstallerStatus.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 171BCB8C24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */; }; - 171BCB8D24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */; }; - 171BCBAF24CBBFD8006E22D9 /* EditAccountCredentialsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */; }; - 171BCBB024CBBFFD006E22D9 /* AccountUpdateErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */; }; - 172199C924AB228900A31D04 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199C824AB228900A31D04 /* SettingsView.swift */; }; - 172199ED24AB2E0100A31D04 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199EC24AB2E0100A31D04 /* SafariView.swift */; }; - 172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */; }; - 17241249257B8A8A00ACCEBC /* AddFeedlyAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17241248257B8A8A00ACCEBC /* AddFeedlyAccountView.swift */; }; - 1724126A257BBEBB00ACCEBC /* AddFeedbinViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17241269257BBEBB00ACCEBC /* AddFeedbinViewModel.swift */; }; - 17241278257BBEE700ACCEBC /* AddFeedlyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17241277257BBEE700ACCEBC /* AddFeedlyViewModel.swift */; }; - 17241280257BBF3E00ACCEBC /* AddFeedWranglerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1724127F257BBF3E00ACCEBC /* AddFeedWranglerViewModel.swift */; }; - 17241288257BBF7000ACCEBC /* AddNewsBlurViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17241287257BBF7000ACCEBC /* AddNewsBlurViewModel.swift */; }; - 17241290257BBFAD00ACCEBC /* AddReaderAPIViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1724128F257BBFAD00ACCEBC /* AddReaderAPIViewModel.swift */; }; - 1724129D257BC01C00ACCEBC /* AddNewsBlurViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17241287257BBF7000ACCEBC /* AddNewsBlurViewModel.swift */; }; - 1724129E257BC01C00ACCEBC /* AddReaderAPIViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1724128F257BBFAD00ACCEBC /* AddReaderAPIViewModel.swift */; }; - 1724129F257BC01C00ACCEBC /* AddFeedlyAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17241248257B8A8A00ACCEBC /* AddFeedlyAccountView.swift */; }; - 172412A0257BC01C00ACCEBC /* AddFeedWranglerAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF98E2AF2578AA5C00F18944 /* AddFeedWranglerAccountView.swift */; }; - 172412A1257BC01C00ACCEBC /* AddReaderAPIAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF98E2C52578AD1B00F18944 /* AddReaderAPIAccountView.swift */; }; - 172412A2257BC01C00ACCEBC /* AddLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17386B792577C4BF0014C8B2 /* AddLocalAccountView.swift */; }; - 172412A3257BC01C00ACCEBC /* AddNewsBlurAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF98E2BD2578AC0000F18944 /* AddNewsBlurAccountView.swift */; }; - 172412A4257BC01C00ACCEBC /* AddFeedlyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17241277257BBEE700ACCEBC /* AddFeedlyViewModel.swift */; }; - 172412A5257BC01C00ACCEBC /* AddCloudKitAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF98E2992578A73A00F18944 /* AddCloudKitAccountView.swift */; }; - 172412A6257BC01C00ACCEBC /* AddFeedbinViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17241269257BBEBB00ACCEBC /* AddFeedbinViewModel.swift */; }; - 172412A7257BC01C00ACCEBC /* AddFeedbinAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17386BC32577CC600014C8B2 /* AddFeedbinAccountView.swift */; }; - 172412A8257BC01C00ACCEBC /* AddFeedWranglerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1724127F257BBF3E00ACCEBC /* AddFeedWranglerViewModel.swift */; }; - 172412AF257BC0C300ACCEBC /* AccountType+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173A64162547BE0900267F6E /* AccountType+Helpers.swift */; }; - 1727B39924C1368D00A4DBDC /* LayoutPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1727B39824C1368D00A4DBDC /* LayoutPreferencesView.swift */; }; - 1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */; }; - 1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */; }; - 1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */; }; - 1729529724AA1CD000D65E66 /* MacPreferencePanes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529624AA1CD000D65E66 /* MacPreferencePanes.swift */; }; - 1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529A24AA1FD200D65E66 /* MacSearchField.swift */; }; - 17386B5E2577BC820014C8B2 /* AccountType+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173A64162547BE0900267F6E /* AccountType+Helpers.swift */; }; - 17386B6C2577BD820014C8B2 /* RSSparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 17386B6B2577BD820014C8B2 /* RSSparkle */; }; - 17386B7A2577C4BF0014C8B2 /* AddLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17386B792577C4BF0014C8B2 /* AddLocalAccountView.swift */; }; - 17386B952577C6240014C8B2 /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 17386B942577C6240014C8B2 /* RSCore */; }; - 17386B962577C6240014C8B2 /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17386B942577C6240014C8B2 /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17386B982577C6240014C8B2 /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 17386B972577C6240014C8B2 /* RSTree */; }; - 17386B992577C6240014C8B2 /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17386B972577C6240014C8B2 /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17386B9B2577C6240014C8B2 /* RSWeb in Frameworks */ = {isa = PBXBuildFile; productRef = 17386B9A2577C6240014C8B2 /* RSWeb */; }; - 17386B9C2577C6240014C8B2 /* RSWeb in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17386B9A2577C6240014C8B2 /* RSWeb */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17386B9E2577C6240014C8B2 /* RSDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 17386B9D2577C6240014C8B2 /* RSDatabase */; }; - 17386B9F2577C6240014C8B2 /* RSDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17386B9D2577C6240014C8B2 /* RSDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17386BA42577C6240014C8B2 /* RSParser in Frameworks */ = {isa = PBXBuildFile; productRef = 17386BA32577C6240014C8B2 /* RSParser */; }; - 17386BA52577C6240014C8B2 /* RSParser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17386BA32577C6240014C8B2 /* RSParser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17386BB62577C7340014C8B2 /* RSCoreResources in Frameworks */ = {isa = PBXBuildFile; productRef = 17386BB52577C7340014C8B2 /* RSCoreResources */; }; - 17386BC42577CC600014C8B2 /* AddFeedbinAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17386BC32577CC600014C8B2 /* AddFeedbinAccountView.swift */; }; 173A64172547BE0900267F6E /* AccountType+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173A64162547BE0900267F6E /* AccountType+Helpers.swift */; }; 173A642C2547BE9600267F6E /* AccountType+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173A64162547BE0900267F6E /* AccountType+Helpers.swift */; }; - 175942AA24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; - 175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 176813D02564BA5900D98635 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813B62564B9F800D98635 /* WidgetData.swift */; }; 176813D12564BA5900D98635 /* WidgetDataDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813C92564BA5400D98635 /* WidgetDataDecoder.swift */; }; 176813D22564BA5900D98635 /* WidgetDataEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813BD2564BA2800D98635 /* WidgetDataEncoder.swift */; }; @@ -94,64 +42,23 @@ 176814652564BD7F00D98635 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813B62564B9F800D98635 /* WidgetData.swift */; }; 1768146C2564BD8100D98635 /* WidgetDeepLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176813D82564BA8700D98635 /* WidgetDeepLinks.swift */; }; 1768147B2564BE5400D98635 /* widget-sample.json in Resources */ = {isa = PBXBuildFile; fileRef = 1768147A2564BE5400D98635 /* widget-sample.json */; }; - 1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */; }; - 1769E32524BC5A65000E1E8E /* AddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32424BC5A65000E1E8E /* AddAccountView.swift */; }; - 1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */; }; - 1769E32D24BD20A0000E1E8E /* AccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */; }; - 1769E33024BD6271000E1E8E /* EditAccountCredentialsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */; }; - 1769E33624BD9621000E1E8E /* EditAccountCredentialsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */; }; - 1769E33824BD97CB000E1E8E /* AccountUpdateErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */; }; - 1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; - 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; }; 177A0C2D25454AAB00D7EAF6 /* ReaderAPIAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 177A0C2C25454AAB00D7EAF6 /* ReaderAPIAccountViewController.swift */; }; - 17897ACA24C281A40014BA03 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17897AC924C281A40014BA03 /* InspectorView.swift */; }; - 17897ACB24C281A40014BA03 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17897AC924C281A40014BA03 /* InspectorView.swift */; }; 178A9F9D2549449F00AB7E9D /* AddAccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */; }; 178A9F9E2549449F00AB7E9D /* AddAccountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */; }; - 17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; - 17930ED524AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; }; - 1799E6A924C2F93F00511E91 /* InspectorPlatformModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1799E6A824C2F93F00511E91 /* InspectorPlatformModifier.swift */; }; - 1799E6AA24C2F93F00511E91 /* InspectorPlatformModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1799E6A824C2F93F00511E91 /* InspectorPlatformModifier.swift */; }; - 1799E6CD24C320D600511E91 /* InspectorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1799E6CC24C320D600511E91 /* InspectorModel.swift */; }; - 1799E6CE24C320D600511E91 /* InspectorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1799E6CC24C320D600511E91 /* InspectorModel.swift */; }; 179C39EA26F76B0500D4E741 /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = 179C39E926F76B0500D4E741 /* Zip */; }; 179C39EB26F76B3800D4E741 /* ArticleThemePlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179D280C26F73D83003B2E0A /* ArticleThemePlist.swift */; }; 179D280B26F6F93D003B2E0A /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = 179D280A26F6F93D003B2E0A /* Zip */; }; 179D280D26F73D83003B2E0A /* ArticleThemePlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179D280C26F73D83003B2E0A /* ArticleThemePlist.swift */; }; - 179D280E26F73D83003B2E0A /* ArticleThemePlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179D280C26F73D83003B2E0A /* ArticleThemePlist.swift */; }; - 179D280F26F73D83003B2E0A /* ArticleThemePlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179D280C26F73D83003B2E0A /* ArticleThemePlist.swift */; }; 179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; 179DB3CE822BFCC2D774D9F4 /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; }; - 17A1597C24E3DEDD005DA32A /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 17A1597B24E3DEDD005DA32A /* RSCore */; }; - 17A1597D24E3DEDD005DA32A /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1597B24E3DEDD005DA32A /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17A1597F24E3DEDD005DA32A /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 17A1597E24E3DEDD005DA32A /* RSTree */; }; - 17A1598024E3DEDD005DA32A /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1597E24E3DEDD005DA32A /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17A1598224E3DEDD005DA32A /* RSWeb in Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598124E3DEDD005DA32A /* RSWeb */; }; - 17A1598324E3DEDD005DA32A /* RSWeb in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598124E3DEDD005DA32A /* RSWeb */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17A1598524E3DEDD005DA32A /* RSDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598424E3DEDD005DA32A /* RSDatabase */; }; - 17A1598624E3DEDD005DA32A /* RSDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598424E3DEDD005DA32A /* RSDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 17A1598824E3DEDD005DA32A /* RSParser in Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598724E3DEDD005DA32A /* RSParser */; }; - 17A1598924E3DEDD005DA32A /* RSParser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17A1598724E3DEDD005DA32A /* RSParser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 17D0682C2564F47E00C0B37E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */; }; - 17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */; }; - 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */; }; - 17D3CEE3257C4D2300E74939 /* AddAccountSignUp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D3CEE2257C4D2300E74939 /* AddAccountSignUp.swift */; }; - 17D3CEE4257C4D2300E74939 /* AddAccountSignUp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D3CEE2257C4D2300E74939 /* AddAccountSignUp.swift */; }; - 17D5F17124B0BC6700375168 /* SidebarToolbarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */; }; - 17D5F17224B0BC6700375168 /* SidebarToolbarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */; }; - 17D5F19524B0C1DD00375168 /* SidebarToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */; }; 17D643B126F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */; }; 17D643B226F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */; }; 17D7586F2679C21800B17787 /* OnePasswordExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D7586E2679C21800B17787 /* OnePasswordExtension.m */; }; 17E0084625941887000C23F0 /* SizeCategories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E0084525941887000C23F0 /* SizeCategories.swift */; }; - 17E4DBD624BFC53E00FE462A /* AdvancedPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */; }; 17EF6A2125C4E5B4002C9F81 /* RSWeb in Frameworks */ = {isa = PBXBuildFile; productRef = 17EF6A2025C4E5B4002C9F81 /* RSWeb */; }; 17EF6A2225C4E5B4002C9F81 /* RSWeb in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17EF6A2025C4E5B4002C9F81 /* RSWeb */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 27B86EEB25A53AAB00264340 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4A24D343A500E90810 /* Account */; }; - 27B86EEC25A53AAB00264340 /* Articles in Frameworks */ = {isa = PBXBuildFile; productRef = 17E0080E25936DF6000C23F0 /* Articles */; }; - 27B86EED25A53AAB00264340 /* ArticlesDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 17E0081125936DF6000C23F0 /* ArticlesDatabase */; }; - 27B86EEE25A53AAB00264340 /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = 17E0081425936DFF000C23F0 /* Secrets */; }; - 27B86EEF25A53AAB00264340 /* SyncDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 17E0081725936DFF000C23F0 /* SyncDatabase */; }; 3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; }; 3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; }; 3B826DCC2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; }; @@ -226,7 +133,6 @@ 512392C424E3451400F11704 /* TwitterSelectTypeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510289D12451BC1F00426DDF /* TwitterSelectTypeTableViewController.swift */; }; 512392C524E3451400F11704 /* TwitterEnterDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */; }; 512392C624E3451400F11704 /* TwitterSelectAccountTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510289D52451DDD100426DDF /* TwitterSelectAccountTableViewController.swift */; }; - 5125E6CA24AE461D002A7562 /* TimelineLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */; }; 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */; }; 5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; }; 5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; }; @@ -261,8 +167,6 @@ 5137C2E426F3F52D009EFEDB /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5137C2E326F3F52D009EFEDB /* Sepia.nnwtheme */; }; 5137C2E526F3F52D009EFEDB /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5137C2E326F3F52D009EFEDB /* Sepia.nnwtheme */; }; 5137C2E626F3F52D009EFEDB /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5137C2E326F3F52D009EFEDB /* Sepia.nnwtheme */; }; - 5137C2E726F3F52D009EFEDB /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5137C2E326F3F52D009EFEDB /* Sepia.nnwtheme */; }; - 5137C2E826F3F52D009EFEDB /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5137C2E326F3F52D009EFEDB /* Sepia.nnwtheme */; }; 5137C2EA26F63AE6009EFEDB /* ArticleThemeImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5137C2E926F63AE6009EFEDB /* ArticleThemeImporter.swift */; }; 51386A8E25673277005F3762 /* AccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51386A8D25673276005F3762 /* AccountCell.swift */; }; 51386A8F25673277005F3762 /* AccountCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51386A8D25673276005F3762 /* AccountCell.swift */; }; @@ -276,8 +180,6 @@ 5138E95324D3418100AFF0FE /* RSParser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 5138E95124D3418100AFF0FE /* RSParser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 5138E95824D3419000AFF0FE /* RSWeb in Frameworks */ = {isa = PBXBuildFile; productRef = 5138E95724D3419000AFF0FE /* RSWeb */; }; 5138E95924D3419000AFF0FE /* RSWeb in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 5138E95724D3419000AFF0FE /* RSWeb */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 51392D1B24AC19A000BE0D35 /* SidebarExpandedContainers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51392D1A24AC19A000BE0D35 /* SidebarExpandedContainers.swift */; }; - 51392D1C24AC19A000BE0D35 /* SidebarExpandedContainers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51392D1A24AC19A000BE0D35 /* SidebarExpandedContainers.swift */; }; 513C5CE9232571C2003D4054 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513C5CE8232571C2003D4054 /* ShareViewController.swift */; }; 513C5CEC232571C2003D4054 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 513C5CEA232571C2003D4054 /* MainInterface.storyboard */; }; 513C5CF0232571C2003D4054 /* NetNewsWire iOS Share Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -295,8 +197,6 @@ 513F32812593EF180003048F /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 516B695E24D2F33B00B5702F /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 513F32882593EF8F0003048F /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 513F32872593EF8F0003048F /* RSCore */; }; 513F32892593EF8F0003048F /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F32872593EF8F0003048F /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 51408B7E24A9EC6F0073CF4E /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */; }; - 51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */; }; 5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */; }; 5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142192923522B5500E07E2C /* ImageViewController.swift */; }; 514219372352510100E07E2C /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514219362352510100E07E2C /* ImageScrollView.swift */; }; @@ -320,16 +220,6 @@ 514C16DE24D2EF15009A3AFA /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16DD24D2EF15009A3AFA /* RSTree */; }; 514C16DF24D2EF15009A3AFA /* RSTree in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 514C16DD24D2EF15009A3AFA /* RSTree */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 514C16E124D2EF38009A3AFA /* RSCoreResources in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16E024D2EF38009A3AFA /* RSCoreResources */; }; - 514E6BDA24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */; }; - 514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */; }; - 514E6BFF24AD255D00AC6F6E /* PreviewArticles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6BFE24AD255D00AC6F6E /* PreviewArticles.swift */; }; - 514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6BFE24AD255D00AC6F6E /* PreviewArticles.swift */; }; - 514E6C0224AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */; }; - 514E6C0324AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */; }; - 514E6C0624AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */; }; - 514E6C0724AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */; }; - 514E6C0924AD39AD00AC6F6E /* ArticleIconImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0824AD39AD00AC6F6E /* ArticleIconImageLoader.swift */; }; - 514E6C0A24AD39AD00AC6F6E /* ArticleIconImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514E6C0824AD39AD00AC6F6E /* ArticleIconImageLoader.swift */; }; 5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; 515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; 515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; @@ -362,50 +252,12 @@ 516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9B22371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift */; }; 516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 516AE9E02372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; - 516B695B24D2F28600B5702F /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 516B695A24D2F28600B5702F /* Account */; }; - 516B695D24D2F28E00B5702F /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 516B695C24D2F28E00B5702F /* Account */; }; 516B695F24D2F33B00B5702F /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 516B695E24D2F33B00B5702F /* Account */; }; 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; }; - 5171B4D424B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; - 5171B4F624B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630232336657E00E15FFF /* WebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* WebViewProvider.swift */; }; - 5177470324B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177470224B2657F00EB0F74 /* TimelineToolbarModifier.swift */; }; - 5177470424B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177470224B2657F00EB0F74 /* TimelineToolbarModifier.swift */; }; - 5177470624B2910300EB0F74 /* ArticleToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177470524B2910300EB0F74 /* ArticleToolbarModifier.swift */; }; - 5177470724B2910300EB0F74 /* ArticleToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177470524B2910300EB0F74 /* ArticleToolbarModifier.swift */; }; - 5177470924B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177470824B2F87600EB0F74 /* SidebarListStyleModifier.swift */; }; - 5177470A24B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177470824B2F87600EB0F74 /* SidebarListStyleModifier.swift */; }; - 5177470E24B2FF6F00EB0F74 /* ArticleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177470D24B2FF6F00EB0F74 /* ArticleView.swift */; }; - 5177471024B3029400EB0F74 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177470F24B3029400EB0F74 /* ArticleViewController.swift */; }; - 5177471224B37C5400EB0F74 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471124B37C5400EB0F74 /* WebViewController.swift */; }; - 5177471424B37D4000EB0F74 /* PreloadedWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471324B37D4000EB0F74 /* PreloadedWebView.swift */; }; - 5177471624B37D9700EB0F74 /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471524B37D9700EB0F74 /* ArticleIconSchemeHandler.swift */; }; - 5177471824B3812200EB0F74 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471724B3812200EB0F74 /* IconView.swift */; }; - 5177471A24B3863000EB0F74 /* WebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471924B3863000EB0F74 /* WebViewProvider.swift */; }; - 5177471C24B387AC00EB0F74 /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471B24B387AC00EB0F74 /* ImageScrollView.swift */; }; - 5177471E24B387E100EB0F74 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471D24B387E100EB0F74 /* ImageTransition.swift */; }; - 5177472024B3882600EB0F74 /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471F24B3882600EB0F74 /* ImageViewController.swift */; }; - 5177472224B38CAE00EB0F74 /* ArticleExtractorButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177472124B38CAE00EB0F74 /* ArticleExtractorButtonState.swift */; }; - 5177475C24B39AD500EB0F74 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 5177475824B39AD400EB0F74 /* Credits.rtf */; }; - 5177475D24B39AD500EB0F74 /* Dedication.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 5177475924B39AD400EB0F74 /* Dedication.rtf */; }; - 5177475E24B39AD500EB0F74 /* Thanks.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 5177475A24B39AD500EB0F74 /* Thanks.rtf */; }; - 5177475F24B39AD500EB0F74 /* About.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 5177475B24B39AD500EB0F74 /* About.rtf */; }; - 5177476224B3BC4700EB0F74 /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177476024B3BC4700EB0F74 /* SettingsAboutView.swift */; }; - 5177476524B3BDAE00EB0F74 /* AttributedStringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177476424B3BDAE00EB0F74 /* AttributedStringView.swift */; }; - 5177476724B3BE3400EB0F74 /* SettingsAboutModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177476624B3BE3400EB0F74 /* SettingsAboutModel.swift */; }; 517A745B2443665000B553B9 /* UIPageViewController-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517A745A2443665000B553B9 /* UIPageViewController-Extensions.swift */; }; - 517B2EBC24B3E62A001AC46C /* WrapperScriptMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517B2EBB24B3E62A001AC46C /* WrapperScriptMessageHandler.swift */; }; - 517B2EE224B3E8FE001AC46C /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 517B2EDE24B3E8FE001AC46C /* page.html */; }; - 517B2EE324B3E8FE001AC46C /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 517B2EDE24B3E8FE001AC46C /* page.html */; }; - 517B2EE424B3E8FE001AC46C /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 517B2EDF24B3E8FE001AC46C /* blank.html */; }; - 517B2EE524B3E8FE001AC46C /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 517B2EDF24B3E8FE001AC46C /* blank.html */; }; - 517B2EE824B3E8FE001AC46C /* main_multiplatform.js in Resources */ = {isa = PBXBuildFile; fileRef = 517B2EE124B3E8FE001AC46C /* main_multiplatform.js */; }; - 517B2EE924B3E8FE001AC46C /* main_multiplatform.js in Resources */ = {isa = PBXBuildFile; fileRef = 517B2EE124B3E8FE001AC46C /* main_multiplatform.js */; }; - 5181C5AD24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */; }; - 5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */; }; - 5181C66224B0C326002E0F70 /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5181C66124B0C326002E0F70 /* SettingsModel.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; @@ -418,30 +270,10 @@ 51868BF1254386630011A17B /* SidebarDeleteItemsAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51868BF0254386630011A17B /* SidebarDeleteItemsAlert.swift */; }; 51868BF2254386630011A17B /* SidebarDeleteItemsAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51868BF0254386630011A17B /* SidebarDeleteItemsAlert.swift */; }; 5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5186A634235EF3A800C97195 /* VibrantLabel.swift */; }; - 51872A712706581E0078CF1A /* ArticleTheme+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */; }; - 51872A722706581F0078CF1A /* ArticleTheme+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */; }; 518B2EE82351B45600400001 /* NetNewsWire_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */; }; 518C3193237B00D9004D740F /* DetailIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */; }; 518C3194237B00DA004D740F /* DetailIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */; }; 518ED21D23D0F26000E0A862 /* UIViewController-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518ED21C23D0F26000E0A862 /* UIViewController-Extensions.swift */; }; - 51919FA624AA64B000541E64 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FA524AA64B000541E64 /* SidebarView.swift */; }; - 51919FA724AA64B000541E64 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FA524AA64B000541E64 /* SidebarView.swift */; }; - 51919FAC24AA8CCA00541E64 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAB24AA8CCA00541E64 /* UnreadCountView.swift */; }; - 51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAB24AA8CCA00541E64 /* UnreadCountView.swift */; }; - 51919FAF24AA8EFA00541E64 /* SidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */; }; - 51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */; }; - 51919FB324AAB97900541E64 /* FeedIconImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB224AAB97900541E64 /* FeedIconImageLoader.swift */; }; - 51919FB424AAB97900541E64 /* FeedIconImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB224AAB97900541E64 /* FeedIconImageLoader.swift */; }; - 51919FB624AABCA100541E64 /* IconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB524AABCA100541E64 /* IconImageView.swift */; }; - 51919FB724AABCA100541E64 /* IconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FB524AABCA100541E64 /* IconImageView.swift */; }; - 51919FEE24AB85E400541E64 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FED24AB85E400541E64 /* TimelineContainerView.swift */; }; - 51919FEF24AB85E400541E64 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FED24AB85E400541E64 /* TimelineContainerView.swift */; }; - 51919FF124AB864A00541E64 /* TimelineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FF024AB864A00541E64 /* TimelineModel.swift */; }; - 51919FF224AB864A00541E64 /* TimelineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FF024AB864A00541E64 /* TimelineModel.swift */; }; - 51919FF424AB869C00541E64 /* TimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FF324AB869C00541E64 /* TimelineItem.swift */; }; - 51919FF524AB869C00541E64 /* TimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FF324AB869C00541E64 /* TimelineItem.swift */; }; - 51919FF724AB8B7700541E64 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FF624AB8B7700541E64 /* TimelineView.swift */; }; - 51919FF824AB8B7700541E64 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51919FF624AB8B7700541E64 /* TimelineView.swift */; }; 51934CCB230F599B006127BE /* InteractiveNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CC1230F5963006127BE /* InteractiveNavigationController.swift */; }; 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; 51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; @@ -449,9 +281,6 @@ 5193CD58245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; }; 5193CD59245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; }; 5193CD5A245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; }; - 5194736E24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; }; - 5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; }; - 5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194737024BBCAF4001A2939 /* TimelineSortOrderView.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519CA8E525841DB700EB079A /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 519CA8E425841DB700EB079A /* CrashReporter */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; @@ -467,8 +296,6 @@ 51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16993235E10D600EB091F /* SettingsViewController.swift */; }; 51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; }; 51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; }; - 51A5769624AE617200078888 /* ArticleContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A5769524AE617200078888 /* ArticleContainerView.swift */; }; - 51A5769724AE617200078888 /* ArticleContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A5769524AE617200078888 /* ArticleContainerView.swift */; }; 51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; 51A737AE24DB19730015FA66 /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 51A737AD24DB19730015FA66 /* RSCore */; }; 51A737AF24DB19730015FA66 /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51A737AD24DB19730015FA66 /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -478,16 +305,6 @@ 51A737C624DB19B50015FA66 /* RSWeb in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51A737C424DB19B50015FA66 /* RSWeb */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 51A737C824DB19CC0015FA66 /* RSParser in Frameworks */ = {isa = PBXBuildFile; productRef = 51A737C724DB19CC0015FA66 /* RSParser */; }; 51A737C924DB19CC0015FA66 /* RSParser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51A737C724DB19CC0015FA66 /* RSParser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 51A8001224CA0FC700F41F1D /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001124CA0FC700F41F1D /* Sink.swift */; }; - 51A8001324CA0FC700F41F1D /* Sink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001124CA0FC700F41F1D /* Sink.swift */; }; - 51A8001524CA0FEC00F41F1D /* DemandBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */; }; - 51A8001624CA0FEC00F41F1D /* DemandBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */; }; - 51A8002D24CC451500F41F1D /* ShareReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8002C24CC451500F41F1D /* ShareReplay.swift */; }; - 51A8002E24CC451600F41F1D /* ShareReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8002C24CC451500F41F1D /* ShareReplay.swift */; }; - 51A8005124CC453C00F41F1D /* ReplaySubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8005024CC453C00F41F1D /* ReplaySubject.swift */; }; - 51A8005224CC453C00F41F1D /* ReplaySubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8005024CC453C00F41F1D /* ReplaySubject.swift */; }; - 51A8FFED24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */; }; - 51A8FFEE24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */; }; 51A9A5E12380C4FE0033AADF /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45255226507D200C03939 /* AppDefaults.swift */; }; 51A9A5E42380C8880033AADF /* ShareFolderPickerAccountCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */; }; 51A9A5E62380C8B20033AADF /* ShareFolderPickerFolderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */; }; @@ -500,15 +317,6 @@ 51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */; }; 51A9A60A2382FD240033AADF /* PoppableGestureRecognizerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A6092382FD240033AADF /* PoppableGestureRecognizerDelegate.swift */; }; 51AB8AB323B7F4C6008F147D /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AB8AB223B7F4C6008F147D /* WebViewController.swift */; }; - 51B54A4324B5499B0014348B /* WebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471924B3863000EB0F74 /* WebViewProvider.swift */; }; - 51B54A6524B549B20014348B /* WrapperScriptMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517B2EBB24B3E62A001AC46C /* WrapperScriptMessageHandler.swift */; }; - 51B54A6624B549CB0014348B /* PreloadedWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471324B37D4000EB0F74 /* PreloadedWebView.swift */; }; - 51B54A6724B549FE0014348B /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471524B37D9700EB0F74 /* ArticleIconSchemeHandler.swift */; }; - 51B54A6924B54A490014348B /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B54A6824B54A490014348B /* IconView.swift */; }; - 51B54AB324B5AC830014348B /* ArticleExtractorButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177472124B38CAE00EB0F74 /* ArticleExtractorButtonState.swift */; }; - 51B54AB624B5B33C0014348B /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B54AB524B5B33C0014348B /* WebViewController.swift */; }; - 51B54ABC24B5BEF20014348B /* ArticleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B54ABB24B5BEF20014348B /* ArticleView.swift */; }; - 51B54B6724B6A7960014348B /* WebStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B54B6624B6A7960014348B /* WebStatusBarView.swift */; }; 51B5C87723F22B8200032075 /* ExtensionContainers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C87623F22B8200032075 /* ExtensionContainers.swift */; }; 51B5C87B23F2317700032075 /* ExtensionFeedAddRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C87A23F2317700032075 /* ExtensionFeedAddRequest.swift */; }; 51B5C87D23F2346200032075 /* ExtensionContainersFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C87C23F2346200032075 /* ExtensionContainersFile.swift */; }; @@ -523,21 +331,6 @@ 51B5C8E623F4BBFA00032075 /* ExtensionFeedAddRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C87A23F2317700032075 /* ExtensionFeedAddRequest.swift */; }; 51B5C8E723F4BBFA00032075 /* ExtensionFeedAddRequestFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C8BF23F3866C00032075 /* ExtensionFeedAddRequestFile.swift */; }; 51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; }; - 51B80EB824BD1F8B00C6C32D /* ActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EB724BD1F8B00C6C32D /* ActivityViewController.swift */; }; - 51B80EDB24BD225200C6C32D /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EDA24BD225200C6C32D /* OpenInSafariActivity.swift */; }; - 51B80EDD24BD296700C6C32D /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EDC24BD296700C6C32D /* ArticleActivityItemSource.swift */; }; - 51B80EDF24BD298900C6C32D /* TitleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EDE24BD298900C6C32D /* TitleActivityItemSource.swift */; }; - 51B80EE124BD3E9600C6C32D /* FindInArticleActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80EE024BD3E9600C6C32D /* FindInArticleActivity.swift */; }; - 51B80F1F24BE531200C6C32D /* SharingServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80F1E24BE531200C6C32D /* SharingServiceView.swift */; }; - 51B80F4224BE588200C6C32D /* SharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80F4124BE588200C6C32D /* SharingServicePickerDelegate.swift */; }; - 51B80F4424BE58BF00C6C32D /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80F4324BE58BF00C6C32D /* SharingServiceDelegate.swift */; }; - 51B80F4624BF76E700C6C32D /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B80F4524BF76E700C6C32D /* Browser.swift */; }; - 51B8104524C0E6D200C6C32D /* TimelineTextSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B8104424C0E6D200C6C32D /* TimelineTextSizer.swift */; }; - 51B8104624C0E6D200C6C32D /* TimelineTextSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B8104424C0E6D200C6C32D /* TimelineTextSizer.swift */; }; - 51B8BCC224C25C3E00360B00 /* SidebarContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B8BCC124C25C3E00360B00 /* SidebarContextMenu.swift */; }; - 51B8BCC324C25C3E00360B00 /* SidebarContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B8BCC124C25C3E00360B00 /* SidebarContextMenu.swift */; }; - 51B8BCE624C25F7C00360B00 /* TimelineContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B8BCE524C25F7C00360B00 /* TimelineContextMenu.swift */; }; - 51B8BCE724C25F7C00360B00 /* TimelineContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B8BCE524C25F7C00360B00 /* TimelineContextMenu.swift */; }; 51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; }; 51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; }; 51BC2F3824D3439A00E90810 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F3724D3439A00E90810 /* Account */; }; @@ -550,10 +343,6 @@ 51BC4B01247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; 51C03081257D815A00609262 /* UnifiedWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C0307F257D815A00609262 /* UnifiedWindow.storyboard */; }; 51C03082257D815A00609262 /* UnifiedWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C0307F257D815A00609262 /* UnifiedWindow.storyboard */; }; - 51C0515E24A77DF800194D5E /* MainApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C0513624A77DF700194D5E /* MainApp.swift */; }; - 51C0515F24A77DF800194D5E /* MainApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C0513624A77DF700194D5E /* MainApp.swift */; }; - 51C0516224A77DF800194D5E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51C0513824A77DF800194D5E /* Assets.xcassets */; }; - 51C0516324A77DF800194D5E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51C0513824A77DF800194D5E /* Assets.xcassets */; }; 51C266EA238C334800F53014 /* ContextMenuPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */; }; 51C45258226508CF00C03939 /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45254226507D200C03939 /* AppAssets.swift */; }; 51C45259226508D300C03939 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45255226507D200C03939 /* AppDefaults.swift */; }; @@ -605,11 +394,7 @@ 51C4CFF024D37D1F00AF9874 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4CFEF24D37D1F00AF9874 /* Secrets.swift */; }; 51C4CFF124D37D1F00AF9874 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4CFEF24D37D1F00AF9874 /* Secrets.swift */; }; 51C4CFF224D37D1F00AF9874 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4CFEF24D37D1F00AF9874 /* Secrets.swift */; }; - 51C4CFF324D37D1F00AF9874 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4CFEF24D37D1F00AF9874 /* Secrets.swift */; }; - 51C4CFF424D37D1F00AF9874 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4CFEF24D37D1F00AF9874 /* Secrets.swift */; }; 51C4CFF624D37DD500AF9874 /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = 51C4CFF524D37DD500AF9874 /* Secrets */; }; - 51C65AFC24CCB2C9008EB3BD /* TimelineItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C65AFB24CCB2C9008EB3BD /* TimelineItems.swift */; }; - 51C65AFD24CCB2C9008EB3BD /* TimelineItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C65AFB24CCB2C9008EB3BD /* TimelineItems.swift */; }; 51C9DE5823EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C9DE5723EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift */; }; 51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */; }; 51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CE1C0A23622006005548FC /* RefreshProgressView.swift */; }; @@ -617,16 +402,12 @@ 51D0214626ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; }; 51D0214726ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; }; 51D0214826ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; }; - 51D0214926ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; }; - 51D0214A26ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; }; 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */; }; 51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; 51DC07982552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; 51DC07992552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; 51DC079A2552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; - 51DC079B2552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; - 51DC079C2552083500A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; 51DC07AC255209E200A3F79F /* ArticleTextSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC07972552083500A3F79F /* ArticleTextSize.swift */; }; 51DC37072402153E0095D371 /* UpdateSelectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC37062402153E0095D371 /* UpdateSelectionOperation.swift */; }; 51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC37082402F1470095D371 /* MasterFeedDataSourceOperation.swift */; }; @@ -634,163 +415,15 @@ 51DEE81226FB9233006DAA56 /* Appanoose.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81126FB9233006DAA56 /* Appanoose.nnwtheme */; }; 51DEE81326FB9233006DAA56 /* Appanoose.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81126FB9233006DAA56 /* Appanoose.nnwtheme */; }; 51DEE81426FB9233006DAA56 /* Appanoose.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81126FB9233006DAA56 /* Appanoose.nnwtheme */; }; - 51DEE81526FB9233006DAA56 /* Appanoose.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81126FB9233006DAA56 /* Appanoose.nnwtheme */; }; - 51DEE81626FB9233006DAA56 /* Appanoose.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81126FB9233006DAA56 /* Appanoose.nnwtheme */; }; 51DEE81826FBFF84006DAA56 /* Promenade.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81726FBFF84006DAA56 /* Promenade.nnwtheme */; }; 51DEE81926FBFF84006DAA56 /* Promenade.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81726FBFF84006DAA56 /* Promenade.nnwtheme */; }; 51DEE81A26FBFF84006DAA56 /* Promenade.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81726FBFF84006DAA56 /* Promenade.nnwtheme */; }; - 51DEE81B26FBFF84006DAA56 /* Promenade.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81726FBFF84006DAA56 /* Promenade.nnwtheme */; }; - 51DEE81C26FBFF84006DAA56 /* Promenade.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 51DEE81726FBFF84006DAA56 /* Promenade.nnwtheme */; }; - 51E0614525A5A28E00194066 /* Articles in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614425A5A28E00194066 /* Articles */; }; - 51E0614625A5A28E00194066 /* Articles in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614425A5A28E00194066 /* Articles */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 51E0614825A5A28E00194066 /* ArticlesDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614725A5A28E00194066 /* ArticlesDatabase */; }; - 51E0614925A5A28E00194066 /* ArticlesDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614725A5A28E00194066 /* ArticlesDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 51E0614B25A5A28E00194066 /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614A25A5A28E00194066 /* Secrets */; }; - 51E0614C25A5A28E00194066 /* Secrets in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614A25A5A28E00194066 /* Secrets */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 51E0614E25A5A28E00194066 /* SyncDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614D25A5A28E00194066 /* SyncDatabase */; }; - 51E0614F25A5A28E00194066 /* SyncDatabase in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51E0614D25A5A28E00194066 /* SyncDatabase */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 51E0615125A5A29600194066 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 51E0615025A5A29600194066 /* CrashReporter */; }; 51E36E71239D6610006F47A5 /* AddFeedSelectFolderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E36E70239D6610006F47A5 /* AddFeedSelectFolderTableViewCell.swift */; }; 51E36E8C239D6765006F47A5 /* AddFeedSelectFolderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51E36E8B239D6765006F47A5 /* AddFeedSelectFolderTableViewCell.xib */; }; 51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; }; 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB3C229AB08300645299 /* ErrorHandler.swift */; }; 51E43962238037C400015C31 /* AddFeedFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E43961238037C400015C31 /* AddFeedFolderViewController.swift */; }; 51E4398023805EBC00015C31 /* AddComboTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397F23805EBC00015C31 /* AddComboTableViewCell.swift */; }; - 51E4989724A8065700B667CB /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4989624A8065700B667CB /* CloudKit.framework */; }; - 51E4989924A8067000B667CB /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4989824A8067000B667CB /* WebKit.framework */; }; - 51E498B124A806A400B667CB /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4DAEC2425F6940091EB5B /* CloudKit.framework */; }; - 51E498B324A806AA00B667CB /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E498B224A806AA00B667CB /* WebKit.framework */; }; - 51E498C724A8085D00B667CB /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; - 51E498C824A8085D00B667CB /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; - 51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; - 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; }; - 51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; - 51E498CC24A8085D00B667CB /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; - 51E498CD24A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; - 51E498CE24A8085D00B667CB /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; - 51E498CF24A8085D00B667CB /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; - 51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; - 51E498F224A8085D00B667CB /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; - 51E498F324A8085D00B667CB /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; - 51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; }; - 51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; - 51E498F624A8085D00B667CB /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; - 51E498F724A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; - 51E498F824A8085D00B667CB /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; - 51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; - 51E498FA24A808BA00B667CB /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; - 51E498FB24A808BA00B667CB /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; - 51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; - 51E498FD24A808BA00B667CB /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; - 51E498FE24A808BA00B667CB /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; - 51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; - 51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; - 51E4990124A808BB00B667CB /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; - 51E4990224A808BB00B667CB /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; - 51E4990324A808BB00B667CB /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; - 51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; }; - 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */; }; - 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; - 51E4990724A808C300B667CB /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; - 51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; - 51E4990924A808C500B667CB /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; }; - 51E4990A24A808C500B667CB /* FeaturedImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */; }; - 51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; - 51E4990C24A808C500B667CB /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; - 51E4990D24A808C500B667CB /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; - 51E4990E24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; - 51E4990F24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; - 51E4991024A808DE00B667CB /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; }; - 51E4991124A808DE00B667CB /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; }; - 51E4991224A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; - 51E4991324A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; - 51E4991424A808FF00B667CB /* ArticleStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */; }; - 51E4991524A808FF00B667CB /* ArticleStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */; }; - 51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; - 51E4991724A8090400B667CB /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; - 51E4991824A8090A00B667CB /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; - 51E4991924A8090A00B667CB /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; - 51E4991A24A8090F00B667CB /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; - 51E4991B24A8091000B667CB /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; - 51E4991C24A8092000B667CB /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; }; - 51E4991D24A8092100B667CB /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; }; - 51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; }; - 51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; }; - 51E4992024A8095000B667CB /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; - 51E4992124A8095000B667CB /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; - 51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; - 51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; - 51E4992424A8098400B667CB /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; }; - 51E4992624A80AAB00B667CB /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4992524A80AAB00B667CB /* AppAssets.swift */; }; - 51E4992724A80AAB00B667CB /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4992524A80AAB00B667CB /* AppAssets.swift */; }; - 51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; - 51E4992C24A8676300B667CB /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; - 51E4992D24A8676300B667CB /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; }; - 51E4992E24A8676300B667CB /* FetchRequestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */; }; - 51E4992F24A8676400B667CB /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; - 51E4993024A8676400B667CB /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; - 51E4993124A8676400B667CB /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; }; - 51E4993224A8676400B667CB /* FetchRequestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */; }; - 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; - 51E4993424A867E700B667CB /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; - 51E4993524A867E800B667CB /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; - 51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; - 51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4993924A8708800B667CB /* AppDelegate.swift */; }; - 51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4993B24A8709900B667CB /* AppDelegate.swift */; }; - 51E4993D24A870F800B667CB /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; - 51E4993E24A870F900B667CB /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; - 51E4993F24A8713B00B667CB /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; - 51E4994024A8713B00B667CB /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; - 51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; - 51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; - 51E4994B24A8734C00B667CB /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; - 51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; }; - 51E4994D24A8734C00B667CB /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; - 51E4994E24A8734C00B667CB /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; - 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; }; - 51E4995024A8734C00B667CB /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; - 51E4995124A8734D00B667CB /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; - 51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; }; - 51E4995424A8734D00B667CB /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; - 51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; }; - 51E4995724A8734D00B667CB /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; - 51E4995924A873F900B667CB /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4995824A873F900B667CB /* ErrorHandler.swift */; }; - 51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4995824A873F900B667CB /* ErrorHandler.swift */; }; - 51E4995B24A875D500B667CB /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; }; - 51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; - 51E4995D24A875F300B667CB /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; - 51E4995E24A875F300B667CB /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; - 51E4995F24A875F300B667CB /* stylesheet.css in Resources */ = {isa = PBXBuildFile; fileRef = B27EEBDF244D15F2000932E6 /* stylesheet.css */; }; - 51E4996024A875F300B667CB /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; - 51E4996124A875F400B667CB /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; - 51E4996224A875F400B667CB /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; - 51E4996324A875F400B667CB /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; - 51E4996424A875F400B667CB /* stylesheet.css in Resources */ = {isa = PBXBuildFile; fileRef = B27EEBDF244D15F2000932E6 /* stylesheet.css */; }; - 51E4996524A875F400B667CB /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; - 51E4996624A8760B00B667CB /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; }; - 51E4996724A8760B00B667CB /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; }; - 51E4996824A8760C00B667CB /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; }; - 51E4996924A8760C00B667CB /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; }; - 51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; - 51E4996B24A8762D00B667CB /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; - 51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; - 51E4996D24A8762D00B667CB /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; - 51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; - 51E4996F24A8764C00B667CB /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; - 51E4997024A8764C00B667CB /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; - 51E4997124A8764C00B667CB /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; - 51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; - 51E4997324A8784300B667CB /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; }; - 51E4997424A8784400B667CB /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; - 51E4997524A8784400B667CB /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; }; - 51E499D824A912C200B667CB /* SceneModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E499D724A912C200B667CB /* SceneModel.swift */; }; - 51E499D924A912C200B667CB /* SceneModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E499D724A912C200B667CB /* SceneModel.swift */; }; - 51E499FD24A9137600B667CB /* SidebarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E499FC24A9137600B667CB /* SidebarModel.swift */; }; - 51E499FE24A9137600B667CB /* SidebarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E499FC24A9137600B667CB /* SidebarModel.swift */; }; - 51E49A0024A91FC100B667CB /* SidebarContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E499FF24A91FC100B667CB /* SidebarContainerView.swift */; }; - 51E49A0124A91FC100B667CB /* SidebarContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E499FF24A91FC100B667CB /* SidebarContainerView.swift */; }; - 51E49A0324A91FF600B667CB /* SceneNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E49A0224A91FF600B667CB /* SceneNavigationView.swift */; }; - 51E49A0424A91FF600B667CB /* SceneNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E49A0224A91FF600B667CB /* SceneNavigationView.swift */; }; 51E4DAED2425F6940091EB5B /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4DAEC2425F6940091EB5B /* CloudKit.framework */; }; 51E4DB082425F9EB0091EB5B /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4DB072425F9EB0091EB5B /* CloudKit.framework */; }; 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; @@ -829,11 +462,6 @@ 55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; }; 55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; }; 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */; }; - 65082A2F24C72AC8009FA994 /* SettingsCredentialsAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65082A2E24C72AC8009FA994 /* SettingsCredentialsAccountView.swift */; }; - 65082A5224C72B88009FA994 /* SettingsCredentialsAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65082A5124C72B88009FA994 /* SettingsCredentialsAccountModel.swift */; }; - 65082A5424C73D2F009FA994 /* AccountCredentialsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65082A5324C73D2F009FA994 /* AccountCredentialsError.swift */; }; - 6535ECFC2680F9FF00C01CB5 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; }; - 6535ECFD2680FA0000C01CB5 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; }; 653813182680E152007A082C /* AccountType+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173A64162547BE0900267F6E /* AccountType+Helpers.swift */; }; 653813192680E15B007A082C /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; 6538131A2680E16C007A082C /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */; }; @@ -874,22 +502,11 @@ 653813522680E2DA007A082C /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; 653813542680E2DA007A082C /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 653813402680E2DA007A082C /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 6538135C2680E47A007A082C /* NetNewsWire Share Extension MAS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 653813592680E2DA007A082C /* NetNewsWire Share Extension MAS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 653A4E7924BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A4E7824BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift */; }; - 65422D1724B75CD1008A2FA2 /* SettingsAddAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65422D1624B75CD1008A2FA2 /* SettingsAddAccountModel.swift */; }; 6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; }; 6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; }; 6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; }; 6581C74020CED60100F4AD34 /* netnewswire-subscribe-to-feed.js in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */; }; 6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; }; - 6586A5F724B632F8002BCF4F /* SettingsDetailAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6586A5F624B632F8002BCF4F /* SettingsDetailAccountModel.swift */; }; - 6591723124B5C35400B638E8 /* AccountHeaderImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6591723024B5C35400B638E8 /* AccountHeaderImageView.swift */; }; - 6591727F24B5D19500B638E8 /* SettingsDetailAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6591727E24B5D19500B638E8 /* SettingsDetailAccountView.swift */; }; - 6594CA3B24AF6F2A005C7D7C /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; }; - 65ACE48424B4779B003AE06A /* SettingsAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65ACE48324B4779B003AE06A /* SettingsAddAccountView.swift */; }; - 65ACE48624B477C9003AE06A /* SettingsAccountLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65ACE48524B477C9003AE06A /* SettingsAccountLabelView.swift */; }; - 65ACE48824B48020003AE06A /* SettingsLocalAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65ACE48724B48020003AE06A /* SettingsLocalAccountView.swift */; }; - 65C2E40124B05D8A000AFDF6 /* FeedsSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65C2E40024B05D8A000AFDF6 /* FeedsSettingsModel.swift */; }; - 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */; }; 65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; 65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; }; @@ -1235,19 +852,9 @@ D5F4EDB720074D6500B9E363 /* WebFeed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */; }; D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; }; DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; }; - DF98E29A2578A73A00F18944 /* AddCloudKitAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF98E2992578A73A00F18944 /* AddCloudKitAccountView.swift */; }; - DF98E2B02578AA5C00F18944 /* AddFeedWranglerAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF98E2AF2578AA5C00F18944 /* AddFeedWranglerAccountView.swift */; }; - DF98E2BE2578AC0000F18944 /* AddNewsBlurAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF98E2BD2578AC0000F18944 /* AddNewsBlurAccountView.swift */; }; - DF98E2C62578AD1B00F18944 /* AddReaderAPIAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF98E2C52578AD1B00F18944 /* AddReaderAPIAccountView.swift */; }; - FA80C11724B0728000974098 /* AddFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA80C11624B0728000974098 /* AddFolderView.swift */; }; - FA80C11824B0728000974098 /* AddFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA80C11624B0728000974098 /* AddFolderView.swift */; }; - FA80C13E24B072AA00974098 /* AddFolderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA80C13D24B072AA00974098 /* AddFolderModel.swift */; }; - FA80C13F24B072AB00974098 /* AddFolderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA80C13D24B072AA00974098 /* AddFolderModel.swift */; }; FF3ABF13232599810074C542 /* ArticleSorterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF09232599450074C542 /* ArticleSorterTests.swift */; }; FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; - FF64D0E724AF53EE0084080A /* RefreshProgressModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF64D0C424AF53EE0084080A /* RefreshProgressModel.swift */; }; - FF64D0E824AF53EE0084080A /* RefreshProgressModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF64D0C424AF53EE0084080A /* RefreshProgressModel.swift */; }; FFD43E412340F488009E5CA3 /* MarkAsReadAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD43E372340F320009E5CA3 /* MarkAsReadAlertController.swift */; }; /* End PBXBuildFile section */ @@ -1408,50 +1015,6 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; - 51E4989524A8061400B667CB /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 17A1598924E3DEDD005DA32A /* RSParser in Embed Frameworks */, - 17A1598624E3DEDD005DA32A /* RSDatabase in Embed Frameworks */, - 17A1598324E3DEDD005DA32A /* RSWeb in Embed Frameworks */, - 17A1597D24E3DEDD005DA32A /* RSCore in Embed Frameworks */, - 17A1598024E3DEDD005DA32A /* RSTree in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - 51E498B024A8069300B667CB /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 51E0614C25A5A28E00194066 /* Secrets in Embed Frameworks */, - 17386BA52577C6240014C8B2 /* RSParser in Embed Frameworks */, - 17386B9F2577C6240014C8B2 /* RSDatabase in Embed Frameworks */, - 17386B9C2577C6240014C8B2 /* RSWeb in Embed Frameworks */, - 51E0614F25A5A28E00194066 /* SyncDatabase in Embed Frameworks */, - 51E0614625A5A28E00194066 /* Articles in Embed Frameworks */, - 17386B962577C6240014C8B2 /* RSCore in Embed Frameworks */, - 17386B992577C6240014C8B2 /* RSTree in Embed Frameworks */, - 51E0614925A5A28E00194066 /* ArticlesDatabase in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - 51E4994924A872AD00B667CB /* Embed XPC Services */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; - dstSubfolderSpec = 16; - files = ( - ); - name = "Embed XPC Services"; - runOnlyForDeploymentPostprocessing = 0; - }; 653813532680E2DA007A082C /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1554,33 +1117,13 @@ /* Begin PBXFileReference section */ 1701E1B62568983D009453D8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 1701E1E625689D1E009453D8 /* Localized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = ""; }; - 1704053324E5985A00A00787 /* SceneNavigationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneNavigationModel.swift; sourceTree = ""; }; 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ArticleTheme+Notifications.swift"; sourceTree = ""; }; 1710B9122552354E00679C0D /* AddAccountHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountHelpView.swift; sourceTree = ""; }; 1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointHelpView.swift; sourceTree = ""; }; - 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesModel.swift; sourceTree = ""; }; 17192AE12567B3FE00AAEACA /* org.sparkle-project.Downloader.xpc */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.xpc-service"; path = "org.sparkle-project.Downloader.xpc"; sourceTree = ""; }; 17192AE22567B3FE00AAEACA /* org.sparkle-project.InstallerConnection.xpc */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.xpc-service"; path = "org.sparkle-project.InstallerConnection.xpc"; sourceTree = ""; }; 17192AE32567B3FE00AAEACA /* org.sparkle-project.InstallerLauncher.xpc */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.xpc-service"; path = "org.sparkle-project.InstallerLauncher.xpc"; sourceTree = ""; }; 17192AE42567B3FE00AAEACA /* org.sparkle-project.InstallerStatus.xpc */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.xpc-service"; path = "org.sparkle-project.InstallerStatus.xpc"; sourceTree = ""; }; - 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixAccountCredentialView.swift; sourceTree = ""; }; - 172199C824AB228900A31D04 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - 172199EC24AB2E0100A31D04 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; - 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModifier.swift; sourceTree = ""; }; - 17241248257B8A8A00ACCEBC /* AddFeedlyAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedlyAccountView.swift; sourceTree = ""; }; - 17241269257BBEBB00ACCEBC /* AddFeedbinViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedbinViewModel.swift; sourceTree = ""; }; - 17241277257BBEE700ACCEBC /* AddFeedlyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedlyViewModel.swift; sourceTree = ""; }; - 1724127F257BBF3E00ACCEBC /* AddFeedWranglerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedWranglerViewModel.swift; sourceTree = ""; }; - 17241287257BBF7000ACCEBC /* AddNewsBlurViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddNewsBlurViewModel.swift; sourceTree = ""; }; - 1724128F257BBFAD00ACCEBC /* AddReaderAPIViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddReaderAPIViewModel.swift; sourceTree = ""; }; - 1727B39824C1368D00A4DBDC /* LayoutPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutPreferencesView.swift; sourceTree = ""; }; - 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsPreferencesView.swift; sourceTree = ""; }; - 1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedPreferencesView.swift; sourceTree = ""; }; - 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesView.swift; sourceTree = ""; }; - 1729529624AA1CD000D65E66 /* MacPreferencePanes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacPreferencePanes.swift; sourceTree = ""; }; - 1729529A24AA1FD200D65E66 /* MacSearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacSearchField.swift; sourceTree = ""; }; - 17386B792577C4BF0014C8B2 /* AddLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddLocalAccountView.swift; sourceTree = ""; }; - 17386BC32577CC600014C8B2 /* AddFeedbinAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedbinAccountView.swift; sourceTree = ""; }; 173A64162547BE0900267F6E /* AccountType+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountType+Helpers.swift"; sourceTree = ""; }; 176813B62564B9F800D98635 /* WidgetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetData.swift; sourceTree = ""; }; 176813BD2564BA2800D98635 /* WidgetDataEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataEncoder.swift; sourceTree = ""; }; @@ -1601,33 +1144,16 @@ 176814562564BD0600D98635 /* ArticleItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleItemView.swift; sourceTree = ""; }; 1768147A2564BE5400D98635 /* widget-sample.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "widget-sample.json"; sourceTree = ""; }; 176814822564C02A00D98635 /* NetNewsWire_iOS_WidgetExtension.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_WidgetExtension.entitlements; sourceTree = ""; }; - 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsPreferencesModel.swift; sourceTree = ""; }; - 1769E32424BC5A65000E1E8E /* AddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountView.swift; sourceTree = ""; }; - 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfiguredAccountRow.swift; sourceTree = ""; }; - 1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDetailView.swift; sourceTree = ""; }; - 1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountCredentialsView.swift; sourceTree = ""; }; - 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountCredentialsModel.swift; sourceTree = ""; }; - 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUpdateErrors.swift; sourceTree = ""; }; - 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; 177A0C2C25454AAB00D7EAF6 /* ReaderAPIAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderAPIAccountViewController.swift; sourceTree = ""; }; - 17897AC924C281A40014BA03 /* InspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorView.swift; sourceTree = ""; }; 178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountsView.swift; sourceTree = ""; }; - 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = ""; }; - 1799E6A824C2F93F00511E91 /* InspectorPlatformModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorPlatformModifier.swift; sourceTree = ""; }; - 1799E6CC24C320D600511E91 /* InspectorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorModel.swift; sourceTree = ""; }; 179D280C26F73D83003B2E0A /* ArticleThemePlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemePlist.swift; sourceTree = ""; }; 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; }; - 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLayoutView.swift; sourceTree = ""; }; 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; - 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedModel.swift; sourceTree = ""; }; - 17D3CEE2257C4D2300E74939 /* AddAccountSignUp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountSignUp.swift; sourceTree = ""; }; - 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModel.swift; sourceTree = ""; }; 17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemeDownloader.swift; sourceTree = ""; }; 17D7586C2679C21700B17787 /* NetNewsWire-iOS-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-iOS-Bridging-Header.h"; sourceTree = ""; }; 17D7586D2679C21800B17787 /* OnePasswordExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OnePasswordExtension.h; sourceTree = ""; }; 17D7586E2679C21800B17787 /* OnePasswordExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OnePasswordExtension.m; sourceTree = ""; }; 17E0084525941887000C23F0 /* SizeCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeCategories.swift; sourceTree = ""; }; - 17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPreferencesModel.swift; sourceTree = ""; }; 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = ""; }; 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; }; 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = ""; }; @@ -1684,13 +1210,11 @@ 5137C2E326F3F52D009EFEDB /* Sepia.nnwtheme */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Sepia.nnwtheme; sourceTree = ""; }; 5137C2E926F63AE6009EFEDB /* ArticleThemeImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemeImporter.swift; sourceTree = ""; }; 51386A8D25673276005F3762 /* AccountCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCell.swift; sourceTree = ""; }; - 51392D1A24AC19A000BE0D35 /* SidebarExpandedContainers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarExpandedContainers.swift; sourceTree = ""; }; 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NetNewsWire iOS Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 513C5CE8232571C2003D4054 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; 513C5CEB232571C2003D4054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; 513C5CED232571C2003D4054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 513CCF08248808BA00C55709 /* MasterFeedTableViewIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedTableViewIdentifier.swift; sourceTree = ""; }; - 51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItem.swift; sourceTree = ""; }; 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = ""; }; 5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailIconSchemeHandler.swift; sourceTree = ""; }; 5142192923522B5500E07E2C /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; @@ -1709,11 +1233,6 @@ 514A8980244FD63F0085E65D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddTwitterFeedSheet.xib; sourceTree = SOURCE_ROOT; }; 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AddTwitterFeedWindowController.swift; path = AddFeed/AddTwitterFeedWindowController.swift; sourceTree = ""; }; 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = ""; }; - 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemView.swift; sourceTree = ""; }; - 514E6BFE24AD255D00AC6F6E /* PreviewArticles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewArticles.swift; sourceTree = ""; }; - 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemStatusView.swift; sourceTree = ""; }; - 514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image-Extensions.swift"; sourceTree = ""; }; - 514E6C0824AD39AD00AC6F6E /* ArticleIconImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconImageLoader.swift; sourceTree = ""; }; 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = ""; }; 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider-Extensions.swift"; sourceTree = ""; }; 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointEnableWindowController.swift; sourceTree = ""; }; @@ -1743,34 +1262,7 @@ 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = ""; }; 517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = ""; }; 517630222336657E00E15FFF /* WebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewProvider.swift; sourceTree = ""; }; - 5177470224B2657F00EB0F74 /* TimelineToolbarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineToolbarModifier.swift; sourceTree = ""; }; - 5177470524B2910300EB0F74 /* ArticleToolbarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleToolbarModifier.swift; sourceTree = ""; }; - 5177470824B2F87600EB0F74 /* SidebarListStyleModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListStyleModifier.swift; sourceTree = ""; }; - 5177470D24B2FF6F00EB0F74 /* ArticleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleView.swift; sourceTree = ""; }; - 5177470F24B3029400EB0F74 /* ArticleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleViewController.swift; sourceTree = ""; }; - 5177471124B37C5400EB0F74 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; - 5177471324B37D4000EB0F74 /* PreloadedWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreloadedWebView.swift; sourceTree = ""; }; - 5177471524B37D9700EB0F74 /* ArticleIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconSchemeHandler.swift; sourceTree = ""; }; - 5177471724B3812200EB0F74 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; - 5177471924B3863000EB0F74 /* WebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewProvider.swift; sourceTree = ""; }; - 5177471B24B387AC00EB0F74 /* ImageScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = ""; }; - 5177471D24B387E100EB0F74 /* ImageTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransition.swift; sourceTree = ""; }; - 5177471F24B3882600EB0F74 /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; - 5177472124B38CAE00EB0F74 /* ArticleExtractorButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButtonState.swift; sourceTree = ""; }; - 5177475824B39AD400EB0F74 /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; - 5177475924B39AD400EB0F74 /* Dedication.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Dedication.rtf; sourceTree = ""; }; - 5177475A24B39AD500EB0F74 /* Thanks.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Thanks.rtf; sourceTree = ""; }; - 5177475B24B39AD500EB0F74 /* About.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = About.rtf; sourceTree = ""; }; - 5177476024B3BC4700EB0F74 /* SettingsAboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = ""; }; - 5177476424B3BDAE00EB0F74 /* AttributedStringView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedStringView.swift; sourceTree = ""; }; - 5177476624B3BE3400EB0F74 /* SettingsAboutModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAboutModel.swift; sourceTree = ""; }; 517A745A2443665000B553B9 /* UIPageViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPageViewController-Extensions.swift"; sourceTree = ""; }; - 517B2EBB24B3E62A001AC46C /* WrapperScriptMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrapperScriptMessageHandler.swift; sourceTree = ""; }; - 517B2EDE24B3E8FE001AC46C /* page.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; - 517B2EDF24B3E8FE001AC46C /* blank.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; - 517B2EE124B3E8FE001AC46C /* main_multiplatform.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = main_multiplatform.js; sourceTree = ""; }; - 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferredColorSchemeModifier.swift; sourceTree = ""; }; - 5181C66124B0C326002E0F70 /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshInterval.swift; sourceTree = ""; }; @@ -1783,21 +1275,10 @@ 518B2ED22351B3DD00400001 /* NetNewsWire-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "NetNewsWire-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSTests_target.xcconfig; sourceTree = ""; }; 518ED21C23D0F26000E0A862 /* UIViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController-Extensions.swift"; sourceTree = ""; }; - 51919FA524AA64B000541E64 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; - 51919FAB24AA8CCA00541E64 /* UnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadCountView.swift; sourceTree = ""; }; - 51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItemView.swift; sourceTree = ""; }; - 51919FB224AAB97900541E64 /* FeedIconImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconImageLoader.swift; sourceTree = ""; }; - 51919FB524AABCA100541E64 /* IconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImageView.swift; sourceTree = ""; }; - 51919FED24AB85E400541E64 /* TimelineContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineContainerView.swift; sourceTree = ""; }; - 51919FF024AB864A00541E64 /* TimelineModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineModel.swift; sourceTree = ""; }; - 51919FF324AB869C00541E64 /* TimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItem.swift; sourceTree = ""; }; - 51919FF624AB8B7700541E64 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = ""; }; 51934CC1230F5963006127BE /* InteractiveNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveNavigationController.swift; sourceTree = ""; }; 51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = ""; }; 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = ""; }; 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RedditFeedProvider-Extensions.swift"; sourceTree = ""; }; - 5194736D24BBB937001A2939 /* HiddenModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenModifier.swift; sourceTree = ""; }; - 5194737024BBCAF4001A2939 /* TimelineSortOrderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSortOrderView.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = ""; }; @@ -1811,58 +1292,24 @@ 51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = ""; }; - 51A5769524AE617200078888 /* ArticleContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleContainerView.swift; sourceTree = ""; }; 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedDefaultContainer.swift; sourceTree = ""; }; - 51A8001124CA0FC700F41F1D /* Sink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sink.swift; sourceTree = ""; }; - 51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemandBuffer.swift; sourceTree = ""; }; - 51A8002C24CC451500F41F1D /* ShareReplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareReplay.swift; sourceTree = ""; }; - 51A8005024CC453C00F41F1D /* ReplaySubject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplaySubject.swift; sourceTree = ""; }; - 51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WIthLatestFrom.swift; sourceTree = ""; }; 51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerAccountCell.xib; sourceTree = ""; }; 51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerFolderCell.xib; sourceTree = ""; }; 51A9A5E72380CA130033AADF /* ShareFolderPickerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerCell.swift; sourceTree = ""; }; 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalNavigationController.swift; sourceTree = ""; }; 51A9A6092382FD240033AADF /* PoppableGestureRecognizerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoppableGestureRecognizerDelegate.swift; sourceTree = ""; }; 51AB8AB223B7F4C6008F147D /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; - 51B54A6824B54A490014348B /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; - 51B54AB524B5B33C0014348B /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; - 51B54ABB24B5BEF20014348B /* ArticleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleView.swift; sourceTree = ""; }; - 51B54B6624B6A7960014348B /* WebStatusBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebStatusBarView.swift; sourceTree = ""; }; 51B5C87623F22B8200032075 /* ExtensionContainers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionContainers.swift; sourceTree = ""; }; 51B5C87A23F2317700032075 /* ExtensionFeedAddRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionFeedAddRequest.swift; sourceTree = ""; }; 51B5C87C23F2346200032075 /* ExtensionContainersFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionContainersFile.swift; sourceTree = ""; }; 51B5C8BC23F3780900032075 /* ShareDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareDefaultContainer.swift; sourceTree = ""; }; 51B5C8BF23F3866C00032075 /* ExtensionFeedAddRequestFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionFeedAddRequestFile.swift; sourceTree = ""; }; 51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; - 51B80EB724BD1F8B00C6C32D /* ActivityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityViewController.swift; sourceTree = ""; }; - 51B80EDA24BD225200C6C32D /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = ""; }; - 51B80EDC24BD296700C6C32D /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; - 51B80EDE24BD298900C6C32D /* TitleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleActivityItemSource.swift; sourceTree = ""; }; - 51B80EE024BD3E9600C6C32D /* FindInArticleActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindInArticleActivity.swift; sourceTree = ""; }; - 51B80F1E24BE531200C6C32D /* SharingServiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceView.swift; sourceTree = ""; }; - 51B80F4124BE588200C6C32D /* SharingServicePickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServicePickerDelegate.swift; sourceTree = ""; }; - 51B80F4324BE58BF00C6C32D /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; - 51B80F4524BF76E700C6C32D /* Browser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Browser.swift; sourceTree = ""; }; - 51B8104424C0E6D200C6C32D /* TimelineTextSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTextSizer.swift; sourceTree = ""; }; - 51B8BCC124C25C3E00360B00 /* SidebarContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarContextMenu.swift; sourceTree = ""; }; - 51B8BCE524C25F7C00360B00 /* TimelineContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineContextMenu.swift; sourceTree = ""; }; 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = ""; }; 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL-Extensions.swift"; sourceTree = ""; }; 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterEnterDetailTableViewController.swift; sourceTree = ""; }; 51C03080257D815A00609262 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Mac/Base.lproj/UnifiedWindow.storyboard; sourceTree = SOURCE_ROOT; }; - 51C0513624A77DF700194D5E /* MainApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainApp.swift; sourceTree = ""; }; - 51C0513824A77DF800194D5E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 51C0513D24A77DF800194D5E /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 51C0513F24A77DF800194D5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 51C0514424A77DF800194D5E /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 51C0514624A77DF800194D5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 51C0514724A77DF800194D5E /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; - 51C0519724A7808F00194D5E /* NetNewsWire_multiplatform_iOSapp_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_multiplatform_iOSapp_target.xcconfig; sourceTree = ""; }; - 51C0519824A7808F00194D5E /* NetNewsWire_multiplatform_macOSapp_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_multiplatform_macOSapp_target.xcconfig; sourceTree = ""; }; - 51C051CD24A7A6DB00194D5E /* macOS-dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "macOS-dev.entitlements"; sourceTree = ""; }; - 51C051CE24A7A72100194D5E /* iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iOS.entitlements; sourceTree = ""; }; - 51C051CF24A7A72100194D5E /* iOS-dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "iOS-dev.entitlements"; sourceTree = ""; }; 51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuPreviewViewController.swift; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = ""; }; @@ -1884,7 +1331,6 @@ 51C4528B2265095F00C03939 /* AddFolderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderViewController.swift; sourceTree = ""; }; 51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 51C4CFEF24D37D1F00AF9874 /* Secrets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; - 51C65AFB24CCB2C9008EB3BD /* TimelineItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItems.swift; sourceTree = ""; }; 51C9DE5723EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrapperScriptMessageHandler.swift; sourceTree = ""; }; 51CD32A824D2CB25009ABAEF /* SyncDatabase */ = {isa = PBXFileReference; lastKnownFileType = folder; path = SyncDatabase; sourceTree = ""; }; 51CD32C324D2CD57009ABAEF /* ArticlesDatabase */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ArticlesDatabase; sourceTree = ""; }; @@ -1911,14 +1357,6 @@ 51E4989624A8065700B667CB /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; }; 51E4989824A8067000B667CB /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 51E498B224A806AA00B667CB /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - 51E4992524A80AAB00B667CB /* AppAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; - 51E4993924A8708800B667CB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 51E4993B24A8709900B667CB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 51E4995824A873F900B667CB /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; - 51E499D724A912C200B667CB /* SceneModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneModel.swift; sourceTree = ""; }; - 51E499FC24A9137600B667CB /* SidebarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarModel.swift; sourceTree = ""; }; - 51E499FF24A91FC100B667CB /* SidebarContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarContainerView.swift; sourceTree = ""; }; - 51E49A0224A91FF600B667CB /* SceneNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneNavigationView.swift; sourceTree = ""; }; 51E4DAEC2425F6940091EB5B /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 51E4DB072425F9EB0091EB5B /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; }; 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = ""; }; @@ -1951,13 +1389,8 @@ 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; }; 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; }; 5F323808231DF9F000706F6B /* VibrantTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantTableViewCell.swift; sourceTree = ""; }; - 65082A2E24C72AC8009FA994 /* SettingsCredentialsAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCredentialsAccountView.swift; sourceTree = ""; }; - 65082A5124C72B88009FA994 /* SettingsCredentialsAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCredentialsAccountModel.swift; sourceTree = ""; }; - 65082A5324C73D2F009FA994 /* AccountCredentialsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCredentialsError.swift; sourceTree = ""; }; 653813592680E2DA007A082C /* NetNewsWire Share Extension MAS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NetNewsWire Share Extension MAS.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 6538135B2680E3A9007A082C /* NetNewsWire_shareextension_target_macappstore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_shareextension_target_macappstore.xcconfig; sourceTree = ""; }; - 653A4E7824BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCloudKitAccountView.swift; sourceTree = ""; }; - 65422D1624B75CD1008A2FA2 /* SettingsAddAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAddAccountModel.swift; sourceTree = ""; }; 6543108B2322D90900658221 /* common */ = {isa = PBXFileReference; lastKnownFileType = folder; path = common; sourceTree = ""; }; 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; @@ -1968,14 +1401,6 @@ 6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "netnewswire-subscribe-to-feed.js"; sourceTree = ""; }; 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = ""; }; 6581C74320CED60100F4AD34 /* Subscribe_to_Feed.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Subscribe_to_Feed.entitlements; sourceTree = ""; }; - 6586A5F624B632F8002BCF4F /* SettingsDetailAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailAccountModel.swift; sourceTree = ""; }; - 6591723024B5C35400B638E8 /* AccountHeaderImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderImageView.swift; sourceTree = ""; }; - 6591727E24B5D19500B638E8 /* SettingsDetailAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailAccountView.swift; sourceTree = ""; }; - 65ACE48324B4779B003AE06A /* SettingsAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAddAccountView.swift; sourceTree = ""; }; - 65ACE48524B477C9003AE06A /* SettingsAccountLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAccountLabelView.swift; sourceTree = ""; }; - 65ACE48724B48020003AE06A /* SettingsLocalAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLocalAccountView.swift; sourceTree = ""; }; - 65C2E40024B05D8A000AFDF6 /* FeedsSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedsSettingsModel.swift; sourceTree = ""; }; - 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteContainerView.swift; sourceTree = ""; }; 65ED4083235DEF6C0081F399 /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED409D235DEF770081F399 /* Subscribe to Feed MAS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed MAS.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 65ED409F235DEFF00081F399 /* container-migration.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "container-migration.plist"; sourceTree = ""; }; @@ -2168,15 +1593,8 @@ D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebFeed+Scriptability.swift"; sourceTree = ""; }; D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = ""; }; DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = ""; }; - DF98E2992578A73A00F18944 /* AddCloudKitAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCloudKitAccountView.swift; sourceTree = ""; }; - DF98E2AF2578AA5C00F18944 /* AddFeedWranglerAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedWranglerAccountView.swift; sourceTree = ""; }; - DF98E2BD2578AC0000F18944 /* AddNewsBlurAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddNewsBlurAccountView.swift; sourceTree = ""; }; - DF98E2C52578AD1B00F18944 /* AddReaderAPIAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddReaderAPIAccountView.swift; sourceTree = ""; }; - FA80C11624B0728000974098 /* AddFolderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFolderView.swift; sourceTree = ""; }; - FA80C13D24B072AA00974098 /* AddFolderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFolderModel.swift; sourceTree = ""; }; FF3ABF09232599450074C542 /* ArticleSorterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorterTests.swift; sourceTree = ""; }; FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorter.swift; sourceTree = ""; }; - FF64D0C424AF53EE0084080A /* RefreshProgressModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshProgressModel.swift; sourceTree = ""; }; FFD43E372340F320009E5CA3 /* MarkAsReadAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkAsReadAlertController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -2227,47 +1645,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 51C0513A24A77DF800194D5E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 17A1598224E3DEDD005DA32A /* RSWeb in Frameworks */, - 17A1597F24E3DEDD005DA32A /* RSTree in Frameworks */, - 17A1598824E3DEDD005DA32A /* RSParser in Frameworks */, - 17A1597C24E3DEDD005DA32A /* RSCore in Frameworks */, - 516B695D24D2F28E00B5702F /* Account in Frameworks */, - 17A1598524E3DEDD005DA32A /* RSDatabase in Frameworks */, - 27B86EEC25A53AAB00264340 /* Articles in Frameworks */, - 27B86EEE25A53AAB00264340 /* Secrets in Frameworks */, - 51E4989724A8065700B667CB /* CloudKit.framework in Frameworks */, - 27B86EED25A53AAB00264340 /* ArticlesDatabase in Frameworks */, - 27B86EEF25A53AAB00264340 /* SyncDatabase in Frameworks */, - 51E4989924A8067000B667CB /* WebKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 51C0514124A77DF800194D5E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 51E0614E25A5A28E00194066 /* SyncDatabase in Frameworks */, - 51E0614825A5A28E00194066 /* ArticlesDatabase in Frameworks */, - 17386BA42577C6240014C8B2 /* RSParser in Frameworks */, - 17386B952577C6240014C8B2 /* RSCore in Frameworks */, - 51E0614B25A5A28E00194066 /* Secrets in Frameworks */, - 17386B6C2577BD820014C8B2 /* RSSparkle in Frameworks */, - 51E0615125A5A29600194066 /* CrashReporter in Frameworks */, - 516B695B24D2F28600B5702F /* Account in Frameworks */, - 17386B9B2577C6240014C8B2 /* RSWeb in Frameworks */, - 17386B9E2577C6240014C8B2 /* RSDatabase in Frameworks */, - 17386BB62577C7340014C8B2 /* RSCoreResources in Frameworks */, - 51E0614525A5A28E00194066 /* Articles in Frameworks */, - 51E498B124A806A400B667CB /* CloudKit.framework in Frameworks */, - 51E498B324A806AA00B667CB /* WebKit.framework in Frameworks */, - 17386B982577C6240014C8B2 /* RSTree in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 6538134C2680E2DA007A082C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2364,83 +1741,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 171BCBB124CBD569006E22D9 /* Account Management */ = { - isa = PBXGroup; - children = ( - 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */, - ); - path = "Account Management"; - sourceTree = ""; - }; - 172199EB24AB228E00A31D04 /* Settings */ = { - isa = PBXGroup; - children = ( - 65C2E40024B05D8A000AFDF6 /* FeedsSettingsModel.swift */, - 65CBAD5924AE03C20006DD91 /* ColorPaletteContainerView.swift */, - 5181C66124B0C326002E0F70 /* SettingsModel.swift */, - 172199C824AB228900A31D04 /* SettingsView.swift */, - 17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */, - 65ACE46124B47770003AE06A /* Accounts */, - 5177475724B399B500EB0F74 /* About */, - ); - path = Settings; - sourceTree = ""; - }; - 17241268257BBE7B00ACCEBC /* Add Account Models */ = { - isa = PBXGroup; - children = ( - 17241269257BBEBB00ACCEBC /* AddFeedbinViewModel.swift */, - 17241277257BBEE700ACCEBC /* AddFeedlyViewModel.swift */, - 1724127F257BBF3E00ACCEBC /* AddFeedWranglerViewModel.swift */, - 17241287257BBF7000ACCEBC /* AddNewsBlurViewModel.swift */, - 1724128F257BBFAD00ACCEBC /* AddReaderAPIViewModel.swift */, - 17D3CEE2257C4D2300E74939 /* AddAccountSignUp.swift */, - ); - path = "Add Account Models"; - sourceTree = ""; - }; - 1727B37624C1365300A4DBDC /* Viewing */ = { - isa = PBXGroup; - children = ( - 1727B39824C1368D00A4DBDC /* LayoutPreferencesView.swift */, - ); - path = Viewing; - sourceTree = ""; - }; - 1729528F24AA1A4F00D65E66 /* Preferences */ = { - isa = PBXGroup; - children = ( - 1729529624AA1CD000D65E66 /* MacPreferencePanes.swift */, - 1729529924AA1CE100D65E66 /* Preference Panes */, - ); - path = Preferences; - sourceTree = ""; - }; - 1729529924AA1CE100D65E66 /* Preference Panes */ = { - isa = PBXGroup; - children = ( - 1769E2FD24BC589E000E1E8E /* General */, - 1769E31F24BC58A4000E1E8E /* Accounts */, - 1727B37624C1365300A4DBDC /* Viewing */, - 1769E32024BC58AD000E1E8E /* Advanced */, - ); - path = "Preference Panes"; - sourceTree = ""; - }; - 17386B812577C4C60014C8B2 /* Add Account Sheets */ = { - isa = PBXGroup; - children = ( - 17386B792577C4BF0014C8B2 /* AddLocalAccountView.swift */, - DF98E2992578A73A00F18944 /* AddCloudKitAccountView.swift */, - 17386BC32577CC600014C8B2 /* AddFeedbinAccountView.swift */, - 17241248257B8A8A00ACCEBC /* AddFeedlyAccountView.swift */, - DF98E2AF2578AA5C00F18944 /* AddFeedWranglerAccountView.swift */, - DF98E2BD2578AC0000F18944 /* AddNewsBlurAccountView.swift */, - DF98E2C52578AD1B00F18944 /* AddReaderAPIAccountView.swift */, - ); - path = "Add Account Sheets"; - sourceTree = ""; - }; 176813A22564B9D100D98635 /* Widget */ = { isa = PBXGroup; children = ( @@ -2498,86 +1798,6 @@ path = Resources; sourceTree = ""; }; - 1769E2FD24BC589E000E1E8E /* General */ = { - isa = PBXGroup; - children = ( - 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */, - 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */, - ); - path = General; - sourceTree = ""; - }; - 1769E31F24BC58A4000E1E8E /* Accounts */ = { - isa = PBXGroup; - children = ( - 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */, - 1769E33924BD97E5000E1E8E /* Account Preferences */, - ); - path = Accounts; - sourceTree = ""; - }; - 1769E32024BC58AD000E1E8E /* Advanced */ = { - isa = PBXGroup; - children = ( - 17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */, - 1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */, - ); - path = Advanced; - sourceTree = ""; - }; - 1769E32324BC5A50000E1E8E /* Add Account */ = { - isa = PBXGroup; - children = ( - 1769E32424BC5A65000E1E8E /* AddAccountView.swift */, - ); - path = "Add Account"; - sourceTree = ""; - }; - 1769E32E24BD5F22000E1E8E /* Edit Account */ = { - isa = PBXGroup; - children = ( - 1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */, - 1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */, - 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */, - ); - path = "Edit Account"; - sourceTree = ""; - }; - 1769E33924BD97E5000E1E8E /* Account Preferences */ = { - isa = PBXGroup; - children = ( - 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */, - 1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */, - 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */, - 1769E32324BC5A50000E1E8E /* Add Account */, - 1769E32E24BD5F22000E1E8E /* Edit Account */, - ); - path = "Account Preferences"; - sourceTree = ""; - }; - 17897AA724C281520014BA03 /* Inspector */ = { - isa = PBXGroup; - children = ( - 17897AC924C281A40014BA03 /* InspectorView.swift */, - 1799E6A824C2F93F00511E91 /* InspectorPlatformModifier.swift */, - 1799E6CC24C320D600511E91 /* InspectorModel.swift */, - ); - path = Inspector; - sourceTree = ""; - }; - 17930ED224AF10CD00A9BA52 /* Add */ = { - isa = PBXGroup; - children = ( - 17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */, - 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */, - FA80C13D24B072AA00974098 /* AddFolderModel.swift */, - FA80C11624B0728000974098 /* AddFolderView.swift */, - 17241268257BBE7B00ACCEBC /* Add Account Models */, - 17386B812577C4C60014C8B2 /* Add Account Sheets */, - ); - path = Add; - sourceTree = ""; - }; 17D7586B2679C1DF00B17787 /* 1Password */ = { isa = PBXGroup; children = ( @@ -2725,24 +1945,6 @@ path = OPML; sourceTree = ""; }; - 514E6BFD24AD252400AC6F6E /* Previews */ = { - isa = PBXGroup; - children = ( - 514E6BFE24AD255D00AC6F6E /* PreviewArticles.swift */, - ); - path = Previews; - sourceTree = ""; - }; - 514E6C0424AD2B0400AC6F6E /* SwiftUI Extensions */ = { - isa = PBXGroup; - children = ( - 514E6C0524AD2B5F00AC6F6E /* Image-Extensions.swift */, - 5181C5AC24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift */, - 5194736D24BBB937001A2939 /* HiddenModifier.swift */, - ); - path = "SwiftUI Extensions"; - sourceTree = ""; - }; 516A093E236123A800EAE89B /* Account */ = { isa = PBXGroup; children = ( @@ -2769,52 +1971,6 @@ path = Reddit; sourceTree = ""; }; - 5177470B24B2FF2C00EB0F74 /* Article */ = { - isa = PBXGroup; - children = ( - 51B80EB724BD1F8B00C6C32D /* ActivityViewController.swift */, - 51B80EDC24BD296700C6C32D /* ArticleActivityItemSource.swift */, - 5177470D24B2FF6F00EB0F74 /* ArticleView.swift */, - 5177470F24B3029400EB0F74 /* ArticleViewController.swift */, - 51B80EE024BD3E9600C6C32D /* FindInArticleActivity.swift */, - 5177471724B3812200EB0F74 /* IconView.swift */, - 5177471B24B387AC00EB0F74 /* ImageScrollView.swift */, - 5177471D24B387E100EB0F74 /* ImageTransition.swift */, - 5177471F24B3882600EB0F74 /* ImageViewController.swift */, - 51B80EDA24BD225200C6C32D /* OpenInSafariActivity.swift */, - 51B80EDE24BD298900C6C32D /* TitleActivityItemSource.swift */, - 5177471124B37C5400EB0F74 /* WebViewController.swift */, - ); - path = Article; - sourceTree = ""; - }; - 5177470C24B2FF3B00EB0F74 /* Article */ = { - isa = PBXGroup; - children = ( - 51B54ABB24B5BEF20014348B /* ArticleView.swift */, - 51B54A6824B54A490014348B /* IconView.swift */, - 51B80F4324BE58BF00C6C32D /* SharingServiceDelegate.swift */, - 51B80F4124BE588200C6C32D /* SharingServicePickerDelegate.swift */, - 51B80F1E24BE531200C6C32D /* SharingServiceView.swift */, - 51B54B6624B6A7960014348B /* WebStatusBarView.swift */, - 51B54AB524B5B33C0014348B /* WebViewController.swift */, - ); - path = Article; - sourceTree = ""; - }; - 5177475724B399B500EB0F74 /* About */ = { - isa = PBXGroup; - children = ( - 5177475B24B39AD500EB0F74 /* About.rtf */, - 5177475824B39AD400EB0F74 /* Credits.rtf */, - 5177475924B39AD400EB0F74 /* Dedication.rtf */, - 5177475A24B39AD500EB0F74 /* Thanks.rtf */, - 5177476624B3BE3400EB0F74 /* SettingsAboutModel.swift */, - 5177476024B3BC4700EB0F74 /* SettingsAboutView.swift */, - ); - path = About; - sourceTree = ""; - }; 5183CCEA226F70350010922C /* Timer */ = { isa = PBXGroup; children = ( @@ -2857,34 +2013,6 @@ path = NNW3; sourceTree = ""; }; - 51919FB124AAB95300541E64 /* Images */ = { - isa = PBXGroup; - children = ( - 514E6C0824AD39AD00AC6F6E /* ArticleIconImageLoader.swift */, - 51919FB224AAB97900541E64 /* FeedIconImageLoader.swift */, - 51919FB524AABCA100541E64 /* IconImageView.swift */, - ); - path = Images; - sourceTree = ""; - }; - 51919FCB24AB855000541E64 /* Timeline */ = { - isa = PBXGroup; - children = ( - 51919FED24AB85E400541E64 /* TimelineContainerView.swift */, - 51B8BCE524C25F7C00360B00 /* TimelineContextMenu.swift */, - 51919FF324AB869C00541E64 /* TimelineItem.swift */, - 51C65AFB24CCB2C9008EB3BD /* TimelineItems.swift */, - 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */, - 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */, - 51919FF024AB864A00541E64 /* TimelineModel.swift */, - 5194737024BBCAF4001A2939 /* TimelineSortOrderView.swift */, - 51B8104424C0E6D200C6C32D /* TimelineTextSizer.swift */, - 5177470224B2657F00EB0F74 /* TimelineToolbarModifier.swift */, - 51919FF624AB8B7700541E64 /* TimelineView.swift */, - ); - path = Timeline; - sourceTree = ""; - }; 51934CCC231078DC006127BE /* Activity */ = { isa = PBXGroup; children = ( @@ -2894,35 +2022,6 @@ path = Activity; sourceTree = ""; }; - 51A576B924AE617B00078888 /* Article */ = { - isa = PBXGroup; - children = ( - 517B2EDF24B3E8FE001AC46C /* blank.html */, - 517B2EDE24B3E8FE001AC46C /* page.html */, - 517B2EE124B3E8FE001AC46C /* main_multiplatform.js */, - 51A5769524AE617200078888 /* ArticleContainerView.swift */, - 5177472124B38CAE00EB0F74 /* ArticleExtractorButtonState.swift */, - 5177471524B37D9700EB0F74 /* ArticleIconSchemeHandler.swift */, - 5177470524B2910300EB0F74 /* ArticleToolbarModifier.swift */, - 5177471324B37D4000EB0F74 /* PreloadedWebView.swift */, - 5177471924B3863000EB0F74 /* WebViewProvider.swift */, - 517B2EBB24B3E62A001AC46C /* WrapperScriptMessageHandler.swift */, - ); - path = Article; - sourceTree = ""; - }; - 51A8001024CA0FAE00F41F1D /* CombineExt */ = { - isa = PBXGroup; - children = ( - 51A8001424CA0FEC00F41F1D /* DemandBuffer.swift */, - 51A8001124CA0FC700F41F1D /* Sink.swift */, - 51A8FFEC24CA0CF400F41F1D /* WIthLatestFrom.swift */, - 51A8002C24CC451500F41F1D /* ShareReplay.swift */, - 51A8005024CC453C00F41F1D /* ReplaySubject.swift */, - ); - path = CombineExt; - sourceTree = ""; - }; 51B5C85A23F22A7A00032075 /* ShareExtension */ = { isa = PBXGroup; children = ( @@ -2936,72 +2035,6 @@ path = ShareExtension; sourceTree = ""; }; - 51C0519224A77E3500194D5E /* Multiplatform */ = { - isa = PBXGroup; - children = ( - 51C0519524A77E8B00194D5E /* Shared */, - 51C0519424A77E6D00194D5E /* macOS */, - 51C0519324A77E6600194D5E /* iOS */, - ); - path = Multiplatform; - sourceTree = ""; - }; - 51C0519324A77E6600194D5E /* iOS */ = { - isa = PBXGroup; - children = ( - 51C051CF24A7A72100194D5E /* iOS-dev.entitlements */, - 51C051CE24A7A72100194D5E /* iOS.entitlements */, - 51C0513F24A77DF800194D5E /* Info.plist */, - 51E4993B24A8709900B667CB /* AppDelegate.swift */, - 172199EC24AB2E0100A31D04 /* SafariView.swift */, - 5177476424B3BDAE00EB0F74 /* AttributedStringView.swift */, - 5177470B24B2FF2C00EB0F74 /* Article */, - 172199EB24AB228E00A31D04 /* Settings */, - ); - path = iOS; - sourceTree = ""; - }; - 51C0519424A77E6D00194D5E /* macOS */ = { - isa = PBXGroup; - children = ( - 51C051CD24A7A6DB00194D5E /* macOS-dev.entitlements */, - 51C0514724A77DF800194D5E /* macOS.entitlements */, - 51C0514624A77DF800194D5E /* Info.plist */, - 51E4993924A8708800B667CB /* AppDelegate.swift */, - 51B80F4524BF76E700C6C32D /* Browser.swift */, - 1729529A24AA1FD200D65E66 /* MacSearchField.swift */, - 5177470C24B2FF3B00EB0F74 /* Article */, - 1729528F24AA1A4F00D65E66 /* Preferences */, - ); - path = macOS; - sourceTree = ""; - }; - 51C0519524A77E8B00194D5E /* Shared */ = { - isa = PBXGroup; - children = ( - 51E4992524A80AAB00B667CB /* AppAssets.swift */, - 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */, - 51E4995824A873F900B667CB /* ErrorHandler.swift */, - 51C0513624A77DF700194D5E /* MainApp.swift */, - FF64D0C424AF53EE0084080A /* RefreshProgressModel.swift */, - 51E499D724A912C200B667CB /* SceneModel.swift */, - 51E49A0224A91FF600B667CB /* SceneNavigationView.swift */, - 1704053324E5985A00A00787 /* SceneNavigationModel.swift */, - 51C0513824A77DF800194D5E /* Assets.xcassets */, - 171BCBB124CBD569006E22D9 /* Account Management */, - 17930ED224AF10CD00A9BA52 /* Add */, - 51A576B924AE617B00078888 /* Article */, - 51A8001024CA0FAE00F41F1D /* CombineExt */, - 51919FB124AAB95300541E64 /* Images */, - 17897AA724C281520014BA03 /* Inspector */, - 514E6BFD24AD252400AC6F6E /* Previews */, - 51E499FB24A9135A00B667CB /* Sidebar */, - 514E6C0424AD2B0400AC6F6E /* SwiftUI Extensions */, - 51919FCB24AB855000541E64 /* Timeline */, - ); - path = Shared; - sourceTree = ""; - }; 51C45245226506C800C03939 /* UIKit Extensions */ = { isa = PBXGroup; children = ( @@ -3170,24 +2203,6 @@ name = Frameworks; sourceTree = ""; }; - 51E499FB24A9135A00B667CB /* Sidebar */ = { - isa = PBXGroup; - children = ( - 51E499FF24A91FC100B667CB /* SidebarContainerView.swift */, - 51B8BCC124C25C3E00360B00 /* SidebarContextMenu.swift */, - 51392D1A24AC19A000BE0D35 /* SidebarExpandedContainers.swift */, - 51408B7D24A9EC6F0073CF4E /* SidebarItem.swift */, - 51919FAE24AA8EFA00541E64 /* SidebarItemView.swift */, - 5177470824B2F87600EB0F74 /* SidebarListStyleModifier.swift */, - 51E499FC24A9137600B667CB /* SidebarModel.swift */, - 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */, - 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */, - 51919FA524AA64B000541E64 /* SidebarView.swift */, - 51919FAB24AA8CCA00541E64 /* UnreadCountView.swift */, - ); - path = Sidebar; - sourceTree = ""; - }; 51FA739A2332BDE70090D516 /* Article Extractor */ = { isa = PBXGroup; children = ( @@ -3219,24 +2234,6 @@ path = SafariExtension; sourceTree = ""; }; - 65ACE46124B47770003AE06A /* Accounts */ = { - isa = PBXGroup; - children = ( - 65ACE48324B4779B003AE06A /* SettingsAddAccountView.swift */, - 65422D1624B75CD1008A2FA2 /* SettingsAddAccountModel.swift */, - 65ACE48524B477C9003AE06A /* SettingsAccountLabelView.swift */, - 65ACE48724B48020003AE06A /* SettingsLocalAccountView.swift */, - 6591723024B5C35400B638E8 /* AccountHeaderImageView.swift */, - 6591727E24B5D19500B638E8 /* SettingsDetailAccountView.swift */, - 6586A5F624B632F8002BCF4F /* SettingsDetailAccountModel.swift */, - 653A4E7824BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift */, - 65082A2E24C72AC8009FA994 /* SettingsCredentialsAccountView.swift */, - 65082A5124C72B88009FA994 /* SettingsCredentialsAccountModel.swift */, - 65082A5324C73D2F009FA994 /* AccountCredentialsError.swift */, - ); - path = Accounts; - sourceTree = ""; - }; 840D61942029031D009BC708 /* NetNewsWire-iOSTests */ = { isa = PBXGroup; children = ( @@ -3496,7 +2493,6 @@ 84CBDDAE1FD3674C005A61AA /* Technotes */, 84C9FC6522629B3900D921D6 /* Mac */, 84C9FC922262A0E600D921D6 /* iOS */, - 51C0519224A77E3500194D5E /* Multiplatform */, 176813F82564BB2C00D98635 /* Widget */, 84C9FC6822629C9A00D921D6 /* Shared */, 84C9FCA52262A1E600D921D6 /* Tests */, @@ -3524,8 +2520,6 @@ 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */, 65ED4083235DEF6C0081F399 /* NetNewsWire.app */, 65ED409D235DEF770081F399 /* Subscribe to Feed MAS.appex */, - 51C0513D24A77DF800194D5E /* NetNewsWire.app */, - 51C0514424A77DF800194D5E /* NetNewsWire.app */, 510C415C24E5CDE3008226FD /* NetNewsWire Share Extension.appex */, 176813F32564BB2C00D98635 /* NetNewsWire iOS Widget Extension.appex */, 653813592680E2DA007A082C /* NetNewsWire Share Extension MAS.appex */, @@ -3846,8 +2840,6 @@ 518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */, 65ED40F2235DF5E00081F399 /* NetNewsWire_macapp_target_macappstore.xcconfig */, D5907CE02002F0FA005947E5 /* NetNewsWire_macapp_target.xcconfig */, - 51C0519724A7808F00194D5E /* NetNewsWire_multiplatform_iOSapp_target.xcconfig */, - 51C0519824A7808F00194D5E /* NetNewsWire_multiplatform_macOSapp_target.xcconfig */, D5907CDD2002F0BE005947E5 /* NetNewsWire_project_debug.xcconfig */, D5907CDC2002F0BE005947E5 /* NetNewsWire_project_release.xcconfig */, D5907CDE2002F0BE005947E5 /* NetNewsWire_project.xcconfig */, @@ -3992,70 +2984,6 @@ productReference = 518B2ED22351B3DD00400001 /* NetNewsWire-iOSTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 51C0513C24A77DF800194D5E /* Multiplatform iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 51C0518D24A77DF800194D5E /* Build configuration list for PBXNativeTarget "Multiplatform iOS" */; - buildPhases = ( - 51C0513924A77DF800194D5E /* Sources */, - 51C0513A24A77DF800194D5E /* Frameworks */, - 51C0513B24A77DF800194D5E /* Resources */, - 51E4989524A8061400B667CB /* Embed Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "Multiplatform iOS"; - packageProductDependencies = ( - 516B695C24D2F28E00B5702F /* Account */, - 17A1597B24E3DEDD005DA32A /* RSCore */, - 17A1597E24E3DEDD005DA32A /* RSTree */, - 17A1598124E3DEDD005DA32A /* RSWeb */, - 17A1598424E3DEDD005DA32A /* RSDatabase */, - 17A1598724E3DEDD005DA32A /* RSParser */, - 17E0080E25936DF6000C23F0 /* Articles */, - 17E0081125936DF6000C23F0 /* ArticlesDatabase */, - 17E0081425936DFF000C23F0 /* Secrets */, - 17E0081725936DFF000C23F0 /* SyncDatabase */, - ); - productName = iOS; - productReference = 51C0513D24A77DF800194D5E /* NetNewsWire.app */; - productType = "com.apple.product-type.application"; - }; - 51C0514324A77DF800194D5E /* Multiplatform macOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 51C0518E24A77DF800194D5E /* Build configuration list for PBXNativeTarget "Multiplatform macOS" */; - buildPhases = ( - 51C0514024A77DF800194D5E /* Sources */, - 51C0514124A77DF800194D5E /* Frameworks */, - 51C0514224A77DF800194D5E /* Resources */, - 51E498B024A8069300B667CB /* Embed Frameworks */, - 51E4994924A872AD00B667CB /* Embed XPC Services */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "Multiplatform macOS"; - packageProductDependencies = ( - 516B695A24D2F28600B5702F /* Account */, - 17386B6B2577BD820014C8B2 /* RSSparkle */, - 17386B942577C6240014C8B2 /* RSCore */, - 17386B972577C6240014C8B2 /* RSTree */, - 17386B9A2577C6240014C8B2 /* RSWeb */, - 17386B9D2577C6240014C8B2 /* RSDatabase */, - 17386BA32577C6240014C8B2 /* RSParser */, - 17386BB52577C7340014C8B2 /* RSCoreResources */, - 51E0614425A5A28E00194066 /* Articles */, - 51E0614725A5A28E00194066 /* ArticlesDatabase */, - 51E0614A25A5A28E00194066 /* Secrets */, - 51E0614D25A5A28E00194066 /* SyncDatabase */, - 51E0615025A5A29600194066 /* CrashReporter */, - ); - productName = macOS; - productReference = 51C0514424A77DF800194D5E /* NetNewsWire.app */; - productType = "com.apple.product-type.application"; - }; 6538133E2680E2DA007A082C /* NetNewsWire Share Extension MAS */ = { isa = PBXNativeTarget; buildConfigurationList = 653813562680E2DA007A082C /* Build configuration list for PBXNativeTarget "NetNewsWire Share Extension MAS" */; @@ -4285,16 +3213,6 @@ ProvisioningStyle = Automatic; TestTargetID = 840D617B2029031C009BC708; }; - 51C0513C24A77DF800194D5E = { - CreatedOnToolsVersion = 12.0; - DevelopmentTeam = SHJK2V3AJG; - ProvisioningStyle = Automatic; - }; - 51C0514324A77DF800194D5E = { - CreatedOnToolsVersion = 12.0; - DevelopmentTeam = SHJK2V3AJG; - ProvisioningStyle = Automatic; - }; 6581C73220CED60000F4AD34 = { DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; @@ -4370,8 +3288,6 @@ 51314636235A7BBE00387FDC /* NetNewsWire iOS Intents Extension */, 176813F22564BB2C00D98635 /* NetNewsWire iOS Widget Extension */, 518B2ED12351B3DD00400001 /* NetNewsWire-iOSTests */, - 51C0513C24A77DF800194D5E /* Multiplatform iOS */, - 51C0514324A77DF800194D5E /* Multiplatform macOS */, 510C415B24E5CDE3008226FD /* NetNewsWire Share Extension */, 6538133E2680E2DA007A082C /* NetNewsWire Share Extension MAS */, ); @@ -4426,50 +3342,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 51C0513B24A77DF800194D5E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 5177475E24B39AD500EB0F74 /* Thanks.rtf in Resources */, - 517B2EE224B3E8FE001AC46C /* page.html in Resources */, - 51E4995F24A875F300B667CB /* stylesheet.css in Resources */, - 517B2EE824B3E8FE001AC46C /* main_multiplatform.js in Resources */, - 51DEE81B26FBFF84006DAA56 /* Promenade.nnwtheme in Resources */, - 51E4997324A8784300B667CB /* DefaultFeeds.opml in Resources */, - 5137C2E726F3F52D009EFEDB /* Sepia.nnwtheme in Resources */, - 51C0516224A77DF800194D5E /* Assets.xcassets in Resources */, - 5177475F24B39AD500EB0F74 /* About.rtf in Resources */, - 51E4996024A875F300B667CB /* template.html in Resources */, - 5177475D24B39AD500EB0F74 /* Dedication.rtf in Resources */, - 51E4995E24A875F300B667CB /* newsfoot.js in Resources */, - 517B2EE424B3E8FE001AC46C /* blank.html in Resources */, - 51DEE81526FB9233006DAA56 /* Appanoose.nnwtheme in Resources */, - 5177475C24B39AD500EB0F74 /* Credits.rtf in Resources */, - 51D0214926ED617100FF2E0F /* core.css in Resources */, - 51E4995D24A875F300B667CB /* main.js in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 51C0514224A77DF800194D5E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 517B2EE324B3E8FE001AC46C /* page.html in Resources */, - 51E4996424A875F400B667CB /* stylesheet.css in Resources */, - 5137C2E826F3F52D009EFEDB /* Sepia.nnwtheme in Resources */, - 51E4997524A8784400B667CB /* DefaultFeeds.opml in Resources */, - 51DEE81626FB9233006DAA56 /* Appanoose.nnwtheme in Resources */, - 51C0516324A77DF800194D5E /* Assets.xcassets in Resources */, - 51E4996524A875F400B667CB /* template.html in Resources */, - 517B2EE924B3E8FE001AC46C /* main_multiplatform.js in Resources */, - 51D0214A26ED617100FF2E0F /* core.css in Resources */, - 517B2EE524B3E8FE001AC46C /* blank.html in Resources */, - 51DEE81C26FBFF84006DAA56 /* Promenade.nnwtheme in Resources */, - 51E4996324A875F400B667CB /* newsfoot.js in Resources */, - 51E4996224A875F400B667CB /* main.js in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 6538134F2680E2DA007A082C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4959,343 +3831,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 51C0513924A77DF800194D5E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 172412A6257BC01C00ACCEBC /* AddFeedbinViewModel.swift in Sources */, - 17D5F17124B0BC6700375168 /* SidebarToolbarModel.swift in Sources */, - 51E4995924A873F900B667CB /* ErrorHandler.swift in Sources */, - 51392D1B24AC19A000BE0D35 /* SidebarExpandedContainers.swift in Sources */, - 51E4992F24A8676400B667CB /* ArticleArray.swift in Sources */, - 5177471C24B387AC00EB0F74 /* ImageScrollView.swift in Sources */, - 51E498F824A8085D00B667CB /* UnreadFeed.swift in Sources */, - 6591723124B5C35400B638E8 /* AccountHeaderImageView.swift in Sources */, - 51B8104524C0E6D200C6C32D /* TimelineTextSizer.swift in Sources */, - FF64D0E724AF53EE0084080A /* RefreshProgressModel.swift in Sources */, - 51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */, - 51919FF124AB864A00541E64 /* TimelineModel.swift in Sources */, - 5194736E24BBB937001A2939 /* HiddenModifier.swift in Sources */, - 51DC079B2552083500A3F79F /* ArticleTextSize.swift in Sources */, - 51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */, - 51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */, - 51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */, - 1704053424E5985A00A00787 /* SceneNavigationModel.swift in Sources */, - 514E6C0924AD39AD00AC6F6E /* ArticleIconImageLoader.swift in Sources */, - 172412A2257BC01C00ACCEBC /* AddLocalAccountView.swift in Sources */, - 6594CA3B24AF6F2A005C7D7C /* OPMLExporter.swift in Sources */, - FA80C13E24B072AA00974098 /* AddFolderModel.swift in Sources */, - 5177470624B2910300EB0F74 /* ArticleToolbarModifier.swift in Sources */, - 51919FAF24AA8EFA00541E64 /* SidebarItemView.swift in Sources */, - 514E6BDA24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */, - 172412A1257BC01C00ACCEBC /* AddReaderAPIAccountView.swift in Sources */, - 5177471624B37D9700EB0F74 /* ArticleIconSchemeHandler.swift in Sources */, - FA80C11724B0728000974098 /* AddFolderView.swift in Sources */, - 51E4990D24A808C500B667CB /* RSHTMLMetadata+Extension.swift in Sources */, - 51919FF424AB869C00541E64 /* TimelineItem.swift in Sources */, - 514E6C0224AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */, - 51C65AFC24CCB2C9008EB3BD /* TimelineItems.swift in Sources */, - 51E49A0024A91FC100B667CB /* SidebarContainerView.swift in Sources */, - 5177471824B3812200EB0F74 /* IconView.swift in Sources */, - 51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */, - 51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */, - 51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */, - 171BCB8C24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */, - 51E4990924A808C500B667CB /* WebFeedIconDownloader.swift in Sources */, - 51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, - 171BCBAF24CBBFD8006E22D9 /* EditAccountCredentialsModel.swift in Sources */, - 51B80EDB24BD225200C6C32D /* OpenInSafariActivity.swift in Sources */, - 172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */, - 65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */, - 5177470924B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */, - 65082A5424C73D2F009FA994 /* AccountCredentialsError.swift in Sources */, - 51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */, - 51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, - 51A8001524CA0FEC00F41F1D /* DemandBuffer.swift in Sources */, - 514E6BFF24AD255D00AC6F6E /* PreviewArticles.swift in Sources */, - 51E4993024A8676400B667CB /* ArticleSorter.swift in Sources */, - 51408B7E24A9EC6F0073CF4E /* SidebarItem.swift in Sources */, - 5177476724B3BE3400EB0F74 /* SettingsAboutModel.swift in Sources */, - 65C2E40124B05D8A000AFDF6 /* FeedsSettingsModel.swift in Sources */, - 51E4990A24A808C500B667CB /* FeaturedImageDownloader.swift in Sources */, - 51E4993224A8676400B667CB /* FetchRequestQueue.swift in Sources */, - 51E4991724A8090400B667CB /* ArticleUtilities.swift in Sources */, - 51E4991B24A8091000B667CB /* IconImage.swift in Sources */, - 51E4995424A8734D00B667CB /* ExtensionPointIdentifer.swift in Sources */, - 51E4996924A8760C00B667CB /* ArticleThemesManager.swift in Sources */, - 5177471E24B387E100EB0F74 /* ImageTransition.swift in Sources */, - 51E498F324A8085D00B667CB /* PseudoFeed.swift in Sources */, - 172412A5257BC01C00ACCEBC /* AddCloudKitAccountView.swift in Sources */, - 65ACE48424B4779B003AE06A /* SettingsAddAccountView.swift in Sources */, - 51A5769624AE617200078888 /* ArticleContainerView.swift in Sources */, - 5181C5AD24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */, - 51E4996B24A8762D00B667CB /* ArticleExtractor.swift in Sources */, - 51E49A0324A91FF600B667CB /* SceneNavigationView.swift in Sources */, - 51E4990124A808BB00B667CB /* FaviconURLFinder.swift in Sources */, - 51E4991D24A8092100B667CB /* NSAttributedString+NetNewsWire.swift in Sources */, - 65082A2F24C72AC8009FA994 /* SettingsCredentialsAccountView.swift in Sources */, - 51A8FFED24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */, - 1724129E257BC01C00ACCEBC /* AddReaderAPIViewModel.swift in Sources */, - 51E499FD24A9137600B667CB /* SidebarModel.swift in Sources */, - 5181C66224B0C326002E0F70 /* SettingsModel.swift in Sources */, - 51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, - 5177471024B3029400EB0F74 /* ArticleViewController.swift in Sources */, - 17D3CEE3257C4D2300E74939 /* AddAccountSignUp.swift in Sources */, - 65082A5224C72B88009FA994 /* SettingsCredentialsAccountModel.swift in Sources */, - 172199C924AB228900A31D04 /* SettingsView.swift in Sources */, - 51B8BCC224C25C3E00360B00 /* SidebarContextMenu.swift in Sources */, - 51A8005124CC453C00F41F1D /* ReplaySubject.swift in Sources */, - 172412A7257BC01C00ACCEBC /* AddFeedbinAccountView.swift in Sources */, - 17D232A824AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, - 51B80EB824BD1F8B00C6C32D /* ActivityViewController.swift in Sources */, - 51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */, - 51872A712706581E0078CF1A /* ArticleTheme+Notifications.swift in Sources */, - 51E498F624A8085D00B667CB /* SearchFeedDelegate.swift in Sources */, - 51C4CFF324D37D1F00AF9874 /* Secrets.swift in Sources */, - 51B80EE124BD3E9600C6C32D /* FindInArticleActivity.swift in Sources */, - 6586A5F724B632F8002BCF4F /* SettingsDetailAccountModel.swift in Sources */, - 51E498F224A8085D00B667CB /* SmartFeedsController.swift in Sources */, - 51919FB624AABCA100541E64 /* IconImageView.swift in Sources */, - 51919FA624AA64B000541E64 /* SidebarView.swift in Sources */, - 51E4997024A8764C00B667CB /* ActivityManager.swift in Sources */, - 5177471224B37C5400EB0F74 /* WebViewController.swift in Sources */, - 51E4990F24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */, - 5177476524B3BDAE00EB0F74 /* AttributedStringView.swift in Sources */, - 51E4993124A8676400B667CB /* FetchRequestOperation.swift in Sources */, - 51E4992624A80AAB00B667CB /* AppAssets.swift in Sources */, - 514E6C0624AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */, - 172412AF257BC0C300ACCEBC /* AccountType+Helpers.swift in Sources */, - 51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, - 5125E6CA24AE461D002A7562 /* TimelineLayoutView.swift in Sources */, - 51E4996824A8760C00B667CB /* ArticleTheme.swift in Sources */, - 51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */, - 51E4997124A8764C00B667CB /* ActivityType.swift in Sources */, - 51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, - 51E499D824A912C200B667CB /* SceneModel.swift in Sources */, - 5177470E24B2FF6F00EB0F74 /* ArticleView.swift in Sources */, - 5171B4F624B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */, - 65422D1724B75CD1008A2FA2 /* SettingsAddAccountModel.swift in Sources */, - 172412A3257BC01C00ACCEBC /* AddNewsBlurAccountView.swift in Sources */, - 5177471424B37D4000EB0F74 /* PreloadedWebView.swift in Sources */, - 51B80EDD24BD296700C6C32D /* ArticleActivityItemSource.swift in Sources */, - 1724129D257BC01C00ACCEBC /* AddNewsBlurViewModel.swift in Sources */, - 17897ACA24C281A40014BA03 /* InspectorView.swift in Sources */, - 517B2EBC24B3E62A001AC46C /* WrapperScriptMessageHandler.swift in Sources */, - 51919FB324AAB97900541E64 /* FeedIconImageLoader.swift in Sources */, - 5177472024B3882600EB0F74 /* ImageViewController.swift in Sources */, - 51919FB324AAB97900541E64 /* FeedIconImageLoader.swift in Sources */, - 51E4991324A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */, - 51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */, - 6591727F24B5D19500B638E8 /* SettingsDetailAccountView.swift in Sources */, - 51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */, - 65ACE48824B48020003AE06A /* SettingsLocalAccountView.swift in Sources */, - 171BCBB024CBBFFD006E22D9 /* AccountUpdateErrors.swift in Sources */, - 17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */, - 51E4995124A8734D00B667CB /* ExtensionPointManager.swift in Sources */, - 51E4990C24A808C500B667CB /* AuthorAvatarDownloader.swift in Sources */, - 1799E6A924C2F93F00511E91 /* InspectorPlatformModifier.swift in Sources */, - 5177472224B38CAE00EB0F74 /* ArticleExtractorButtonState.swift in Sources */, - 5177471A24B3863000EB0F74 /* WebViewProvider.swift in Sources */, - 51E4992124A8095000B667CB /* RSImage-Extensions.swift in Sources */, - 51A8001224CA0FC700F41F1D /* Sink.swift in Sources */, - 172412A8257BC01C00ACCEBC /* AddFeedWranglerViewModel.swift in Sources */, - 51E4990324A808BB00B667CB /* FaviconDownloader.swift in Sources */, - 172199ED24AB2E0100A31D04 /* SafariView.swift in Sources */, - 65ACE48624B477C9003AE06A /* SettingsAccountLabelView.swift in Sources */, - 51E4990224A808BB00B667CB /* ColorHash.swift in Sources */, - 51919FAC24AA8CCA00541E64 /* UnreadCountView.swift in Sources */, - 5177476224B3BC4700EB0F74 /* SettingsAboutView.swift in Sources */, - 1799E6CD24C320D600511E91 /* InspectorModel.swift in Sources */, - 51E4991924A8090A00B667CB /* CacheCleaner.swift in Sources */, - 51E498F724A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */, - 175942AA24AD533200585066 /* RefreshInterval.swift in Sources */, - 179D280E26F73D83003B2E0A /* ArticleThemePlist.swift in Sources */, - 51E4993524A867E800B667CB /* AppNotifications.swift in Sources */, - 6535ECFC2680F9FF00C01CB5 /* IconImageCache.swift in Sources */, - 51C0515E24A77DF800194D5E /* MainApp.swift in Sources */, - 51919FF724AB8B7700541E64 /* TimelineView.swift in Sources */, - 51E4993D24A870F800B667CB /* UserNotificationManager.swift in Sources */, - 172412A0257BC01C00ACCEBC /* AddFeedWranglerAccountView.swift in Sources */, - 5177470324B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */, - 51B80EDF24BD298900C6C32D /* TitleActivityItemSource.swift in Sources */, - 51E4991524A808FF00B667CB /* ArticleStringFormatter.swift in Sources */, - 51919FEE24AB85E400541E64 /* TimelineContainerView.swift in Sources */, - 653A4E7924BCA5BB00EF2D7F /* SettingsCloudKitAccountView.swift in Sources */, - 1724129F257BC01C00ACCEBC /* AddFeedlyAccountView.swift in Sources */, - 51E4995724A8734D00B667CB /* ExtensionPoint.swift in Sources */, - 51A8002D24CC451500F41F1D /* ShareReplay.swift in Sources */, - 51B8BCE624C25F7C00360B00 /* TimelineContextMenu.swift in Sources */, - 172412A4257BC01C00ACCEBC /* AddFeedlyViewModel.swift in Sources */, - 1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */, - 51E4991124A808DE00B667CB /* SmallIconProvider.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 51C0514024A77DF800194D5E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 17930ED524AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */, - 51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */, - 51E498CE24A8085D00B667CB /* UnreadFeed.swift in Sources */, - 6535ECFD2680FA0000C01CB5 /* IconImageCache.swift in Sources */, - 51B8BCC324C25C3E00360B00 /* SidebarContextMenu.swift in Sources */, - 51E498C724A8085D00B667CB /* StarredFeedDelegate.swift in Sources */, - 5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */, - 51A8001624CA0FEC00F41F1D /* DemandBuffer.swift in Sources */, - 51919FB724AABCA100541E64 /* IconImageView.swift in Sources */, - 51B54A6924B54A490014348B /* IconView.swift in Sources */, - 17897ACB24C281A40014BA03 /* InspectorView.swift in Sources */, - 51E498FA24A808BA00B667CB /* SingleFaviconDownloader.swift in Sources */, - 1727B39924C1368D00A4DBDC /* LayoutPreferencesView.swift in Sources */, - 51A8FFEE24CA0CF400F41F1D /* WIthLatestFrom.swift in Sources */, - 51E4993F24A8713B00B667CB /* ArticleStatusSyncTimer.swift in Sources */, - 51B80F4424BE58BF00C6C32D /* SharingServiceDelegate.swift in Sources */, - 51B54AB624B5B33C0014348B /* WebViewController.swift in Sources */, - 51E4994B24A8734C00B667CB /* SendToMicroBlogCommand.swift in Sources */, - 51E4996F24A8764C00B667CB /* ActivityType.swift in Sources */, - 17386BC42577CC600014C8B2 /* AddFeedbinAccountView.swift in Sources */, - 51E4994E24A8734C00B667CB /* SendToMarsEditCommand.swift in Sources */, - 51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */, - 1769E33624BD9621000E1E8E /* EditAccountCredentialsModel.swift in Sources */, - 51919FEF24AB85E400541E64 /* TimelineContainerView.swift in Sources */, - 51E4996624A8760B00B667CB /* ArticleTheme.swift in Sources */, - 5171B4D424B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */, - 17E4DBD624BFC53E00FE462A /* AdvancedPreferencesModel.swift in Sources */, - 5177470724B2910300EB0F74 /* ArticleToolbarModifier.swift in Sources */, - FA80C11824B0728000974098 /* AddFolderView.swift in Sources */, - 17386B7A2577C4BF0014C8B2 /* AddLocalAccountView.swift in Sources */, - 51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */, - 51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */, - 51919FF824AB8B7700541E64 /* TimelineView.swift in Sources */, - 1717535624BADF33004498C6 /* GeneralPreferencesModel.swift in Sources */, - 51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */, - 51B54AB324B5AC830014348B /* ArticleExtractorButtonState.swift in Sources */, - 17D5F17224B0BC6700375168 /* SidebarToolbarModel.swift in Sources */, - 514E6C0724AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */, - 51E4994D24A8734C00B667CB /* ExtensionPointIdentifer.swift in Sources */, - 51C65AFD24CCB2C9008EB3BD /* TimelineItems.swift in Sources */, - DF98E2BE2578AC0000F18944 /* AddNewsBlurAccountView.swift in Sources */, - 51B54A6724B549FE0014348B /* ArticleIconSchemeHandler.swift in Sources */, - 51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */, - 51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */, - 51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, - 51DC079C2552083500A3F79F /* ArticleTextSize.swift in Sources */, - 51B80F1F24BE531200C6C32D /* SharingServiceView.swift in Sources */, - 17D232A924AFF10A0005F075 /* AddWebFeedModel.swift in Sources */, - 51A8005224CC453C00F41F1D /* ReplaySubject.swift in Sources */, - 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */, - 51B80F4224BE588200C6C32D /* SharingServicePickerDelegate.swift in Sources */, - 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */, - 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, - 17241280257BBF3E00ACCEBC /* AddFeedWranglerViewModel.swift in Sources */, - 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, - DF98E2B02578AA5C00F18944 /* AddFeedWranglerAccountView.swift in Sources */, - 5177470A24B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */, - 1769E33824BD97CB000E1E8E /* AccountUpdateErrors.swift in Sources */, - 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */, - 5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */, - 1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */, - 51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */, - 51919FF224AB864A00541E64 /* TimelineModel.swift in Sources */, - 51E4991A24A8090F00B667CB /* IconImage.swift in Sources */, - 1799E6AA24C2F93F00511E91 /* InspectorPlatformModifier.swift in Sources */, - 51B8104624C0E6D200C6C32D /* TimelineTextSizer.swift in Sources */, - 51E4992724A80AAB00B667CB /* AppAssets.swift in Sources */, - 51E49A0124A91FC100B667CB /* SidebarContainerView.swift in Sources */, - 51E4995B24A875D500B667CB /* ArticlePasteboardWriter.swift in Sources */, - 51E4993424A867E700B667CB /* UserInfoKey.swift in Sources */, - 1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */, - 1729529724AA1CD000D65E66 /* MacPreferencePanes.swift in Sources */, - 51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, - 1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */, - 171BCB8D24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */, - 1769E32D24BD20A0000E1E8E /* AccountDetailView.swift in Sources */, - 51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */, - 51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */, - 51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */, - 51E4991C24A8092000B667CB /* NSAttributedString+NetNewsWire.swift in Sources */, - 1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */, - DF98E2C62578AD1B00F18944 /* AddReaderAPIAccountView.swift in Sources */, - FF64D0E824AF53EE0084080A /* RefreshProgressModel.swift in Sources */, - 51E499D924A912C200B667CB /* SceneModel.swift in Sources */, - 51919FB424AAB97900541E64 /* FeedIconImageLoader.swift in Sources */, - 1769E32524BC5A65000E1E8E /* AddAccountView.swift in Sources */, - 179D280F26F73D83003B2E0A /* ArticleThemePlist.swift in Sources */, - 51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */, - 514E6C0324AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */, - 1724126A257BBEBB00ACCEBC /* AddFeedbinViewModel.swift in Sources */, - 51872A722706581F0078CF1A /* ArticleTheme+Notifications.swift in Sources */, - 51B54A6524B549B20014348B /* WrapperScriptMessageHandler.swift in Sources */, - 51E4996D24A8762D00B667CB /* ArticleExtractor.swift in Sources */, - 1704053524E5985A00A00787 /* SceneNavigationModel.swift in Sources */, - 17241278257BBEE700ACCEBC /* AddFeedlyViewModel.swift in Sources */, - 51E4994024A8713B00B667CB /* AccountRefreshTimer.swift in Sources */, - 51E49A0424A91FF600B667CB /* SceneNavigationView.swift in Sources */, - 17241290257BBFAD00ACCEBC /* AddReaderAPIViewModel.swift in Sources */, - 51E498CC24A8085D00B667CB /* SearchFeedDelegate.swift in Sources */, - 51E498C824A8085D00B667CB /* SmartFeedsController.swift in Sources */, - 175942AB24AD533200585066 /* RefreshInterval.swift in Sources */, - 51B80F4624BF76E700C6C32D /* Browser.swift in Sources */, - 51E4992C24A8676300B667CB /* ArticleSorter.swift in Sources */, - 514E6C0A24AD39AD00AC6F6E /* ArticleIconImageLoader.swift in Sources */, - 51E4995024A8734C00B667CB /* ExtensionPoint.swift in Sources */, - 51E4990E24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */, - DF98E29A2578A73A00F18944 /* AddCloudKitAccountView.swift in Sources */, - 51E498FB24A808BA00B667CB /* FaviconGenerator.swift in Sources */, - 17D5F19524B0C1DD00375168 /* SidebarToolbarModifier.swift in Sources */, - 17241288257BBF7000ACCEBC /* AddNewsBlurViewModel.swift in Sources */, - 51E4996724A8760B00B667CB /* ArticleThemesManager.swift in Sources */, - 1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */, - 51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */, - 514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */, - 51B8BCE724C25F7C00360B00 /* TimelineContextMenu.swift in Sources */, - 51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */, - 51A8002E24CC451600F41F1D /* ShareReplay.swift in Sources */, - 1769E33024BD6271000E1E8E /* EditAccountCredentialsView.swift in Sources */, - 51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */, - 5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */, - 51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, - 51A5769724AE617200078888 /* ArticleContainerView.swift in Sources */, - 51E4991224A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */, - 51B54B6724B6A7960014348B /* WebStatusBarView.swift in Sources */, - 51E4993E24A870F900B667CB /* UserNotificationManager.swift in Sources */, - 51E4992E24A8676300B667CB /* FetchRequestQueue.swift in Sources */, - 17386B5E2577BC820014C8B2 /* AccountType+Helpers.swift in Sources */, - 51E498CF24A8085D00B667CB /* SmartFeed.swift in Sources */, - 51E4990724A808C300B667CB /* AuthorAvatarDownloader.swift in Sources */, - 51E4997424A8784400B667CB /* DefaultFeedsImporter.swift in Sources */, - 51919FF524AB869C00541E64 /* TimelineItem.swift in Sources */, - 51E4992024A8095000B667CB /* RSImage-Extensions.swift in Sources */, - 51E499FE24A9137600B667CB /* SidebarModel.swift in Sources */, - 51E498FE24A808BA00B667CB /* FaviconDownloader.swift in Sources */, - 51919FA724AA64B000541E64 /* SidebarView.swift in Sources */, - 51E498FD24A808BA00B667CB /* ColorHash.swift in Sources */, - 51E4991824A8090A00B667CB /* CacheCleaner.swift in Sources */, - 51B54ABC24B5BEF20014348B /* ArticleView.swift in Sources */, - 51E498CD24A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */, - 51E4996124A875F400B667CB /* ArticleRenderer.swift in Sources */, - FA80C13F24B072AB00974098 /* AddFolderModel.swift in Sources */, - 51392D1C24AC19A000BE0D35 /* SidebarExpandedContainers.swift in Sources */, - 51C0515F24A77DF800194D5E /* MainApp.swift in Sources */, - 51B54A4324B5499B0014348B /* WebViewProvider.swift in Sources */, - 1799E6CE24C320D600511E91 /* InspectorModel.swift in Sources */, - 514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */, - 51C4CFF424D37D1F00AF9874 /* Secrets.swift in Sources */, - 1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */, - 1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */, - 5177470424B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */, - 51A8001324CA0FC700F41F1D /* Sink.swift in Sources */, - 17241249257B8A8A00ACCEBC /* AddFeedlyAccountView.swift in Sources */, - 51E4992D24A8676300B667CB /* FetchRequestOperation.swift in Sources */, - 51E4992424A8098400B667CB /* SmartFeedPasteboardWriter.swift in Sources */, - 17D3CEE4257C4D2300E74939 /* AddAccountSignUp.swift in Sources */, - 51E4991424A808FF00B667CB /* ArticleStringFormatter.swift in Sources */, - 51B54A6624B549CB0014348B /* PreloadedWebView.swift in Sources */, - 51E4991024A808DE00B667CB /* SmallIconProvider.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 653813422680E2DA007A082C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -6167,34 +4702,6 @@ }; name = Release; }; - 51C0516424A77DF800194D5E /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51C0519724A7808F00194D5E /* NetNewsWire_multiplatform_iOSapp_target.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - 51C0516524A77DF800194D5E /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51C0519724A7808F00194D5E /* NetNewsWire_multiplatform_iOSapp_target.xcconfig */; - buildSettings = { - }; - name = Release; - }; - 51C0516624A77DF800194D5E /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51C0519824A7808F00194D5E /* NetNewsWire_multiplatform_macOSapp_target.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - 51C0516724A77DF800194D5E /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 51C0519824A7808F00194D5E /* NetNewsWire_multiplatform_macOSapp_target.xcconfig */; - buildSettings = { - }; - name = Release; - }; 653813572680E2DA007A082C /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 6538135B2680E3A9007A082C /* NetNewsWire_shareextension_target_macappstore.xcconfig */; @@ -6359,24 +4866,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 51C0518D24A77DF800194D5E /* Build configuration list for PBXNativeTarget "Multiplatform iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 51C0516424A77DF800194D5E /* Debug */, - 51C0516524A77DF800194D5E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 51C0518E24A77DF800194D5E /* Build configuration list for PBXNativeTarget "Multiplatform macOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 51C0516624A77DF800194D5E /* Debug */, - 51C0516724A77DF800194D5E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 653813562680E2DA007A082C /* Build configuration list for PBXNativeTarget "NetNewsWire Share Extension MAS" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -6532,41 +5021,6 @@ package = 17192AD82567B3D500AAEACA /* XCRemoteSwiftPackageReference "Sparkle-Binary" */; productName = RSSparkle; }; - 17386B6B2577BD820014C8B2 /* RSSparkle */ = { - isa = XCSwiftPackageProductDependency; - package = 17192AD82567B3D500AAEACA /* XCRemoteSwiftPackageReference "Sparkle-Binary" */; - productName = RSSparkle; - }; - 17386B942577C6240014C8B2 /* RSCore */ = { - isa = XCSwiftPackageProductDependency; - package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */; - productName = RSCore; - }; - 17386B972577C6240014C8B2 /* RSTree */ = { - isa = XCSwiftPackageProductDependency; - package = 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */; - productName = RSTree; - }; - 17386B9A2577C6240014C8B2 /* RSWeb */ = { - isa = XCSwiftPackageProductDependency; - package = 51383A3024D1F90E0027E272 /* XCRemoteSwiftPackageReference "RSWeb" */; - productName = RSWeb; - }; - 17386B9D2577C6240014C8B2 /* RSDatabase */ = { - isa = XCSwiftPackageProductDependency; - package = 51B0DF0D24D24E3B000AD99E /* XCRemoteSwiftPackageReference "RSDatabase" */; - productName = RSDatabase; - }; - 17386BA32577C6240014C8B2 /* RSParser */ = { - isa = XCSwiftPackageProductDependency; - package = 51B0DF2324D2C7FA000AD99E /* XCRemoteSwiftPackageReference "RSParser" */; - productName = RSParser; - }; - 17386BB52577C7340014C8B2 /* RSCoreResources */ = { - isa = XCSwiftPackageProductDependency; - package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */; - productName = RSCoreResources; - }; 179C39E926F76B0500D4E741 /* Zip */ = { isa = XCSwiftPackageProductDependency; package = 179D280926F6F93D003B2E0A /* XCRemoteSwiftPackageReference "Zip" */; @@ -6577,47 +5031,6 @@ package = 179D280926F6F93D003B2E0A /* XCRemoteSwiftPackageReference "Zip" */; productName = Zip; }; - 17A1597B24E3DEDD005DA32A /* RSCore */ = { - isa = XCSwiftPackageProductDependency; - package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */; - productName = RSCore; - }; - 17A1597E24E3DEDD005DA32A /* RSTree */ = { - isa = XCSwiftPackageProductDependency; - package = 510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */; - productName = RSTree; - }; - 17A1598124E3DEDD005DA32A /* RSWeb */ = { - isa = XCSwiftPackageProductDependency; - package = 51383A3024D1F90E0027E272 /* XCRemoteSwiftPackageReference "RSWeb" */; - productName = RSWeb; - }; - 17A1598424E3DEDD005DA32A /* RSDatabase */ = { - isa = XCSwiftPackageProductDependency; - package = 51B0DF0D24D24E3B000AD99E /* XCRemoteSwiftPackageReference "RSDatabase" */; - productName = RSDatabase; - }; - 17A1598724E3DEDD005DA32A /* RSParser */ = { - isa = XCSwiftPackageProductDependency; - package = 51B0DF2324D2C7FA000AD99E /* XCRemoteSwiftPackageReference "RSParser" */; - productName = RSParser; - }; - 17E0080E25936DF6000C23F0 /* Articles */ = { - isa = XCSwiftPackageProductDependency; - productName = Articles; - }; - 17E0081125936DF6000C23F0 /* ArticlesDatabase */ = { - isa = XCSwiftPackageProductDependency; - productName = ArticlesDatabase; - }; - 17E0081425936DFF000C23F0 /* Secrets */ = { - isa = XCSwiftPackageProductDependency; - productName = Secrets; - }; - 17E0081725936DFF000C23F0 /* SyncDatabase */ = { - isa = XCSwiftPackageProductDependency; - productName = SyncDatabase; - }; 17EF6A2025C4E5B4002C9F81 /* RSWeb */ = { isa = XCSwiftPackageProductDependency; package = 51383A3024D1F90E0027E272 /* XCRemoteSwiftPackageReference "RSWeb" */; @@ -6728,14 +5141,6 @@ package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */; productName = RSCoreResources; }; - 516B695A24D2F28600B5702F /* Account */ = { - isa = XCSwiftPackageProductDependency; - productName = Account; - }; - 516B695C24D2F28E00B5702F /* Account */ = { - isa = XCSwiftPackageProductDependency; - productName = Account; - }; 516B695E24D2F33B00B5702F /* Account */ = { isa = XCSwiftPackageProductDependency; productName = Account; @@ -6787,27 +5192,6 @@ isa = XCSwiftPackageProductDependency; productName = Secrets; }; - 51E0614425A5A28E00194066 /* Articles */ = { - isa = XCSwiftPackageProductDependency; - productName = Articles; - }; - 51E0614725A5A28E00194066 /* ArticlesDatabase */ = { - isa = XCSwiftPackageProductDependency; - productName = ArticlesDatabase; - }; - 51E0614A25A5A28E00194066 /* Secrets */ = { - isa = XCSwiftPackageProductDependency; - productName = Secrets; - }; - 51E0614D25A5A28E00194066 /* SyncDatabase */ = { - isa = XCSwiftPackageProductDependency; - productName = SyncDatabase; - }; - 51E0615025A5A29600194066 /* CrashReporter */ = { - isa = XCSwiftPackageProductDependency; - package = 519CA8E325841DB700EB079A /* XCRemoteSwiftPackageReference "plcrashreporter" */; - productName = CrashReporter; - }; 6538131D2680E1CA007A082C /* Account */ = { isa = XCSwiftPackageProductDependency; productName = Account; diff --git a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform iOS.xcscheme b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform iOS.xcscheme deleted file mode 100644 index e06266bca..000000000 --- a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform iOS.xcscheme +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform macOS.xcscheme b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform macOS.xcscheme deleted file mode 100644 index 9ac4c281f..000000000 --- a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/Multiplatform macOS.xcscheme +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/xcconfig/NetNewsWire_multiplatform_iOSapp_target.xcconfig b/xcconfig/NetNewsWire_multiplatform_iOSapp_target.xcconfig deleted file mode 100644 index 0880a7223..000000000 --- a/xcconfig/NetNewsWire_multiplatform_iOSapp_target.xcconfig +++ /dev/null @@ -1,48 +0,0 @@ -CODE_SIGN_IDENTITY= iPhone Developer -DEVELOPMENT_TEAM = M8L2WTLA8W -CODE_SIGN_STYLE = Automatic -ORGANIZATION_IDENTIFIER = com.ranchero -DEVELOPER_ENTITLEMENTS = -PROVISIONING_PROFILE_SPECIFIER = - -// developers can locally override the Xcode settings for code signing -// by creating a DeveloperSettings.xcconfig file locally at the appropriate path -// This allows a pristine project to have code signing set up with the appropriate -// developer ID and certificates, and for dev to be able to have local settings -// without needing to check in anything into source control -// -// As an example, make a ../../SharedXcodeSettings/DeveloperSettings.xcconfig file and -// give it the contents -// -// CODE_SIGN_IDENTITY[sdk=macosx*] = Mac Developer -// CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer -// CODE_SIGN_IDENTITY[sdk=iphonesimulator*] = iPhone Developer -// DEVELOPMENT_TEAM = -// ORGANIZATION_IDENTIFIER = -// CODE_SIGN_STYLE = Automatic -// DEVELOPER_ENTITLEMENTS = -dev -// PROVISIONING_PROFILE_SPECIFIER = -// -// And you should be able to build without code signing errors and without modifying -// the NetNewsWire Xcode project. -// -// Example: if your NetNewsWire Xcode project file is at -// /Users/Shared/git/NetNewsWire/NetNewsWire.xcodeproj -// create your DeveloperSettings.xcconfig file at -// /Users/Shared/git/SharedXcodeSettings/DeveloperSettings.xcconfig -// - -#include? "../../SharedXcodeSettings/ProjectSettings.xcconfig" -#include? "../../SharedXcodeSettings/DeveloperSettings.xcconfig" -#include "./common/NetNewsWire_ios_target_common.xcconfig" - -LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks -INFOPLIST_FILE = Multiplatform/iOS/Info.plist -CODE_SIGN_ENTITLEMENTS = Multiplatform/iOS/iOS$(DEVELOPER_ENTITLEMENTS).entitlements -PRODUCT_BUNDLE_IDENTIFIER = $(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS -PRODUCT_NAME = NetNewsWire - -// Override NetNewsWire_project.xcconfig until we are ready to only target 10.16 -IPHONEOS_DEPLOYMENT_TARGET = 14.0 -SWIFT_SWIFT3_OBJC_INFERENCE = Off -SWIFT_VERSION = 5.3 diff --git a/xcconfig/NetNewsWire_multiplatform_macOSapp_target.xcconfig b/xcconfig/NetNewsWire_multiplatform_macOSapp_target.xcconfig deleted file mode 100644 index af5706350..000000000 --- a/xcconfig/NetNewsWire_multiplatform_macOSapp_target.xcconfig +++ /dev/null @@ -1,46 +0,0 @@ -CODE_SIGN_IDENTITY = Mac Developer -DEVELOPMENT_TEAM = M8L2WTLA8W -CODE_SIGN_STYLE = Automatic -ORGANIZATION_IDENTIFIER = com.ranchero -PROVISIONING_PROFILE_SPECIFIER = -DEVELOPER_ENTITLEMENTS = - -// developers can locally override the Xcode settings for code signing -// by creating a DeveloperSettings.xcconfig file locally at the appropriate path -// This allows a pristine project to have code signing set up with the appropriate -// developer ID and certificates, and for dev to be able to have local settings -// without needing to check in anything into source control -// -// As an example, make a ../../SharedXcodeSettings/DeveloperSettings.xcconfig file and -// give it the contents -// -// CODE_SIGN_IDENTITY[sdk=macosx*] = Mac Developer -// CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer -// CODE_SIGN_IDENTITY[sdk=iphonesimulator*] = iPhone Developer -// DEVELOPMENT_TEAM = -// ORGANIZATION_IDENTIFIER = -// CODE_SIGN_STYLE = Automatic -// DEVELOPER_ENTITLEMENTS = -dev -// PROVISIONING_PROFILE_SPECIFIER = -// -// And you should be able to build without code signing errors and without modifying -// the NetNewsWire Xcode project. -// -// Example: if your NetNewsWire Xcode project file is at -// /Users/Shared/git/NetNewsWire/NetNewsWire.xcodeproj -// create your DeveloperSettings.xcconfig file at -// /Users/Shared/git/SharedXcodeSettings/DeveloperSettings.xcconfig -// - -#include? "../../SharedXcodeSettings/DeveloperSettings.xcconfig" -#include "./common/NetNewsWire_macapp_target_common.xcconfig" - -INFOPLIST_FILE = Multiplatform/macOS/Info.plist -CODE_SIGN_ENTITLEMENTS = Multiplatform/macOS/macOS$(DEVELOPER_ENTITLEMENTS).entitlements -PRODUCT_BUNDLE_IDENTIFIER = $(ORGANIZATION_IDENTIFIER).NetNewsWire-Evergreen -PRODUCT_NAME = NetNewsWire - -// Override NetNewsWire_project.xcconfig until we are ready to only target 10.16 -MACOSX_DEPLOYMENT_TARGET = 11.0 -SWIFT_SWIFT3_OBJC_INFERENCE = Off -SWIFT_VERSION = 5.3