Article themes moved to SwiftUI

This commit is contained in:
Stuart Breckenridge
2022-12-20 20:35:18 +08:00
parent 432aeea1b5
commit 53e49aa699
29 changed files with 220 additions and 39 deletions

View File

@@ -871,6 +871,10 @@
DFB349A0294E87B700BC81AD /* LocalAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB3499F294E87B700BC81AD /* LocalAddAccountView.swift */; };
DFB349A2294E90B500BC81AD /* FeedbinAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB349A1294E90B500BC81AD /* FeedbinAddAccountView.swift */; };
DFB349A4294E914D00BC81AD /* AccountSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB349A3294E914D00BC81AD /* AccountSectionHeader.swift */; };
DFBB4EAC2951BC0200639228 /* NNWThemeDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */; };
DFBB4EAD2951BC0200639228 /* NNWThemeDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */; };
DFBB4EAE2951BC0200639228 /* NNWThemeDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */; };
DFBB4EB02951BCAC00639228 /* ArticleThemeManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFBB4EAF2951BCAC00639228 /* ArticleThemeManagerView.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 */; };
@@ -1633,6 +1637,8 @@
DFB3499F294E87B700BC81AD /* LocalAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAddAccountView.swift; sourceTree = "<group>"; };
DFB349A1294E90B500BC81AD /* FeedbinAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAddAccountView.swift; sourceTree = "<group>"; };
DFB349A3294E914D00BC81AD /* AccountSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSectionHeader.swift; sourceTree = "<group>"; };
DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NNWThemeDocument.swift; sourceTree = "<group>"; };
DFBB4EAF2951BCAC00639228 /* ArticleThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemeManagerView.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>"; };
@@ -1923,7 +1929,9 @@
5123DB95233EC69300282CC9 /* Inspector */ = {
isa = PBXGroup;
children = (
DFB3497D294B076C00BC81AD /* Views */,
DFB3497F294B085100BC81AD /* AccountInspectorView.swift */,
DFB34989294B45AC00BC81AD /* ExtensionInspectorView.swift */,
DFB3498B294B4CA700BC81AD /* WebFeedInspectorView.swift */,
);
path = Inspector;
sourceTree = "<group>";
@@ -1993,7 +2001,11 @@
516A093E236123A800EAE89B /* Account */ = {
isa = PBXGroup;
children = (
DFB3498F294C0B0D00BC81AD /* Views */,
DFB3499F294E87B700BC81AD /* LocalAddAccountView.swift */,
DFB3499D294C5D5000BC81AD /* CloudKitAddAccountView.swift */,
DFB349A1294E90B500BC81AD /* FeedbinAddAccountView.swift */,
DF28B44E294ED92F00C4D8CA /* NewsBlurAddAccountView.swift */,
DFB34990294C0B2200BC81AD /* ReaderAPIAddAccountView.swift */,
);
path = Account;
sourceTree = "<group>";
@@ -2023,7 +2035,11 @@
5183CCEB227117C70010922C /* Settings */ = {
isa = PBXGroup;
children = (
DFD406F8291FB5D500C02962 /* Views */,
DF766FEB2936344D006FBBE2 /* General */,
DFD406FD291FDBD900C02962 /* Appearance */,
DF59F0752920E42000ACD33D /* Account and Extensions */,
DF3630E92936038400326FB8 /* New Article Notifications */,
DF766FEA2936337A006FBBE2 /* Help */,
5137C2E926F63AE6009EFEDB /* ArticleThemeImporter.swift */,
510FFAB226EEA22C00F32265 /* ArticleThemesTableViewController.swift */,
51A16990235E10D600EB091F /* Settings.storyboard */,
@@ -2786,6 +2802,7 @@
849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */,
84A3EE52223B667F00557320 /* DefaultFeeds.opml */,
DF3630EA2936183D00326FB8 /* OPMLDocument.swift */,
DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */,
);
path = Importers;
sourceTree = "<group>";
@@ -2957,16 +2974,6 @@
path = Extensions;
sourceTree = "<group>";
};
DFB3497D294B076C00BC81AD /* Views */ = {
isa = PBXGroup;
children = (
DFB3497F294B085100BC81AD /* AccountInspectorView.swift */,
DFB34989294B45AC00BC81AD /* ExtensionInspectorView.swift */,
DFB3498B294B4CA700BC81AD /* WebFeedInspectorView.swift */,
);
path = Views;
sourceTree = "<group>";
};
DFB3497E294B07D900BC81AD /* Views */ = {
isa = PBXGroup;
children = (
@@ -2999,18 +3006,6 @@
path = "SwiftUI Extensions";
sourceTree = "<group>";
};
DFB3498F294C0B0D00BC81AD /* Views */ = {
isa = PBXGroup;
children = (
DFB3499F294E87B700BC81AD /* LocalAddAccountView.swift */,
DFB3499D294C5D5000BC81AD /* CloudKitAddAccountView.swift */,
DFB349A1294E90B500BC81AD /* FeedbinAddAccountView.swift */,
DF28B44E294ED92F00C4D8CA /* NewsBlurAddAccountView.swift */,
DFB34990294C0B2200BC81AD /* ReaderAPIAddAccountView.swift */,
);
path = Views;
sourceTree = "<group>";
};
DFC14F0928EA51AB00F6EE86 /* About */ = {
isa = PBXGroup;
children = (
@@ -3021,24 +3016,13 @@
path = About;
sourceTree = "<group>";
};
DFD406F8291FB5D500C02962 /* Views */ = {
isa = PBXGroup;
children = (
DF766FEB2936344D006FBBE2 /* General */,
DFD406FD291FDBD900C02962 /* Appearance */,
DF59F0752920E42000ACD33D /* Account and Extensions */,
DF3630E92936038400326FB8 /* New Article Notifications */,
DF766FEA2936337A006FBBE2 /* Help */,
);
path = Views;
sourceTree = "<group>";
};
DFD406FD291FDBD900C02962 /* Appearance */ = {
isa = PBXGroup;
children = (
DFD406FE291FDC0C00C02962 /* DisplayAndBehaviorsView.swift */,
DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */,
DF84E562295122BA0045C334 /* TimelineCustomizerView.swift */,
DFBB4EAF2951BCAC00639228 /* ArticleThemeManagerView.swift */,
);
path = Appearance;
sourceTree = "<group>";
@@ -4042,6 +4026,7 @@
65ED3FD5235DEF6C0081F399 /* SmartFeed.swift in Sources */,
51333D1724685D2E00EB5C91 /* AddRedditFeedWindowController.swift in Sources */,
65ED3FD6235DEF6C0081F399 /* MarkStatusCommand.swift in Sources */,
DFBB4EAD2951BC0200639228 /* NNWThemeDocument.swift in Sources */,
5183CFB0254C78C8006B83A5 /* EnableExtensionPointView.swift in Sources */,
65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */,
65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */,
@@ -4211,6 +4196,7 @@
518ED21D23D0F26000E0A862 /* UIViewController-Extensions.swift in Sources */,
DFD406FF291FDC0C00C02962 /* DisplayAndBehaviorsView.swift in Sources */,
51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */,
DFBB4EAE2951BC0200639228 /* NNWThemeDocument.swift in Sources */,
51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */,
51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */,
5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */,
@@ -4295,6 +4281,7 @@
51E4398023805EBC00015C31 /* AddComboTableViewCell.swift in Sources */,
51C4529A22650A0400C03939 /* ArticleTheme.swift in Sources */,
51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */,
DFBB4EB02951BCAC00639228 /* ArticleThemeManagerView.swift in Sources */,
51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */,
51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */,
512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */,
@@ -4490,6 +4477,7 @@
845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */,
515A5107243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */,
845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */,
DFBB4EAC2951BC0200639228 /* NNWThemeDocument.swift in Sources */,
DFC14F1228EA5DC500F6EE86 /* AboutData.swift in Sources */,
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */,
511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */,

View File

@@ -34,6 +34,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging, Observable
do {
currentTheme = try articleThemeWithThemeName(newValue)
AppDefaults.shared.currentThemeName = newValue
objectWillChange.send()
} catch {
logger.error("Unable to set new theme: \(error.localizedDescription, privacy: .public)")
}
@@ -51,12 +52,14 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging, Observable
}() {
didSet {
NotificationCenter.default.post(name: .CurrentArticleThemeDidChangeNotification, object: self)
objectWillChange.send()
}
}
lazy var themeNames = { buildThemeNames() }() {
didSet {
NotificationCenter.default.post(name: .ArticleThemeNamesDidChangeNotification, object: self)
objectWillChange.send()
}
}
@@ -102,6 +105,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging, Observable
}
try FileManager.default.copyItem(atPath: filename, toPath: toFilename)
objectWillChange.send()
}
func articleThemeWithThemeName(_ themeName: String) throws -> ArticleTheme {
@@ -127,6 +131,7 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging, Observable
func deleteTheme(themeName: String) {
if let filename = pathForThemeName(themeName, folder: folderPath) {
try? FileManager.default.removeItem(atPath: filename)
objectWillChange.send()
}
}

View File

@@ -0,0 +1,33 @@
//
// NNWThemeDocument.swift
// NetNewsWire
//
// Created by Stuart Breckenridge on 20/12/2022.
// Copyright © 2022 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
import UniformTypeIdentifiers
public struct NNWThemeDocument: FileDocument {
public static var readableContentTypes: [UTType] {
UTType.types(tag: "nnwtheme", tagClass: .filenameExtension, conformingTo: nil)
}
public static var writableContentTypes: [UTType] {
UTType.types(tag: "nnwtheme", tagClass: .filenameExtension, conformingTo: nil)
}
public init(configuration: ReadConfiguration) throws {
}
public func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let wrapper = try FileWrapper(url: URL(string: "")!)
return wrapper
}
}

View File

@@ -17,3 +17,5 @@
"ENABLE_EXTENSION_BUTTON_TITLE" = "Enable Extension";
"UPDATE_CREDENTIALS_BUTTON_TITLE" = "Update Credentials";
"USE_CLOUDKIT_BUTTON_TITLE" = "Use iCloud";
"DELETE_THEME_BUTTON_TITLE" = "Delete Theme";
"IMPORT_THEME_BUTTON_TITLE" = "Import Theme";

View File

@@ -43,7 +43,15 @@
"TIMELINE_LAYOUT" = "Timeline Layout";
"ICON_SIZE" = "Icon Size";
"NUMBER_OF_LINES" = "Number of Lines";
"INSTALLED_THEMES" = "Installed Themes";
"ARTICLE_THEMES_TITLE" = "Article Themes";
"ARTICLE_THEME" = "Article Theme";
"IMPORT_THEME_CONFIRMATION_TITLE" = "Import Theme";
"IMPORT_THEME_CONFIRMATION_MESSAGE_%@_%@" = "Are you sure you want to import “%@” by %@?";
"IMPORT_THEME_SUCCESS_TITLE" = "Import Successful";
"IMPORT_THEME_SUCCESS_MESSAGE_%@" = "“%@” has been imported successfully.";
"DELETE_THEME_ALERT_TITLE_%@" = "Are you sure you want to delete “%@”?";
"DELETE_THEME_ALERT_MESSAGE" = "Are you sure you want to delete this theme? This action is not reversible.";
"CONFIRM_MARK_ALL_AS_READ" = "Confirm Mark All as Read";
"OPEN_LINKS_IN_APP" = "Open Links in NetNewsWire";
"SMALL_ICON_SIZE" = "Small";

View File

@@ -17,7 +17,7 @@ struct ExtensionInspectorView: View {
var body: some View {
Form {
Section(header: extensionHeader) {}
Section(footer: Text(extensionPoint?.description.string ?? ""), content: {
Section(footer: extensionExplainer, content: {
//
})
@@ -65,6 +65,11 @@ struct ExtensionInspectorView: View {
Spacer()
}
}
var extensionExplainer: some View {
Text(extensionPoint?.description.string ?? "")
.multilineTextAlignment(.center)
}
}
struct ExtensionInspectorView_Previews: PreviewProvider {

View File

@@ -0,0 +1,140 @@
//
// ArticleThemeManagerView.swift
// NetNewsWire-iOS
//
// Created by Stuart Breckenridge on 20/12/2022.
// Copyright © 2022 Ranchero Software. All rights reserved.
//
import SwiftUI
struct ArticleThemeManagerView: View {
@StateObject private var themeManager = ArticleThemesManager.shared
@State private var showDeleteConfirmation: (String, Bool) = ("", false)
@State private var showImportThemeView: Bool = false
@State private var showImportConfirmationAlert: (ArticleTheme?, Bool) = (nil, false)
@State private var showImportErrorAlert: (Error?, Bool) = (nil, false)
@State private var showImportSuccessAlert: Bool = false
var body: some View {
Form {
Section(header: Text("INSTALLED_THEMES", tableName: "Settings")) {
articleThemeRow("Default")
ForEach(themeManager.themeNames, id: \.self) {theme in
articleThemeRow(theme)
}
}
}
.navigationTitle(Text("ARTICLE_THEMES_TITLE", tableName: "Settings"))
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
showImportThemeView = true
} label: {
Label {
Text("IMPORT_THEME_BUTTON_TITLE", tableName: "Buttons")
} icon: {
Image(systemName: "plus")
}
}
}
}
.fileImporter(isPresented: $showImportThemeView, allowedContentTypes: NNWThemeDocument.readableContentTypes) { result in
switch result {
case .success(let success):
do {
let theme = try ArticleTheme(path: success.path, isAppTheme: true)
showImportConfirmationAlert = (theme, true)
} catch {
showImportErrorAlert = (error, true)
}
case .failure(let failure):
showImportErrorAlert = (failure, true)
}
}
.alert(Text("DELETE_THEME_ALERT_TITLE_\(showDeleteConfirmation.0)", tableName: "Settings"), isPresented: $showDeleteConfirmation.1, actions: {
Button(role: .destructive) {
ArticleThemesManager.shared.deleteTheme(themeName: showDeleteConfirmation.0)
} label: {
Text("DELETE_THEME_BUTTON_TITLE", tableName: "Buttons")
}
Button(role: .cancel) {
} label: {
Text("CANCEL_BUTTON_TITLE", tableName: "Buttons")
}
}, message: {
Text("DELETE_THEME_ALERT_MESSAGE", tableName: "Settings")
})
.alert(Text("IMPORT_THEME_CONFIRMATION_TITLE", tableName: "Settings"),
isPresented: $showImportConfirmationAlert.1,
actions: {
Button {
do {
try ArticleThemesManager.shared.importTheme(filename: showImportConfirmationAlert.0!.path!)
showImportSuccessAlert = true
} catch {
showImportErrorAlert = (error, true)
}
} label: {
Text("IMPORT_THEME_BUTTON_TITLE", tableName: "Buttons")
}
Button(role: .cancel) {
} label: {
Text("CANCEL_BUTTON_TITLE", tableName: "Buttons")
}
}, message: {
Text("IMPORT_THEME_CONFIRMATION_MESSAGE_\(showImportConfirmationAlert.0?.name ?? "")_\(showImportConfirmationAlert.0?.creatorName ?? "")", tableName: "Settings")
})
.alert(Text("IMPORT_THEME_SUCCESS_TITLE", tableName: "Settings"),
isPresented: $showImportSuccessAlert,
actions: {
Button(role: .cancel) {
} label: {
Text("DISMISS_BUTTON_TITLE", tableName: "Buttons")
}
}, message: {
Text("IMPORT_THEME_SUCCESS_MESSAGE_\(showImportConfirmationAlert.0?.name ?? "")", tableName: "Settings")
})
}
func articleThemeRow(_ theme: String) -> some View {
Button {
ArticleThemesManager.shared.currentThemeName = theme
} label: {
HStack {
Text(theme)
.foregroundColor(.primary)
Spacer()
if ArticleThemesManager.shared.currentThemeName == theme {
Image(systemName: "checkmark")
.foregroundColor(Color(uiColor: AppAssets.primaryAccentColor))
}
}
}
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
if theme == "Default" || ArticleThemesManager.shared.currentThemeName == theme { }
else {
Button {
showDeleteConfirmation = (theme, true)
} label: {
Text("DELETE_BUTTON_TITLE", tableName: "Buttons")
Image(systemName: "trash")
}
.tint(.red)
}
}
}
}
struct ArticleThemeImporterView_Previews: PreviewProvider {
static var previews: some View {
ArticleThemeManagerView()
}
}

View File

@@ -154,7 +154,7 @@ struct SettingsViewRows {
/// This row, when tapped, will push the the Theme Selector screen
/// in to view.
static var themeSelection: some View {
NavigationLink(destination: ArticleThemesWrapper().edgesIgnoringSafeArea(.all)) {
NavigationLink(destination: ArticleThemeManagerView()) {
HStack {
Text("ARTICLE_THEME", tableName: "Settings")
Spacer()