mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
This commit includes:
- Localisation for new SwiftUI Settings Views - Inactive/Active Account sections in the Manage Account View - Early work to deprecate AddAccountViewController
This commit is contained in:
@@ -851,18 +851,19 @@
|
||||
DDF9E1D928EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = DDF9E1D628EDF2FC000BC355 /* notificationSoundBlip.mp3 */; };
|
||||
DF32ABE829493193008E3A12 /* SettingsComboTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF32ABE629493192008E3A12 /* SettingsComboTableViewCell.swift */; };
|
||||
DF32ABE929493193008E3A12 /* SettingsComboTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DF32ABE729493193008E3A12 /* SettingsComboTableViewCell.xib */; };
|
||||
DF32ABEB29494CF1008E3A12 /* Settings.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF32ABEA29494CF0008E3A12 /* Settings.strings */; };
|
||||
DF3630EB2936183D00326FB8 /* OPMLDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF3630EA2936183D00326FB8 /* OPMLDocument.swift */; };
|
||||
DF3630EC2936183D00326FB8 /* OPMLDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF3630EA2936183D00326FB8 /* OPMLDocument.swift */; };
|
||||
DF3630ED2936183D00326FB8 /* OPMLDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF3630EA2936183D00326FB8 /* OPMLDocument.swift */; };
|
||||
DF3630EF293618A900326FB8 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF3630EE293618A900326FB8 /* SettingsViewModel.swift */; };
|
||||
DF394F0029357A180081EB6E /* NewArticleNotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF394EFF29357A180081EB6E /* NewArticleNotificationsView.swift */; };
|
||||
DF47CDB02947F77200FCD57E /* AddAccountListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF47CDAF2947F77200FCD57E /* AddAccountListView.swift */; };
|
||||
DF47CDB2294803AB00FCD57E /* AddExtensionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF47CDB1294803AB00FCD57E /* AddExtensionListView.swift */; };
|
||||
DF59F072292085B800ACD33D /* ColorPaletteSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */; };
|
||||
DF59F0742920DB5100ACD33D /* AccountsManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */; };
|
||||
DF5AD10128D6922200CA3BF7 /* SmartFeedSummaryWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1768144D2564BCE000D98635 /* SmartFeedSummaryWidget.swift */; };
|
||||
DF766FED29377FD9006FBBE2 /* ExtensionsManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF766FEC29377FD9006FBBE2 /* ExtensionsManagementView.swift */; };
|
||||
DF790D6228E990A900455FC7 /* AboutData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF790D6128E990A900455FC7 /* AboutData.swift */; };
|
||||
DFB3497A294A962D00BC81AD /* AddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB34979294A962D00BC81AD /* AddAccountView.swift */; };
|
||||
DFC14F0F28EA55BD00F6EE86 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFC14F0E28EA55BD00F6EE86 /* AboutWindowController.swift */; };
|
||||
DFC14F1228EA5DC500F6EE86 /* AboutData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF790D6128E990A900455FC7 /* AboutData.swift */; };
|
||||
DFC14F1328EA677C00F6EE86 /* Bundle-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF42273625800C787DC /* Bundle-Extensions.swift */; };
|
||||
@@ -1610,15 +1611,16 @@
|
||||
DDF9E1D628EDF2FC000BC355 /* notificationSoundBlip.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = notificationSoundBlip.mp3; sourceTree = "<group>"; };
|
||||
DF32ABE629493192008E3A12 /* SettingsComboTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsComboTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DF32ABE729493193008E3A12 /* SettingsComboTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsComboTableViewCell.xib; sourceTree = "<group>"; };
|
||||
DF32ABEA29494CF0008E3A12 /* Settings.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Settings.strings; sourceTree = "<group>"; };
|
||||
DF3630EA2936183D00326FB8 /* OPMLDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLDocument.swift; sourceTree = "<group>"; };
|
||||
DF3630EE293618A900326FB8 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
DF394EFF29357A180081EB6E /* NewArticleNotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewArticleNotificationsView.swift; sourceTree = "<group>"; };
|
||||
DF47CDAF2947F77200FCD57E /* AddAccountListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountListView.swift; sourceTree = "<group>"; };
|
||||
DF47CDB1294803AB00FCD57E /* AddExtensionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionListView.swift; sourceTree = "<group>"; };
|
||||
DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteSelectorView.swift; sourceTree = "<group>"; };
|
||||
DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsManagementView.swift; sourceTree = "<group>"; };
|
||||
DF766FEC29377FD9006FBBE2 /* ExtensionsManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsManagementView.swift; sourceTree = "<group>"; };
|
||||
DF790D6128E990A900455FC7 /* AboutData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutData.swift; sourceTree = "<group>"; };
|
||||
DFB34979294A962D00BC81AD /* AddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountView.swift; sourceTree = "<group>"; };
|
||||
DFC14F0E28EA55BD00F6EE86 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = "<group>"; };
|
||||
DFC14F1428EB177000F6EE86 /* AboutNetNewsWireView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutNetNewsWireView.swift; sourceTree = "<group>"; };
|
||||
DFC14F1628EB17A800F6EE86 /* CreditsNetNewsWireView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsNetNewsWireView.swift; sourceTree = "<group>"; };
|
||||
@@ -2018,6 +2020,7 @@
|
||||
5183CCEB227117C70010922C /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DF32ABEA29494CF0008E3A12 /* Settings.strings */,
|
||||
DFD406F8291FB5D500C02962 /* Views */,
|
||||
51A16992235E10D600EB091F /* AddAccountViewController.swift */,
|
||||
519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */,
|
||||
@@ -2909,10 +2912,8 @@
|
||||
DF59F0752920E42000ACD33D /* Account and Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */,
|
||||
DF47CDAF2947F77200FCD57E /* AddAccountListView.swift */,
|
||||
DF766FEC29377FD9006FBBE2 /* ExtensionsManagementView.swift */,
|
||||
DF47CDB1294803AB00FCD57E /* AddExtensionListView.swift */,
|
||||
DFB3497B294AA95200BC81AD /* Accounts */,
|
||||
DFB3497C294AA95A00BC81AD /* Extensions */,
|
||||
);
|
||||
path = "Account and Extensions";
|
||||
sourceTree = "<group>";
|
||||
@@ -2936,6 +2937,24 @@
|
||||
path = General;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DFB3497B294AA95200BC81AD /* Accounts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DFB34979294A962D00BC81AD /* AddAccountView.swift */,
|
||||
DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */,
|
||||
);
|
||||
path = Accounts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DFB3497C294AA95A00BC81AD /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DF766FEC29377FD9006FBBE2 /* ExtensionsManagementView.swift */,
|
||||
DF47CDB1294803AB00FCD57E /* AddExtensionListView.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DFC14F0928EA51AB00F6EE86 /* About */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3536,6 +3555,7 @@
|
||||
51077C5A27A86D16000C71DB /* Hyperlegible.nnwtheme in Resources */,
|
||||
516A09422361248000EAE89B /* Inspector.storyboard in Resources */,
|
||||
DDF9E1D928EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */,
|
||||
DF32ABEB29494CF1008E3A12 /* Settings.strings in Resources */,
|
||||
51DEE81A26FBFF84006DAA56 /* Promenade.nnwtheme in Resources */,
|
||||
1768140B2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig in Resources */,
|
||||
5103A9B424216A4200410853 /* blank.html in Resources */,
|
||||
@@ -4147,6 +4167,7 @@
|
||||
519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */,
|
||||
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
|
||||
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */,
|
||||
DFB3497A294A962D00BC81AD /* AddAccountView.swift in Sources */,
|
||||
513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */,
|
||||
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */,
|
||||
51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */,
|
||||
@@ -4259,7 +4280,6 @@
|
||||
5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */,
|
||||
DF3630EF293618A900326FB8 /* SettingsViewModel.swift in Sources */,
|
||||
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */,
|
||||
DF47CDB02947F77200FCD57E /* AddAccountListView.swift in Sources */,
|
||||
51B5C8C023F3866C00032075 /* ExtensionFeedAddRequestFile.swift in Sources */,
|
||||
51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */,
|
||||
51934CCE2310792F006127BE /* ActivityManager.swift in Sources */,
|
||||
|
||||
@@ -11,39 +11,11 @@ import UIKit
|
||||
import SwiftUI
|
||||
import RSCore
|
||||
|
||||
struct AddAccountViewControllerRepresentable: UIViewControllerRepresentable {
|
||||
func makeUIViewController(context: Context) -> AddAccountViewController {
|
||||
let storyboard = UIStoryboard(name: "Settings", bundle: .main)
|
||||
let controller = storyboard.instantiateViewController(withIdentifier: "AddAccountViewController") as! AddAccountViewController
|
||||
|
||||
context.coordinator.parentObserver = controller.observe(\.parent, changeHandler: { vc, _ in
|
||||
vc.parent?.title = vc.title
|
||||
vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems
|
||||
})
|
||||
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: AddAccountViewController, context: Context) {
|
||||
//
|
||||
}
|
||||
|
||||
typealias UIViewControllerType = AddAccountViewController
|
||||
|
||||
class Coordinator {
|
||||
var parentObserver: NSKeyValueObservation?
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Self.Coordinator { Coordinator() }
|
||||
|
||||
}
|
||||
|
||||
|
||||
protocol AddAccountDismissDelegate: UIViewController {
|
||||
func dismiss()
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use AddAccountView")
|
||||
class AddAccountViewController: UITableViewController, AddAccountDismissDelegate {
|
||||
|
||||
private enum AddAccountSections: Int, CaseIterable {
|
||||
|
||||
102
iOS/Settings/Settings.strings
Normal file
102
iOS/Settings/Settings.strings
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
Settings.strings
|
||||
NetNewsWire
|
||||
|
||||
Created by Stuart Breckenridge on 14/12/2022.
|
||||
Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
*/
|
||||
|
||||
/* Settings */
|
||||
|
||||
"SETTINGS_TITLE" = "Settings";
|
||||
"DEVICE_PERMISSIONS_HEADER" = "Device Permissions";
|
||||
"DEVICE_PERMISSIONS_FOOTER" = "Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more.";
|
||||
"ACCOUNTS_EXTENSIONS_HEADER" = "Accounts & Extensions";
|
||||
"ACCOUNTS_EXTENSIONS_FOOTER" = "Add, delete, enable, or disable accounts and extensions.";
|
||||
"APPEARANCE_HEADER" = "Appearance";
|
||||
"APPEARANCE_FOOTER" = "Manage the look, feel, and behavior of NetNewsWire.";
|
||||
"DISPLAY_BEHAVIORS_HEADER" = "Display & Behaviors";
|
||||
|
||||
/* Settings Rows */
|
||||
|
||||
"OPEN_SYSTEM_SETTINGS" = "Open System Settings";
|
||||
"NEW_ARTICLE_NOTIFICATIONS" = "New Article Notifications";
|
||||
"MANAGE_ACCOUNTS" = "Manage Accounts";
|
||||
"MANAGE_EXTENSIONS" = "Manage Extensions";
|
||||
"IMPORT_SUBSCRIPTIONS" = "Import Subscriptions";
|
||||
"EXPORT_SUBSCRIPTIONS" = "Export Subscriptions";
|
||||
"ABOUT" = "About NetNewsWire";
|
||||
"NETNEWSWIRE_HELP" = "NetNewsWire Help Guide";
|
||||
"NETNEWSWIRE_WEBSITE" = "NewNewsWire Website";
|
||||
|
||||
/* Display & Behaviors */
|
||||
|
||||
"APPLICATION_HEADER" = "Application";
|
||||
"TIMELINE_HEADER" = "Timeline";
|
||||
"ARTICLE_HEADER" = "Article";
|
||||
"ALWAYS_DARK_MODE" = "Always Dark";
|
||||
"ALWAYS_LIGHT_MODE" = "Always Light";
|
||||
"USE_SYSTEM_DISPLAY_MODE" = "Use System";
|
||||
"SORT_OLDEST_NEWEST" = "Sort Oldest to Newest";
|
||||
"GROUP_BY_FEED" = "Group by Feed";
|
||||
"REFRESH_TO_CLEAR_READ_ARTICLES" = "Refresh to Clear Articles";
|
||||
"TIMELINE_LAYOUT" = "Timeline Layout";
|
||||
"ARTICLE_THEME" = "Article Theme";
|
||||
"CONFIRM_MARK_ALL_AS_READ" = "Confirm Mark All as Read";
|
||||
"OPEN_LINKS_IN_APP" = "Open Links in NetNewsWire";
|
||||
|
||||
/* Account Management */
|
||||
|
||||
"ADD_ACCOUNT" = "Add Account";
|
||||
"NO_INACTIVE_ACCOUNT_FOOTER" = "There are no inactive accounts.";
|
||||
"ACCOUNT_REMOVE %@" = "Are you sure you want to remove “%@”?";
|
||||
"REMOVE_FEEDLY_CONFIRMATION" = "Are you sure you want to remove this account? NetNewsWire will no longer be able to access articles and feeds unless the account is added again.";
|
||||
"REMOVE_ACCOUNT_CONFIRMATION" = "Are you sure you want to remove this account? This cannot be undone.";
|
||||
"ACTIVE_ACCOUNTS_HEADER" = "Active Accounts";
|
||||
"INACTIVE_ACCOUNTS_HEADER" = "Inactive Accounts";
|
||||
|
||||
/* Extension Management */
|
||||
|
||||
"DEACTIVATE" = "Deactivate";
|
||||
"DEACTIVATE_EXTENSION_TITLE" = "Deactivate Extension";
|
||||
"DEACTIVATE_EXTENSION %@" = "Deactivate “%@”";
|
||||
"ACTIVE_EXTENSIONS" = "Active Extensions";
|
||||
"FEED_PROVIDER_HEADER" = "Feed Provider";
|
||||
"FEED_PROVIDER_FOOTER" = "Feed Providers allow you to subscribe to some pages as if they were RSS Feeds.";
|
||||
"ADD_EXTENSIONS_TITLE" = "Add Extension";
|
||||
|
||||
/* New Article Notifications */
|
||||
|
||||
"NEW_ARTICLE_NOTIFICATIONS_TITLE" = "New Article Notifications";
|
||||
|
||||
/* About */
|
||||
|
||||
"ABOUT_TITLE" = "About";
|
||||
"PRIMARY_CONTRIBUTORS" = "Primary Contributors";
|
||||
"ADDITIONAL_CONTRIBUTORS" = "Additional Contributors";
|
||||
"THANKS" = "Thanks";
|
||||
"BYLINE" = "By Brent Simmons and the Ranchero Software team.";
|
||||
|
||||
/* Add Account */
|
||||
"ADD_LOCAL_ACCOUNT_HEADER" = "Local";
|
||||
"ADD_LOCAL_ACCOUNT_FOOTER" = "Local accounts do not sync your feeds across devices";
|
||||
"ADD_CLOUDKIT_ACCOUNT_HEADER" = "iCloud";
|
||||
"ADD_CLOUDKIT_ACCOUNT_FOOTER" = "Your iCloud account syncs your feeds across your Mac and iOS devices";
|
||||
"ADD_WEB_ACCOUNT_HEADER" = "Web";
|
||||
"ADD_WEB_ACCOUNT_FOOTER" = "Web accounts sync your feeds across all your devices";
|
||||
"ADD_SELFHOSTED_ACCOUNT_HEADER" = "Self-hosted";
|
||||
"ADD_SELFHOSTED_ACCOUNT_FOOTER" = "Self-hosted accounts sync your feeds across all your devices";
|
||||
|
||||
|
||||
/* Alerts */
|
||||
|
||||
"IMPORT_OPML_CONFIRMATION" = "Choose an account to receive the imported feeds and folders";
|
||||
"IMPORT_OPML_SUCCESS_TITLE" = "Imported Successfully";
|
||||
"IMPORT_OPML_SUCCESS_MESSAGE %@" = "Subscriptions have been imported to your %@ account.";
|
||||
"EXPORT_OPML_CONFIRMATION" = "Choose an account with the subscriptions to export";
|
||||
"EXPORT_OPML_SUCCESS_TITLE" = "Exported Successfully";
|
||||
"EXPORT_OPML_SUCCESS_MESSAGE" = "Your OPML file has been successfully exported.";
|
||||
"ERROR_TITLE" = "Error";
|
||||
"REMOVE" = "Remove";
|
||||
"CANCEL" = "Cancel";
|
||||
"DONE" = "Done";
|
||||
@@ -10,50 +10,30 @@ import SwiftUI
|
||||
import Account
|
||||
import Combine
|
||||
|
||||
struct AddAccountWrapper: UIViewControllerRepresentable {
|
||||
func makeUIViewController(context: Context) -> AddAccountViewController {
|
||||
let controller = UIStoryboard.settings.instantiateViewController(withIdentifier: "AddAccountViewController") as! AddAccountViewController
|
||||
|
||||
|
||||
context.coordinator.parentObserver = controller.observe(\.parent, changeHandler: { vc, _ in
|
||||
vc.parent?.title = vc.title
|
||||
vc.parent?.navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction(title: NSLocalizedString("Done", comment: "Done"), image: nil, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { _ in
|
||||
controller.dismiss(animated: true )
|
||||
}), menu: nil)
|
||||
})
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: AddAccountViewController, context: Context) {
|
||||
//
|
||||
}
|
||||
|
||||
class Coordinator {
|
||||
var parentObserver: NSKeyValueObservation?
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Self.Coordinator { Coordinator() }
|
||||
|
||||
typealias UIViewControllerType = AddAccountViewController
|
||||
|
||||
}
|
||||
|
||||
struct AccountsManagementView: View {
|
||||
|
||||
@State private var showAddAccountSheet: Bool = false
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
@State private var sortedAccounts = [Account]()
|
||||
@State private var sortedActiveAccounts = [Account]()
|
||||
@State private var sortedInactiveAccounts = [Account]()
|
||||
@State private var accountToRemove: Account?
|
||||
@State private var showRemoveAccountAlert: Bool = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(sortedAccounts, id: \.self) { account in
|
||||
accountRow(account, showRemoveAccountAlert: $showRemoveAccountAlert, accountToRemove: $accountToRemove)
|
||||
Section(header: Text("ACTIVE_ACCOUNTS_HEADER", tableName: "Settings")) {
|
||||
ForEach(sortedActiveAccounts, id: \.self) { account in
|
||||
accountRow(account, showRemoveAccountAlert: $showRemoveAccountAlert, accountToRemove: $accountToRemove)
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("INACTIVE_ACCOUNTS_HEADER", tableName: "Settings")) {
|
||||
ForEach(sortedInactiveAccounts, id: \.self) { account in
|
||||
accountRow(account, showRemoveAccountAlert: $showRemoveAccountAlert, accountToRemove: $accountToRemove)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.navigationTitle(Text("Manage Accounts"))
|
||||
.navigationTitle(Text("MANAGE_ACCOUNTS", tableName: "Settings"))
|
||||
.tint(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
@@ -80,20 +60,20 @@ struct AccountsManagementView: View {
|
||||
refreshAccounts()
|
||||
}
|
||||
.sheet(isPresented: $showAddAccountSheet) {
|
||||
AddAccountListView()
|
||||
AddAccountView()
|
||||
}
|
||||
.alert("Remove “\(accountToRemove?.nameForDisplay ?? "")”?",
|
||||
.alert(Text("ACCOUNT_REMOVE \(accountToRemove?.nameForDisplay ?? "")", tableName: "Settings"),
|
||||
isPresented: $showRemoveAccountAlert) {
|
||||
Button(role: .destructive) {
|
||||
AccountManager.shared.deleteAccount(accountToRemove!)
|
||||
} label: {
|
||||
Text("Remove")
|
||||
Text("REMOVE", tableName: "Settings")
|
||||
}
|
||||
|
||||
Button(role: .cancel) {
|
||||
accountToRemove = nil
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
Text("CANCEL", tableName: "Settings")
|
||||
}
|
||||
} message: {
|
||||
switch accountToRemove {
|
||||
@@ -102,18 +82,17 @@ struct AccountsManagementView: View {
|
||||
case .some(let wrapped):
|
||||
switch wrapped.type {
|
||||
case .feedly:
|
||||
Text("Are you sure you want to remove this account? NetNewsWire will no longer be able to access articles and feeds unless the account is added again.")
|
||||
Text("REMOVE_FEEDLY_CONFIRMATION", tableName: "Settings")
|
||||
default:
|
||||
Text("Are you sure you want to remove this account? This cannot be undone.")
|
||||
Text("REMOVE_ACCOUNT_CONFIRMATION", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func refreshAccounts() {
|
||||
sortedAccounts = []
|
||||
sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
sortedActiveAccounts = AccountManager.shared.sortedActiveAccounts
|
||||
sortedInactiveAccounts = AccountManager.shared.sortedAccounts.filter({ $0.isActive == false })
|
||||
}
|
||||
|
||||
func accountRow(_ account: Account, showRemoveAccountAlert: Binding<Bool>, accountToRemove: Binding<Account?>) -> some View {
|
||||
@@ -123,6 +102,7 @@ struct AccountsManagementView: View {
|
||||
} label: {
|
||||
Image(uiImage: account.smallIcon!.image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 25, height: 25)
|
||||
Text(account.nameForDisplay)
|
||||
}.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
@@ -131,7 +111,11 @@ struct AccountsManagementView: View {
|
||||
accountToRemove.wrappedValue = account
|
||||
showRemoveAccountAlert.wrappedValue = true
|
||||
} label: {
|
||||
Label("Remove Account", systemImage: "trash")
|
||||
Label {
|
||||
Text("REMOVE_ACCOUNT", tableName: "Settings")
|
||||
} icon: {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
}.tint(.red)
|
||||
}
|
||||
}
|
||||
@@ -139,7 +123,7 @@ struct AccountsManagementView: View {
|
||||
|
||||
var inactiveFooterText: some View {
|
||||
if AccountManager.shared.sortedAccounts.filter({ $0.isActive == false }).count == 0 {
|
||||
return Text("There are no inactive accounts.")
|
||||
return Text("NO_INACTIVE_ACCOUNT_FOOTER", tableName: "Settings")
|
||||
} else {
|
||||
return Text("")
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// AddAccountView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 15/12/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
public final class AddAcccountViewModel: ObservableObject, OAuthAccountAuthorizationOperationDelegate {
|
||||
|
||||
@Published public var showAddAccountSheet: (Bool, accountType: AccountType) = (false, .onMyMac)
|
||||
public var webAccountTypes: [AccountType] {
|
||||
if AppDefaults.shared.isDeveloperBuild {
|
||||
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader].filter({ $0.isDeveloperRestricted == false })
|
||||
} else {
|
||||
return [.bazQux, .feedbin, .feedly, .inoreader, .newsBlur, .theOldReader]
|
||||
}
|
||||
}
|
||||
|
||||
public var rootViewController: UIViewController? {
|
||||
var currentKeyWindow: UIWindow? {
|
||||
UIApplication.shared.connectedScenes
|
||||
.filter { $0.activationState == .foregroundActive }
|
||||
.map { $0 as? UIWindowScene }
|
||||
.compactMap { $0 }
|
||||
.first?.windows
|
||||
.filter { $0.isKeyWindow }
|
||||
.first
|
||||
}
|
||||
|
||||
var rootViewController: UIViewController? {
|
||||
currentKeyWindow?.rootViewController
|
||||
}
|
||||
|
||||
return rootViewController
|
||||
}
|
||||
|
||||
public func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) {
|
||||
account.refreshAll { [weak self] result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
guard let viewController = self?.rootViewController else {
|
||||
return
|
||||
}
|
||||
viewController.presentError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) {
|
||||
//presentError(error)
|
||||
}
|
||||
}
|
||||
|
||||
struct AddAccountView: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@StateObject private var viewModel = AddAcccountViewModel()
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
localAccountSection
|
||||
cloudKitSection
|
||||
webAccountSection
|
||||
selfHostedSection
|
||||
}
|
||||
.navigationTitle(Text("ADD_ACCOUNT", tableName: "Settings"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.listItemTint(.primary)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(role: .cancel) {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("DONE", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $viewModel.showAddAccountSheet.0) {
|
||||
switch viewModel.showAddAccountSheet.accountType {
|
||||
case .onMyMac:
|
||||
Text("ON MY MAC")
|
||||
case .cloudKit:
|
||||
Text("CLOUDKIT")
|
||||
case .freshRSS:
|
||||
Text("SELF_HOSTED")
|
||||
default:
|
||||
Text(viewModel.showAddAccountSheet.accountType.localizedAccountName())
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .UserDidAddAccount)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var localAccountSection: some View {
|
||||
Section {
|
||||
Button {
|
||||
viewModel.showAddAccountSheet = (true, .onMyMac)
|
||||
} label: {
|
||||
Label {
|
||||
Text(AccountType.onMyMac.localizedAccountName())
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(uiImage: AppAssets.image(for: .onMyMac)!)
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("ADD_LOCAL_ACCOUNT_HEADER", tableName: "Settings")
|
||||
} footer: {
|
||||
Text("ADD_LOCAL_ACCOUNT_FOOTER", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
var cloudKitSection: some View {
|
||||
Section {
|
||||
Button {
|
||||
viewModel.showAddAccountSheet = (true, .cloudKit)
|
||||
} label: {
|
||||
Label {
|
||||
Text(AccountType.cloudKit.localizedAccountName())
|
||||
.foregroundColor(interactionDisabled(for: .cloudKit) ? .secondary : .primary)
|
||||
} icon: {
|
||||
Image(uiImage: AppAssets.image(for: .cloudKit)!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
.disabled(interactionDisabled(for: .cloudKit))
|
||||
} header: {
|
||||
Text("ADD_CLOUDKIT_ACCOUNT_HEADER", tableName: "Settings")
|
||||
} footer: {
|
||||
Text("ADD_CLOUDKIT_ACCOUNT_FOOTER", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
var webAccountSection: some View {
|
||||
Section {
|
||||
ForEach(viewModel.webAccountTypes, id: \.self) { webAccount in
|
||||
Button {
|
||||
if webAccount == .feedly {
|
||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
||||
addAccount.delegate = viewModel
|
||||
addAccount.presentationAnchor = viewModel.rootViewController?.view.window
|
||||
MainThreadOperationQueue.shared.add(addAccount)
|
||||
} else {
|
||||
viewModel.showAddAccountSheet = (true, webAccount)
|
||||
}
|
||||
|
||||
} label: {
|
||||
Label {
|
||||
Text(webAccount.localizedAccountName())
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(uiImage: AppAssets.image(for: webAccount)!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("ADD_WEB_ACCOUNT_HEADER", tableName: "Settings")
|
||||
} footer: {
|
||||
Text("ADD_WEB_ACCOUNT_FOOTER", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
var selfHostedSection: some View {
|
||||
Section {
|
||||
Button {
|
||||
viewModel.showAddAccountSheet = (true, .freshRSS)
|
||||
} label: {
|
||||
Label {
|
||||
Text(AccountType.freshRSS.localizedAccountName())
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(uiImage: AppAssets.image(for: .freshRSS)!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("ADD_SELFHOSTED_ACCOUNT_HEADER", tableName: "Settings")
|
||||
} footer: {
|
||||
Text("ADD_SELFHOSTED_ACCOUNT_FOOTER", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func interactionDisabled(for accountType: AccountType) -> Bool {
|
||||
if accountType == .cloudKit {
|
||||
if AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) {
|
||||
return true
|
||||
}
|
||||
return AppDefaults.shared.isDeveloperBuild
|
||||
}
|
||||
|
||||
return accountType.isDeveloperRestricted
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// AddAccountListView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/12/2022.
|
||||
// Copyright © 2022 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
struct AddAccountListView: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
AddAccountWrapper()
|
||||
.navigationTitle("Add Account")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .UserDidAddAccount)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddAccountListView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddAccountListView()
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,8 @@ struct AddExtensionListView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
Section(header: Text("Feed Provider"),footer: Text("Feed Providers allow you to subscribe to some pages as if they were RSS Feeds.")) {
|
||||
Section(header: Text("FEED_PROVIDER_HEADER", tableName: "Settings"),
|
||||
footer: Text("FEED_PROVIDER_FOOTER", tableName: "Settings")) {
|
||||
ForEach(0..<availableExtensionPointTypes.count, id: \.self) { i in
|
||||
NavigationLink {
|
||||
EnableExtensionPointViewWrapper(extensionPoint: availableExtensionPointTypes[i])
|
||||
@@ -34,13 +35,13 @@ struct AddExtensionListView: View {
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle("Add Extension")
|
||||
.navigationTitle(Text("ADD_EXTENSIONS_TITLE", tableName: "Settings"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
Button(role: .cancel) {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Done")
|
||||
Text("DONE", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ struct ExtensionsManagementView: View {
|
||||
List {
|
||||
activeExtensionsSection
|
||||
}
|
||||
.navigationTitle(Text("Manage Extensions"))
|
||||
.navigationTitle(Text("MANAGE_EXTENSIONS", tableName: "Settings"))
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
@@ -33,15 +33,23 @@ struct ExtensionsManagementView: View {
|
||||
.sheet(isPresented: $showAddExtensionView) {
|
||||
AddExtensionListView()
|
||||
}
|
||||
.alert("Deactivate Extension", isPresented: $showDeactivateAlert) {
|
||||
Button("Deactivate", role: .destructive) {
|
||||
.alert(Text("DEACTIVATE_EXTENSION_TITLE", tableName: "Settings"),
|
||||
isPresented: $showDeactivateAlert) {
|
||||
|
||||
Button(role: .destructive) {
|
||||
ExtensionPointManager.shared.deactivateExtensionPoint(extensionToDeactivate!.value.extensionPointID)
|
||||
} label: {
|
||||
Text("DEACTIVATE", tableName: "Settings")
|
||||
}
|
||||
Button("Dismiss", role: .cancel) {
|
||||
|
||||
Button(role: .cancel) {
|
||||
extensionToDeactivate = nil
|
||||
} label: {
|
||||
Text("DISMISS", tableName: "Settings")
|
||||
}
|
||||
|
||||
} message: {
|
||||
Text("Deactivate “\(extensionToDeactivate?.value.title ?? "")”?")
|
||||
Text("DEACTIVATE_EXTENSION \(extensionToDeactivate?.value.title ?? "")", tableName: "Settings")
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .ActiveExtensionPointsDidChange)) { _ in
|
||||
availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes.sorted(by: { $0.title < $1.title })
|
||||
@@ -50,7 +58,7 @@ struct ExtensionsManagementView: View {
|
||||
}
|
||||
|
||||
private var activeExtensionsSection: some View {
|
||||
Section(header: Text("Active Extensions")) {
|
||||
Section(header: Text("ACTIVE_EXTENSIONS", tableName: "Settings")) {
|
||||
ForEach(0..<ExtensionPointManager.shared.activeExtensionPoints.count, id: \.self) { i in
|
||||
let point = Array(ExtensionPointManager.shared.activeExtensionPoints)[i]
|
||||
NavigationLink {
|
||||
@@ -68,7 +76,7 @@ struct ExtensionsManagementView: View {
|
||||
extensionToDeactivate = point
|
||||
showDeactivateAlert = true
|
||||
} label: {
|
||||
Text("Deactivate")
|
||||
Text("DEACTIVATE", tableName: "Settings")
|
||||
Image(systemName: "poweroff")
|
||||
}.tint(.red)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ struct ColorPaletteSelectorView: View {
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 40.0, height: 40.0)
|
||||
Text("Always Light")
|
||||
Text("ALWAYS_LIGHT_MODE", tableName: "Settings")
|
||||
.font(.subheadline)
|
||||
if AppDefaults.userInterfaceColorPalette == .light {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
@@ -47,7 +47,7 @@ struct ColorPaletteSelectorView: View {
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 40.0, height: 40.0)
|
||||
Text("Always Dark")
|
||||
Text("ALWAYS_DARK_MODE", tableName: "Settings")
|
||||
.font(.subheadline)
|
||||
if AppDefaults.userInterfaceColorPalette == .dark {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
@@ -66,7 +66,7 @@ struct ColorPaletteSelectorView: View {
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 40.0, height: 40.0)
|
||||
Text("Use System")
|
||||
Text("USE_SYSTEM_DISPLAY_MODE", tableName: "Settings")
|
||||
.font(.subheadline)
|
||||
if AppDefaults.userInterfaceColorPalette == .automatic {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
|
||||
@@ -14,19 +14,19 @@ struct DisplayAndBehaviorsView: View {
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section("Application") {
|
||||
Section(header: Text("APPLICATION_HEADER", tableName: "Settings")) {
|
||||
ColorPaletteSelectorView()
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
|
||||
Section("Timeline") {
|
||||
Section(header: Text("TIMELINE_HEADER", tableName: "Settings")) {
|
||||
SettingsViewRows.sortOldestToNewest($appDefaults.timelineSortDirectionBool)
|
||||
SettingsViewRows.groupByFeed($appDefaults.timelineGroupByFeed)
|
||||
SettingsViewRows.refreshToClearReadArticles($appDefaults.refreshClearsReadArticles)
|
||||
SettingsViewRows.timelineLayout
|
||||
}
|
||||
|
||||
Section("Article") {
|
||||
Section(header: Text("ARTICLE_HEADER", tableName: "Settings")) {
|
||||
SettingsViewRows.themeSelection
|
||||
SettingsViewRows.confirmMarkAllAsRead($appDefaults.confirmMarkAllAsRead)
|
||||
SettingsViewRows.openLinksInNetNewsWire(Binding<Bool>(
|
||||
|
||||
@@ -18,7 +18,7 @@ struct SettingsViewRows {
|
||||
/// This row, when tapped, will open iOS System Settings.
|
||||
static var openSystemSettings: some View {
|
||||
Label {
|
||||
Text("Open System Settings")
|
||||
Text("OPEN_SYSTEM_SETTINGS", tableName: "Settings")
|
||||
} icon: {
|
||||
Image("system.settings")
|
||||
.resizable()
|
||||
@@ -36,7 +36,7 @@ struct SettingsViewRows {
|
||||
static var configureNewArticleNotifications: some View {
|
||||
NavigationLink(destination: NewArticleNotificationsView()) {
|
||||
Label {
|
||||
Text("New Article Notifications")
|
||||
Text("NEW_ARTICLE_NOTIFICATIONS", tableName: "Settings")
|
||||
} icon: {
|
||||
Image("notifications.sounds")
|
||||
.resizable()
|
||||
@@ -51,7 +51,7 @@ struct SettingsViewRows {
|
||||
static var addAccount: some View {
|
||||
NavigationLink(destination: AccountsManagementView()) {
|
||||
Label {
|
||||
Text("Manage Accounts")
|
||||
Text("MANAGE_ACCOUNTS", tableName: "Settings")
|
||||
} icon: {
|
||||
Image("app.account")
|
||||
.resizable()
|
||||
@@ -66,7 +66,7 @@ struct SettingsViewRows {
|
||||
static var manageExtensions: some View {
|
||||
NavigationLink(destination: ExtensionsManagementView()) {
|
||||
Label {
|
||||
Text("Manage Extensions")
|
||||
Text("MANAGE_EXTENSIONS", tableName: "Settings")
|
||||
} icon: {
|
||||
Image("app.extension")
|
||||
.resizable()
|
||||
@@ -83,7 +83,7 @@ struct SettingsViewRows {
|
||||
showImportActionSheet.wrappedValue.toggle()
|
||||
} label: {
|
||||
Label {
|
||||
Text("Import Subscriptions")
|
||||
Text("IMPORT_SUBSCRIPTIONS", tableName: "Settings")
|
||||
.foregroundColor(.primary)
|
||||
|
||||
} icon: {
|
||||
@@ -102,7 +102,7 @@ struct SettingsViewRows {
|
||||
showExportActionSheet.wrappedValue.toggle()
|
||||
} label: {
|
||||
Label {
|
||||
Text("Export Subscriptions")
|
||||
Text("EXPORT_SUBSCRIPTIONS", tableName: "Settings")
|
||||
.foregroundColor(.primary)
|
||||
|
||||
} icon: {
|
||||
@@ -118,28 +118,34 @@ struct SettingsViewRows {
|
||||
/// - Parameter preference: `Binding<Bool>`
|
||||
/// - Returns: `Toggle`
|
||||
static func sortOldestToNewest(_ preference: Binding<Bool>) -> some View {
|
||||
Toggle("Sort Oldest to Newest", isOn: preference)
|
||||
Toggle(isOn: preference) {
|
||||
Text("SORT_OLDEST_NEWEST", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Toggle` which triggers changes to the user's grouping preference.
|
||||
/// - Parameter preference: `Binding<Bool>`
|
||||
/// - Returns: `Toggle`
|
||||
static func groupByFeed(_ preference: Binding<Bool>) -> some View {
|
||||
Toggle("Group by Feed", isOn: preference)
|
||||
Toggle(isOn: preference) {
|
||||
Text("GROUP_BY_FEED", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `Toggle` which triggers changes to the user's refresh to clear preferences.
|
||||
/// - Parameter preference: `Binding<Bool>`
|
||||
/// - Returns: `Toggle`
|
||||
static func refreshToClearReadArticles(_ preference: Binding<Bool>) -> some View {
|
||||
Toggle("Refresh To Clear Read Articles", isOn: preference)
|
||||
Toggle(isOn: preference) {
|
||||
Text("REFRESH_TO_CLEAR_READ_ARTICLES", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
/// This row, when tapped, will push the the Timeline Layout screen
|
||||
/// in to view.
|
||||
static var timelineLayout: some View {
|
||||
NavigationLink(destination: TimelineCustomizerWrapper().edgesIgnoringSafeArea(.all).navigationTitle(Text("Timeline Layout"))) {
|
||||
Text("Timeline Layout")
|
||||
NavigationLink(destination: TimelineCustomizerWrapper().edgesIgnoringSafeArea(.all).navigationTitle(Text("TIMELINE_LAYOUT", tableName: "Settings"))) {
|
||||
Text("TIMELINE_LAYOUT", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +154,7 @@ struct SettingsViewRows {
|
||||
static var themeSelection: some View {
|
||||
NavigationLink(destination: ArticleThemesWrapper().edgesIgnoringSafeArea(.all)) {
|
||||
HStack {
|
||||
Text("Article Theme")
|
||||
Text("ARTICLE_THEME", tableName: "Settings")
|
||||
Spacer()
|
||||
Text(ArticleThemesManager.shared.currentTheme.name)
|
||||
.font(.callout)
|
||||
@@ -158,31 +164,25 @@ struct SettingsViewRows {
|
||||
}
|
||||
|
||||
static func confirmMarkAllAsRead(_ preference: Binding<Bool>) -> some View {
|
||||
Toggle("Confirm Mark All as Read", isOn: preference)
|
||||
Toggle(isOn: preference) {
|
||||
Text("CONFIRM_MARK_ALL_AS_READ", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
static func openLinksInNetNewsWire(_ preference: Binding<Bool>) -> some View {
|
||||
Toggle("Open Links in NetNewsWire", isOn: preference)
|
||||
Toggle(isOn: preference) {
|
||||
Text("OPEN_LINKS_IN_APP", tableName: "Settings")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add Reader Mode Defaults here. See #3684.
|
||||
static func enableFullScreenArticles(_ preference: Binding<Bool>) -> some View {
|
||||
Toggle(isOn: preference) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Enable Full Screen Articles")
|
||||
Text("Tap the article top bar to enter Full Screen. Tap the top or bottom to exit.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This row, when tapped, will push the New Article Notifications
|
||||
/// screen in to view.
|
||||
static func configureAppearance(_ isShown: Binding<Bool>) -> some View {
|
||||
NavigationLink(destination: DisplayAndBehaviorsView(), isActive: isShown) {
|
||||
Label {
|
||||
Text("Display & Behaviors")
|
||||
Text("DISPLAY_BEHAVIORS_HEADER", tableName: "Settings")
|
||||
} icon: {
|
||||
Image("app.appearance")
|
||||
.resizable()
|
||||
@@ -220,7 +220,7 @@ struct SettingsViewRows {
|
||||
AboutView()
|
||||
} label: {
|
||||
Label {
|
||||
Text("About NetNewsWire")
|
||||
Text("ABOUT", tableName: "Settings")
|
||||
} icon: {
|
||||
Image(systemName: "info.circle")
|
||||
.resizable()
|
||||
|
||||
@@ -22,18 +22,21 @@ struct SettingsView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
|
||||
// Device Permissions
|
||||
Section(header: Text("Device Permissions"), footer: Text("Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more.")) {
|
||||
Section(header: Text("DEVICE_PERMISSIONS_HEADER", tableName: "Settings"),
|
||||
footer: Text("DEVICE_PERMISSIONS_FOOTER", tableName: "Settings")) {
|
||||
SettingsViewRows.openSystemSettings
|
||||
}
|
||||
|
||||
// Account/Extensions/OPML Management
|
||||
Section(header: Text("Accounts & Extensions"), footer: Text("Add, delete, enable, or disable accounts and extensions.")) {
|
||||
Section(header: Text("ACCOUNTS_EXTENSIONS_HEADER", tableName: "Settings"),
|
||||
footer: Text("ACCOUNTS_EXTENSIONS_FOOTER", tableName: "Settings")) {
|
||||
SettingsViewRows.addAccount
|
||||
SettingsViewRows.manageExtensions
|
||||
SettingsViewRows.importOPML(showImportActionSheet: $viewModel.showImportActionSheet)
|
||||
.confirmationDialog("Choose an account to receive the imported feeds and folders", isPresented: $viewModel.showImportActionSheet, titleVisibility: .visible) {
|
||||
.confirmationDialog(Text("IMPORT_OPML_CONFIRMATION", tableName: "Settings"),
|
||||
isPresented: $viewModel.showImportActionSheet,
|
||||
titleVisibility: .visible) {
|
||||
ForEach(AccountManager.shared.sortedActiveAccounts, id: \.self) { account in
|
||||
Button(account.nameForDisplay) {
|
||||
viewModel.importAccount = account
|
||||
@@ -41,8 +44,11 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsViewRows.exportOPML(showExportActionSheet: $viewModel.showExportActionSheet)
|
||||
.confirmationDialog("Choose an account with the subscriptions to export", isPresented: $viewModel.showExportActionSheet, titleVisibility: .visible) {
|
||||
.confirmationDialog(Text("EXPORT_OPML_CONFIRMATION", tableName: "Settings"),
|
||||
isPresented: $viewModel.showExportActionSheet,
|
||||
titleVisibility: .visible) {
|
||||
ForEach(AccountManager.shared.sortedAccounts, id: \.self) { account in
|
||||
Button(account.nameForDisplay) {
|
||||
do {
|
||||
@@ -59,7 +65,8 @@ struct SettingsView: View {
|
||||
}
|
||||
|
||||
// Appearance
|
||||
Section(header: Text("Appearance"), footer: Text("Manage the look, feel, and behavior of NetNewsWire.")) {
|
||||
Section(header: Text("APPEARANCE_HEADER", tableName: "Settings"),
|
||||
footer: Text("APPEARANCE_FOOTER", tableName: "Settings")) {
|
||||
SettingsViewRows.configureAppearance($isConfigureAppearanceShown)
|
||||
if viewModel.notificationPermissions == .authorized {
|
||||
SettingsViewRows.configureNewArticleNotifications
|
||||
@@ -76,10 +83,10 @@ struct SettingsView: View {
|
||||
}
|
||||
.tint(Color(uiColor: AppAssets.primaryAccentColor))
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle(Text("Settings"))
|
||||
.navigationTitle(Text("SETTINGS_TITLE", tableName: "Settings"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.sheet(isPresented: $viewModel.showAddAccountView) {
|
||||
AddAccountViewControllerRepresentable().edgesIgnoringSafeArea(.all)
|
||||
AddAccountView()
|
||||
}
|
||||
.sheet(isPresented: $viewModel.showHelpSheet) {
|
||||
SafariView(url: viewModel.helpSheet.url)
|
||||
@@ -130,21 +137,18 @@ struct SettingsView: View {
|
||||
viewModel.showImportExportError = true
|
||||
}
|
||||
})
|
||||
.alert("Imported Successfully", isPresented: $viewModel.showImportSuccess) {
|
||||
Button("Dismiss") {}
|
||||
} message: {
|
||||
Text("Import to your \(viewModel.importAccount?.nameForDisplay ?? "") account has completed.")
|
||||
}
|
||||
.alert("Exported Successfully", isPresented: $viewModel.showExportSuccess) {
|
||||
Button("Dismiss") {}
|
||||
} message: {
|
||||
Text("Your OPML file has been successfully exported.")
|
||||
}
|
||||
.alert("Error", isPresented: $viewModel.showImportExportError) {
|
||||
Button("Dismiss") {}
|
||||
} message: {
|
||||
Text(viewModel.importExportError?.localizedDescription ?? "Import/Export Error")
|
||||
}
|
||||
.alert(Text("IMPORT_OPML_SUCCESS_TITLE", tableName: "Settings"),
|
||||
isPresented: $viewModel.showImportSuccess,
|
||||
actions: {},
|
||||
message: { Text("IMPORT_OPML_SUCCESS_MESSAGE \(viewModel.importAccount?.nameForDisplay ?? "")", tableName: "Settings") })
|
||||
.alert(Text("EXPORT_OPML_SUCCESS_TITLE", tableName: "Settings"),
|
||||
isPresented: $viewModel.showExportSuccess,
|
||||
actions: {},
|
||||
message: { Text("EXPORT_OPML_SUCCESS_MESSAGE", tableName: "Settings") })
|
||||
.alert(Text("ERROR_TITLE", tableName: "Settings"),
|
||||
isPresented: $viewModel.showImportExportError,
|
||||
actions: {},
|
||||
message: { Text(viewModel.importExportError?.localizedDescription ?? "Import/Export Error") } )
|
||||
}.navigationViewStyle(.stack)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,21 @@ struct AboutView: View, LoadableAboutData {
|
||||
var body: some View {
|
||||
List {
|
||||
Section(header: aboutHeaderView) {}
|
||||
Section(header: Text("Primary Contributors")) {
|
||||
Section(header: Text("PRIMARY_CONTRIBUTORS", tableName: "Settings")) {
|
||||
ForEach(0..<about.PrimaryContributors.count, id: \.self) { i in
|
||||
contributorView(about.PrimaryContributors[i])
|
||||
}
|
||||
}
|
||||
Section(header: Text("Additional Contributors")) {
|
||||
Section(header: Text("ADDITIONAL_CONTRIBUTORS", tableName: "Settings")) {
|
||||
ForEach(0..<about.AdditionalContributors.count, id: \.self) { i in
|
||||
contributorView(about.AdditionalContributors[i])
|
||||
}
|
||||
}
|
||||
Section(header: Text("Thanks"), footer: thanks, content: {})
|
||||
Section(header: Text("THANKS", tableName: "Settings"), footer: thanks, content: {})
|
||||
Section(footer: copyright, content: {})
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle(Text("About"))
|
||||
.navigationTitle(Text("ABOUT", tableName: "Settings"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ struct AboutView: View, LoadableAboutData {
|
||||
.foregroundColor(.secondary)
|
||||
.font(.callout)
|
||||
|
||||
Text("By Brent Simmons and the Ranchero Software team.")
|
||||
Text("BYLINE", tableName: "Settings")
|
||||
.font(.subheadline)
|
||||
|
||||
Text("[netnewswire.com](https://netnewswire.com)")
|
||||
|
||||
@@ -16,9 +16,9 @@ public enum HelpSheet: CustomStringConvertible, CaseIterable {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .help:
|
||||
return NSLocalizedString("NetNewsWire Help", comment: "NetNewsWire Help")
|
||||
return NSLocalizedString("NETNEWSWIRE_HELP", tableName: "Settings", comment: "NetNewsWire Help")
|
||||
case .website:
|
||||
return NSLocalizedString("NetNewsWire Website", comment: "NetNewsWire Website")
|
||||
return NSLocalizedString("NETNEWSWIRE_WEBSITE", tableName: "Settings", comment: "NetNewsWire Website")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ struct NewArticleNotificationsView: View, Logging {
|
||||
|
||||
@State private var activeAccounts = AccountManager.shared.sortedActiveAccounts
|
||||
|
||||
|
||||
var body: some View {
|
||||
List(activeAccounts, id: \.accountID) { account in
|
||||
Section(header: Text(account.nameForDisplay)) {
|
||||
@@ -24,7 +23,7 @@ struct NewArticleNotificationsView: View, Logging {
|
||||
.id(feed.webFeedID)
|
||||
}
|
||||
}
|
||||
.navigationTitle(Text("New Article Notifications"))
|
||||
.navigationTitle(Text("NEW_ARTICLE_NOTIFICATIONS_TITLE", tableName: "Settings"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user