diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index 3c0eaf09d..df9b7c618 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -16,6 +16,8 @@ enum FontSize: Int { final class AppDefaults { + static let defaultThemeName = "Default" + static var shared = AppDefaults() private init() {} @@ -39,6 +41,7 @@ final class AppDefaults { static let importOPMLAccountID = "importOPMLAccountID" static let exportOPMLAccountID = "exportOPMLAccountID" static let defaultBrowserID = "defaultBrowserID" + static let currentThemeName = "currentThemeName" // Hidden prefs static let showDebugMenu = "ShowDebugMenu" @@ -209,6 +212,15 @@ final class AppDefaults { } } + var currentThemeName: String? { + get { + return AppDefaults.string(for: Key.currentThemeName) + } + set { + AppDefaults.setString(for: Key.currentThemeName, newValue) + } + } + var showTitleOnMainWindow: Bool { return AppDefaults.bool(for: Key.showTitleOnMainWindow) } @@ -311,7 +323,8 @@ final class AppDefaults { Key.timelineGroupByFeed: false, "NSScrollViewShouldScrollUnderTitlebar": false, Key.refreshInterval: RefreshInterval.everyHour.rawValue, - Key.showDebugMenu: showDebugMenu] + Key.showDebugMenu: showDebugMenu, + Key.currentThemeName: Self.defaultThemeName] UserDefaults.standard.register(defaults: defaults) diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index a3a50f195..998d553c7 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -120,7 +120,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, SecretsManager.provider = Secrets() AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!) - ArticleStylesManager.shared = ArticleStylesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Styles")!) + 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) diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index e6ccb7b69..5b686241e 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -257,22 +257,22 @@ private extension DetailWebViewController { func reloadHTML() { delegate?.mouseDidExit(self) - let style = ArticleStylesManager.shared.currentStyle + let theme = ArticleThemesManager.shared.currentTheme let rendering: ArticleRenderer.Rendering switch state { case .noSelection: - rendering = ArticleRenderer.noSelectionHTML(style: style) + rendering = ArticleRenderer.noSelectionHTML(theme: theme) case .multipleSelection: - rendering = ArticleRenderer.multipleSelectionHTML(style: style) + rendering = ArticleRenderer.multipleSelectionHTML(theme: theme) case .loading: - rendering = ArticleRenderer.loadingHTML(style: style) + rendering = ArticleRenderer.loadingHTML(theme: theme) case .article(let article): detailIconSchemeHandler.currentArticle = article - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) case .extracted(let article, let extractedArticle): detailIconSchemeHandler.currentArticle = article - rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) + rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme) } let substitutions = [ diff --git a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift index 908eed74b..ee8e2bf1c 100644 --- a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift +++ b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift @@ -25,7 +25,7 @@ extension Article: PasteboardWriterOwner { static let articleUTIInternalType = NSPasteboard.PasteboardType(rawValue: articleUTIInternal) private lazy var renderedHTML: String = { - let rendering = ArticleRenderer.articleHTML(article: article, style: ArticleStylesManager.shared.currentStyle) + let rendering = ArticleRenderer.articleHTML(article: article, theme: ArticleThemesManager.shared.currentTheme) return rendering.html }() diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift index 861036520..51261efaa 100644 --- a/Multiplatform/Shared/AppDefaults.swift +++ b/Multiplatform/Shared/AppDefaults.swift @@ -28,6 +28,8 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { final class AppDefaults: ObservableObject { + static let defaultThemeName = "Default" + #if os(macOS) static let store: UserDefaults = UserDefaults.standard #endif @@ -61,7 +63,8 @@ final class AppDefaults: ObservableObject { static let timelineGroupByFeed = "timelineGroupByFeed" static let timelineIconDimensions = "timelineIconDimensions" static let timelineNumberOfLines = "timelineNumberOfLines" - + static let currentThemeName = "currentThemeName" + // Sidebar Defaults static let sidebarConfirmDelete = "sidebarConfirmDelete" @@ -242,6 +245,8 @@ final class AppDefaults: ObservableObject { var articleTextSize: ArticleTextSize { ArticleTextSize(rawValue: articleTextSizeTag) ?? ArticleTextSize.large } + + @AppStorage(Key.currentThemeName, store: store) var currentThemeName: String? // MARK: Refresh var lastRefresh: Date? { @@ -334,7 +339,8 @@ final class AppDefaults: ObservableObject { Key.articleFullscreenEnabled: false, Key.confirmMarkAllAsRead: true, "NSScrollViewShouldScrollUnderTitlebar": false, - Key.refreshInterval: RefreshInterval.everyHour.rawValue] + Key.refreshInterval: RefreshInterval.everyHour.rawValue, + Key.currentThemeName: Self.defaultThemeName] AppDefaults.store.register(defaults: defaults) } diff --git a/Multiplatform/iOS/AppDelegate.swift b/Multiplatform/iOS/AppDelegate.swift index 864043edf..3b3c5463b 100644 --- a/Multiplatform/iOS/AppDelegate.swift +++ b/Multiplatform/iOS/AppDelegate.swift @@ -62,10 +62,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD appDelegate = self SecretsManager.provider = Secrets() - let documentAccountURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! - let documentAccountsFolder = documentAccountURL.appendingPathComponent("Accounts").absoluteString + + 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 documentStylesFolder = documentFolder.appendingPathComponent("Themes").absoluteString + let documentStylesFolderPath = String(documentStylesFolder.suffix(from: documentAccountsFolder.index(documentStylesFolder.startIndex, offsetBy: 7))) + ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentStylesFolderPath) + FeedProviderManager.shared.delegate = ExtensionPointManager.shared NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) diff --git a/Multiplatform/iOS/Article/WebViewController.swift b/Multiplatform/iOS/Article/WebViewController.swift index 5e5364d54..15a72f3f5 100644 --- a/Multiplatform/iOS/Article/WebViewController.swift +++ b/Multiplatform/iOS/Article/WebViewController.swift @@ -483,23 +483,23 @@ private extension WebViewController { func renderPage(_ webView: PreloadedWebView?) { guard let webView = webView else { return } - let style = ArticleStylesManager.shared.currentStyle + let theme = ArticleThemesManager.shared.currentTheme let rendering: ArticleRenderer.Rendering if let articleExtractor = articleExtractor, articleExtractor.state == .processing { - rendering = ArticleRenderer.loadingHTML(style: style) + rendering = ArticleRenderer.loadingHTML(theme: theme) } else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = article { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) } else if let article = article, let extractedArticle = extractedArticle { if isShowingExtractedArticle { - rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) + rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme) } else { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) } } else if let article = article { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) } else { - rendering = ArticleRenderer.noSelectionHTML(style: style) + rendering = ArticleRenderer.noSelectionHTML(theme: theme) } let substitutions = [ diff --git a/Multiplatform/macOS/AppDelegate.swift b/Multiplatform/macOS/AppDelegate.swift index 1174c3fdf..8a41559df 100644 --- a/Multiplatform/macOS/AppDelegate.swift +++ b/Multiplatform/macOS/AppDelegate.swift @@ -72,6 +72,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele 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) diff --git a/Multiplatform/macOS/Article/WebViewController.swift b/Multiplatform/macOS/Article/WebViewController.swift index e71c7ef1d..0a9c9c3ff 100644 --- a/Multiplatform/macOS/Article/WebViewController.swift +++ b/Multiplatform/macOS/Article/WebViewController.swift @@ -261,25 +261,25 @@ private extension WebViewController { } func renderPage(_ webView: PreloadedWebView) { - let style = ArticleStylesManager.shared.currentStyle + let theme = ArticleThemesManager.shared.currentTheme let rendering: ArticleRenderer.Rendering if articles?.count ?? 0 > 1 { - rendering = ArticleRenderer.multipleSelectionHTML(style: style) + rendering = ArticleRenderer.multipleSelectionHTML(theme: theme) } else if let articleExtractor = articleExtractor, articleExtractor.state == .processing { - rendering = ArticleRenderer.loadingHTML(style: style) + rendering = ArticleRenderer.loadingHTML(theme: theme) } else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = articles?.first { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + 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, style: style) + rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme) } else { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) } } else if let article = articles?.first { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) } else { - rendering = ArticleRenderer.noSelectionHTML(style: style) + rendering = ArticleRenderer.noSelectionHTML(theme: theme) } let substitutions = [ diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index d225fb800..c415fbd29 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -565,8 +565,8 @@ 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; 51C45296226509D300C03939 /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; }; 51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; - 51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; - 51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; + 51C4529922650A0000C03939 /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; }; + 51C4529A22650A0400C03939 /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; }; 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; @@ -736,10 +736,10 @@ 51E4996324A875F400B667CB /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; 51E4996424A875F400B667CB /* shared.css in Resources */ = {isa = PBXBuildFile; fileRef = B27EEBDF244D15F2000932E6 /* shared.css */; }; 51E4996524A875F400B667CB /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; - 51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; - 51E4996724A8760B00B667CB /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; - 51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; - 51E4996924A8760C00B667CB /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; + 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 */; }; @@ -904,14 +904,14 @@ 65ED3FE5235DEF6C0081F399 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; 65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */; }; 65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; - 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; + 65ED3FE8235DEF6C0081F399 /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; }; 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; }; 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */; }; - 65ED3FF0235DEF6C0081F399 /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; + 65ED3FF0235DEF6C0081F399 /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; }; 65ED3FF1235DEF6C0081F399 /* DetailContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD892213E0E3008CE1BF /* DetailContainerView.swift */; }; 65ED3FF2235DEF6C0081F399 /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 65ED3FF3235DEF6C0081F399 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; @@ -1101,8 +1101,8 @@ 849A977F1ED9EC42007D329B /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977E1ED9EC42007D329B /* DetailViewController.swift */; }; 849A97831ED9EC63007D329B /* SidebarStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */; }; - 849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; - 849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; + 849A97891ED9ECEF007D329B /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; }; + 849A978A1ED9ECEF007D329B /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; }; 849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; }; 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; @@ -2021,8 +2021,8 @@ 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleRenderer.swift; sourceTree = ""; }; 849A977E1ED9EC42007D329B /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarStatusBarView.swift; sourceTree = ""; }; - 849A97871ED9ECEF007D329B /* ArticleStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleStyle.swift; sourceTree = ""; }; - 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleStylesManager.swift; sourceTree = ""; }; + 849A97871ED9ECEF007D329B /* ArticleTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleTheme.swift; sourceTree = ""; }; + 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleThemesManager.swift; sourceTree = ""; }; 849A97971ED9EFAA007D329B /* Node-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Node-Extensions.swift"; sourceTree = ""; }; 849A979E1ED9F130007D329B /* SidebarCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarCell.swift; sourceTree = ""; }; 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderTreeControllerDelegate.swift; sourceTree = ""; }; @@ -3437,8 +3437,8 @@ 849A97861ED9ECEF007D329B /* Article Styles */ = { isa = PBXGroup; children = ( - 849A97871ED9ECEF007D329B /* ArticleStyle.swift */, - 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */, + 849A97871ED9ECEF007D329B /* ArticleTheme.swift */, + 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */, ); name = "Article Styles"; path = Shared/ArticleStyles; @@ -4961,7 +4961,7 @@ 51E4991724A8090400B667CB /* ArticleUtilities.swift in Sources */, 51E4991B24A8091000B667CB /* IconImage.swift in Sources */, 51E4995424A8734D00B667CB /* ExtensionPointIdentifer.swift in Sources */, - 51E4996924A8760C00B667CB /* ArticleStylesManager.swift in Sources */, + 51E4996924A8760C00B667CB /* ArticleThemesManager.swift in Sources */, 5177471E24B387E100EB0F74 /* ImageTransition.swift in Sources */, 51E498F324A8085D00B667CB /* PseudoFeed.swift in Sources */, 172412A5257BC01C00ACCEBC /* AddCloudKitAccountView.swift in Sources */, @@ -5005,7 +5005,7 @@ 172412AF257BC0C300ACCEBC /* AccountType+Helpers.swift in Sources */, 51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, 5125E6CA24AE461D002A7562 /* TimelineLayoutView.swift in Sources */, - 51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */, + 51E4996824A8760C00B667CB /* ArticleTheme.swift in Sources */, 51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */, 51E4997124A8764C00B667CB /* ActivityType.swift in Sources */, 51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, @@ -5096,7 +5096,7 @@ 51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */, 1769E33624BD9621000E1E8E /* EditAccountCredentialsModel.swift in Sources */, 51919FEF24AB85E400541E64 /* TimelineContainerView.swift in Sources */, - 51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */, + 51E4996624A8760B00B667CB /* ArticleTheme.swift in Sources */, 5171B4D424B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */, 17E4DBD624BFC53E00FE462A /* AdvancedPreferencesModel.swift in Sources */, 5177470724B2910300EB0F74 /* ArticleToolbarModifier.swift in Sources */, @@ -5180,7 +5180,7 @@ 51E498FB24A808BA00B667CB /* FaviconGenerator.swift in Sources */, 17D5F19524B0C1DD00375168 /* SidebarToolbarModifier.swift in Sources */, 17241288257BBF7000ACCEBC /* AddNewsBlurViewModel.swift in Sources */, - 51E4996724A8760B00B667CB /* ArticleStylesManager.swift in Sources */, + 51E4996724A8760B00B667CB /* ArticleThemesManager.swift in Sources */, 1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */, 51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */, 514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */, @@ -5320,7 +5320,7 @@ 5193CD59245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */, 65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */, 65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */, - 65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */, + 65ED3FE8235DEF6C0081F399 /* ArticleTheme.swift in Sources */, 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 6538131C2680E17F007A082C /* UserInfoKey.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, @@ -5331,7 +5331,7 @@ 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, 653813182680E152007A082C /* AccountType+Helpers.swift in Sources */, 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */, - 65ED3FF0235DEF6C0081F399 /* ArticleStylesManager.swift in Sources */, + 65ED3FF0235DEF6C0081F399 /* ArticleThemesManager.swift in Sources */, 65ED3FF1235DEF6C0081F399 /* DetailContainerView.swift in Sources */, 65ED3FF2235DEF6C0081F399 /* SharingServiceDelegate.swift in Sources */, 515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */, @@ -5532,14 +5532,14 @@ 51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */, 51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */, 51E4398023805EBC00015C31 /* AddComboTableViewCell.swift in Sources */, - 51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */, + 51C4529A22650A0400C03939 /* ArticleTheme.swift in Sources */, 51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */, 51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */, 51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */, 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */, 51A9A60A2382FD240033AADF /* PoppableGestureRecognizerDelegate.swift in Sources */, 51DC079A2552083500A3F79F /* ArticleTextSize.swift in Sources */, - 51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */, + 51C4529922650A0000C03939 /* ArticleThemesManager.swift in Sources */, 51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */, 51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */, 51C452AF2265108300C03939 /* ArticleArray.swift in Sources */, @@ -5690,7 +5690,7 @@ 84A37CB5201ECD610087C5AF /* RenameWindowController.swift in Sources */, 84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */, B2B8075E239C49D300F191E0 /* RSImage-AppIcons.swift in Sources */, - 849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */, + 849A97891ED9ECEF007D329B /* ArticleTheme.swift in Sources */, 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */, 84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */, 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */, @@ -5698,7 +5698,7 @@ 51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */, D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */, 51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */, - 849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */, + 849A978A1ED9ECEF007D329B /* ArticleThemesManager.swift in Sources */, 8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */, 51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */, 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */, diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 85fc0db30..95728676a 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -37,15 +37,15 @@ struct ArticleRenderer { private let article: Article? private let extractedArticle: ExtractedArticle? - private let articleStyle: ArticleStyle + private let articleTheme: ArticleTheme private let title: String private let body: String private let baseURL: String? - private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle) { + private init(article: Article?, extractedArticle: ExtractedArticle?, theme: ArticleTheme) { self.article = article self.extractedArticle = extractedArticle - self.articleStyle = style + self.articleTheme = theme self.title = article?.sanitizedTitle() ?? "" if let content = extractedArticle?.content { self.body = content @@ -58,28 +58,28 @@ struct ArticleRenderer { // MARK: - API - static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle) -> Rendering { - let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style) + static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, theme: ArticleTheme) -> Rendering { + let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, theme: theme) return (renderer.articleCSS, renderer.articleHTML, renderer.title, renderer.baseURL ?? "") } - static func multipleSelectionHTML(style: ArticleStyle) -> Rendering { - let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) + static func multipleSelectionHTML(theme: ArticleTheme) -> Rendering { + let renderer = ArticleRenderer(article: nil, extractedArticle: nil, theme: theme) return (renderer.articleCSS, renderer.multipleSelectionHTML, renderer.title, renderer.baseURL ?? "") } - static func loadingHTML(style: ArticleStyle) -> Rendering { - let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) + static func loadingHTML(theme: ArticleTheme) -> Rendering { + let renderer = ArticleRenderer(article: nil, extractedArticle: nil, theme: theme) return (renderer.articleCSS, renderer.loadingHTML, renderer.title, renderer.baseURL ?? "") } - static func noSelectionHTML(style: ArticleStyle) -> Rendering { - let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) + static func noSelectionHTML(theme: ArticleTheme) -> Rendering { + let renderer = ArticleRenderer(article: nil, extractedArticle: nil, theme: theme) return (renderer.articleCSS, renderer.noSelectionHTML, renderer.title, renderer.baseURL ?? "") } - static func noContentHTML(style: ArticleStyle) -> Rendering { - let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) + static func noContentHTML(theme: ArticleTheme) -> Rendering { + let renderer = ArticleRenderer(article: nil, extractedArticle: nil, theme: theme) return (renderer.articleCSS, renderer.noContentHTML, renderer.title, renderer.baseURL ?? "") } } @@ -128,11 +128,11 @@ private extension ArticleRenderer { }() func styleString() -> String { - return articleStyle.css ?? ArticleRenderer.defaultStyleSheet + return articleTheme.css ?? ArticleRenderer.defaultStyleSheet } func template() -> String { - return articleStyle.template ?? ArticleRenderer.defaultTemplate + return articleTheme.template ?? ArticleRenderer.defaultTemplate } func titleOrTitleLink() -> String { diff --git a/Shared/ArticleStyles/ArticleStylesManager.swift b/Shared/ArticleStyles/ArticleStylesManager.swift deleted file mode 100644 index 06ff1a64e..000000000 --- a/Shared/ArticleStyles/ArticleStylesManager.swift +++ /dev/null @@ -1,187 +0,0 @@ -// -// ArticleStylesManager.sqift -// NetNewsWire -// -// Created by Brent Simmons on 9/26/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -#if os(macOS) -import AppKit -#else -import UIKit -#endif - -import RSCore - -let ArticleStyleNamesDidChangeNotification = "ArticleStyleNamesDidChangeNotification" -let CurrentArticleStyleDidChangeNotification = "CurrentArticleStyleDidChangeNotification" - -private let styleKey = "style" -private let defaultStyleName = "Default" -private let stylesInResourcesFolderName = "Styles" -private let styleSuffix = ".netnewswirestyle" -private let nnwStyleSuffix = ".nnwstyle" -private let cssStyleSuffix = ".css" -private let styleSuffixes = [styleSuffix, nnwStyleSuffix, cssStyleSuffix]; - -final class ArticleStylesManager { - - static var shared: ArticleStylesManager! - private let folderPath: String - - var currentStyleName: String { - get { - return UserDefaults.standard.string(forKey: styleKey)! - } - set { - if newValue != currentStyleName { - UserDefaults.standard.set(newValue, forKey: styleKey) - } - } - } - - var currentStyle: ArticleStyle { - didSet { - NotificationCenter.default.post(name: Notification.Name(rawValue: CurrentArticleStyleDidChangeNotification), object: self) - } - } - - var styleNames = [defaultStyleName] { - didSet { - NotificationCenter.default.post(name: Notification.Name(rawValue: ArticleStyleNamesDidChangeNotification), object: self) - } - } - - init(folderPath: String) { - self.folderPath = folderPath - - do { - try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil) - } catch { - assertionFailure("Could not create folder for Styles.") - abort() - } - - UserDefaults.standard.register(defaults: [styleKey: defaultStyleName]) - currentStyle = ArticleStyle.defaultStyle - - updateStyleNames() - updateCurrentStyle() - - #if os(macOS) - NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSApplication.didBecomeActiveNotification, object: nil) - #else - NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil) - #endif - } - - // MARK: Notifications - - @objc dynamic func applicationDidBecomeActive(_ note: Notification) { - - updateStyleNames() - updateCurrentStyle() - } - - // MARK : Internal - - private func updateStyleNames() { - - let updatedStyleNames = allStylePaths(folderPath).map { styleNameForPath($0) } - - if updatedStyleNames != styleNames { - styleNames = updatedStyleNames - } - } - - private func articleStyleWithStyleName(_ styleName: String) -> ArticleStyle? { - - if styleName == defaultStyleName { - return ArticleStyle.defaultStyle - } - - guard let path = pathForStyleName(styleName, folder: folderPath) else { - return nil - } - - return ArticleStyle(path: path) - } - - private func defaultArticleStyle() -> ArticleStyle { - - return articleStyleWithStyleName(defaultStyleName)! - } - - private func updateCurrentStyle() { - - var styleName = currentStyleName - if !styleNames.contains(styleName) { - styleName = defaultStyleName - currentStyleName = defaultStyleName - } - - var articleStyle = articleStyleWithStyleName(styleName) - if articleStyle == nil { - articleStyle = defaultArticleStyle() - currentStyleName = defaultStyleName - } - - if let articleStyle = articleStyle, articleStyle != currentStyle { - currentStyle = articleStyle - } - } -} - - -private func allStylePaths(_ folder: String) -> [String] { - - let filepaths = FileManager.default.filePaths(inFolder: folder) - return filepaths?.filter { fileAtPathIsStyle($0) } ?? [] -} - -private func fileAtPathIsStyle(_ f: String) -> Bool { - - if !f.hasSuffix(styleSuffix) && !f.hasSuffix(nnwStyleSuffix) && !f.hasSuffix(cssStyleSuffix) { - return false - } - - if (f as NSString).lastPathComponent.hasPrefix(".") { - return false - } - - return true -} - -private func filenameWithStyleSuffixRemoved(_ filename: String) -> String { - - for oneSuffix in styleSuffixes { - if filename.hasSuffix(oneSuffix) { - return filename.stripping(suffix: oneSuffix) - } - } - - return filename -} - -private func styleNameForPath(_ f: String) -> String { - - let filename = (f as NSString).lastPathComponent - return filenameWithStyleSuffixRemoved(filename) -} - -private func pathIsPathForStyleName(_ styleName: String, path: String) -> Bool { - - let filename = (path as NSString).lastPathComponent - return filenameWithStyleSuffixRemoved(filename) == styleName -} - -private func pathForStyleName(_ styleName: String, folder: String) -> String? { - for onePath in allStylePaths(folder) { - if pathIsPathForStyleName(styleName, path: onePath) { - return onePath - } - } - - return nil -} diff --git a/Shared/ArticleStyles/ArticleStyle.swift b/Shared/ArticleStyles/ArticleTheme.swift similarity index 82% rename from Shared/ArticleStyles/ArticleStyle.swift rename to Shared/ArticleStyles/ArticleTheme.swift index 97b9a3030..efd87ad80 100644 --- a/Shared/ArticleStyles/ArticleStyle.swift +++ b/Shared/ArticleStyles/ArticleTheme.swift @@ -1,5 +1,5 @@ // -// ArticleStyle.swift +// ArticleTheme.swift // NetNewsWire // // Created by Brent Simmons on 9/26/15. @@ -8,21 +8,18 @@ import Foundation -struct ArticleStyle: Equatable { +struct ArticleTheme: Equatable { - static let defaultStyle = ArticleStyle() + static let defaultTheme = ArticleTheme() let path: String? let template: String? let css: String? - let emptyCSS: String? + let systemMessageCSS: String? let info: NSDictionary? init() { - - //Default style - self.path = nil; - self.emptyCSS = nil + self.systemMessageCSS = nil self.info = ["CreatorHomePage": "https://netnewswire.com/", "CreatorName": "Ranchero Software", "Version": "1.0"] @@ -53,8 +50,8 @@ struct ArticleStyle: Equatable { let cssPath = (path as NSString).appendingPathComponent("stylesheet.css") self.css = stringAtPath(cssPath) - let emptyCSSPath = (path as NSString).appendingPathComponent("stylesheet_empty.css") - self.emptyCSS = stringAtPath(emptyCSSPath) + let systemMessageCSSPath = (path as NSString).appendingPathComponent("system_message_stylesheet.css") + self.systemMessageCSS = stringAtPath(systemMessageCSSPath) let templatePath = (path as NSString).appendingPathComponent("template.html") self.template = stringAtPath(templatePath) @@ -64,7 +61,7 @@ struct ArticleStyle: Equatable { self.css = stringAtPath(path) self.template = nil - self.emptyCSS = nil + self.systemMessageCSS = nil self.info = nil } } diff --git a/Shared/ArticleStyles/ArticleThemesManager.swift b/Shared/ArticleStyles/ArticleThemesManager.swift new file mode 100644 index 000000000..1d2e317b2 --- /dev/null +++ b/Shared/ArticleStyles/ArticleThemesManager.swift @@ -0,0 +1,153 @@ +// +// ArticleThemesManager.sqift +// NetNewsWire +// +// Created by Brent Simmons on 9/26/15. +// Copyright © 2015 Ranchero Software, LLC. All rights reserved. +// + +#if os(macOS) +import AppKit +#else +import UIKit +#endif + +import RSCore + +let ArticleThemeNamesDidChangeNotification = "ArticleThemeNamesDidChangeNotification" +let CurrentArticleThemeDidChangeNotification = "CurrentArticleThemeDidChangeNotification" + +private let themesInResourcesFolderName = "Themes" +private let nnwThemeSuffix = ".nnwtheme" + +final class ArticleThemesManager { + + static var shared: ArticleThemesManager! + private let folderPath: String + + var currentThemeName: String { + get { + return AppDefaults.shared.currentThemeName ?? AppDefaults.defaultThemeName + } + set { + if newValue != currentThemeName { + AppDefaults.shared.currentThemeName = newValue + } + } + } + + var currentTheme: ArticleTheme { + didSet { + NotificationCenter.default.post(name: Notification.Name(rawValue: CurrentArticleThemeDidChangeNotification), object: self) + } + } + + var themeNames = [AppDefaults.defaultThemeName] { + didSet { + NotificationCenter.default.post(name: Notification.Name(rawValue: ArticleThemeNamesDidChangeNotification), object: self) + } + } + + init(folderPath: String) { + self.folderPath = folderPath + + do { + try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil) + } catch { + assertionFailure("Could not create folder for Themes.") + abort() + } + + currentTheme = ArticleTheme.defaultTheme + + updateThemeNames() + updateCurrentTheme() + + #if os(macOS) + NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSApplication.didBecomeActiveNotification, object: nil) + #else + NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil) + #endif + } + + // MARK: Notifications + + @objc dynamic func applicationDidBecomeActive(_ note: Notification) { + updateThemeNames() + updateCurrentTheme() + } + + // MARK : Internal + + private func updateThemeNames() { + let updatedThemeNames = allThemePaths(folderPath).map { themeNameForPath($0) } + + if updatedThemeNames != themeNames { + themeNames = updatedThemeNames + } + } + + private func articleThemeWithThemeName(_ themeName: String) -> ArticleTheme? { + if themeName == AppDefaults.defaultThemeName { + return ArticleTheme.defaultTheme + } + + guard let path = pathForThemeName(themeName, folder: folderPath) else { + return nil + } + + return ArticleTheme(path: path) + } + + private func defaultArticleTheme() -> ArticleTheme { + return articleThemeWithThemeName(AppDefaults.defaultThemeName)! + } + + private func updateCurrentTheme() { + + var themeName = currentThemeName + if !themeNames.contains(themeName) { + themeName = AppDefaults.defaultThemeName + currentThemeName = AppDefaults.defaultThemeName + } + + var articleTheme = articleThemeWithThemeName(themeName) + if articleTheme == nil { + articleTheme = defaultArticleTheme() + currentThemeName = AppDefaults.defaultThemeName + } + + if let articleTheme = articleTheme, articleTheme != currentTheme { + currentTheme = articleTheme + } + } +} + + +private func allThemePaths(_ folder: String) -> [String] { + let filepaths = FileManager.default.filePaths(inFolder: folder) + return filepaths?.filter { $0.hasSuffix(nnwThemeSuffix) } ?? [] +} + +private func filenameWithThemeSuffixRemoved(_ filename: String) -> String { + return filename.stripping(suffix: nnwThemeSuffix) +} + +private func themeNameForPath(_ f: String) -> String { + let filename = (f as NSString).lastPathComponent + return filenameWithThemeSuffixRemoved(filename) +} + +private func pathIsPathForThemeName(_ themeName: String, path: String) -> Bool { + let filename = (path as NSString).lastPathComponent + return filenameWithThemeSuffixRemoved(filename) == themeName +} + +private func pathForThemeName(_ themeName: String, folder: String) -> String? { + for onePath in allThemePaths(folder) { + if pathIsPathForThemeName(themeName, path: onePath) { + return onePath + } + } + return nil +} diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index c3935b68b..c7a01f33a 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -28,6 +28,8 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { final class AppDefaults { + static let defaultThemeName = "Defaults" + static let shared = AppDefaults() private init() {} @@ -55,6 +57,7 @@ final class AppDefaults { static let addWebFeedFolderName = "addWebFeedFolderName" static let addFolderAccountID = "addFolderAccountID" static let useSystemBrowser = "useSystemBrowser" + static let currentThemeName = "currentThemeName" } let isDeveloperBuild: Bool = { @@ -220,6 +223,15 @@ final class AppDefaults { } } + var currentThemeName: String? { + get { + return AppDefaults.string(for: Key.currentThemeName) + } + set { + AppDefaults.setString(for: Key.currentThemeName, newValue) + } + } + static func registerDefaults() { let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue, Key.timelineGroupByFeed: false, @@ -229,7 +241,8 @@ final class AppDefaults { Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, Key.articleFullscreenAvailable: false, Key.articleFullscreenEnabled: false, - Key.confirmMarkAllAsRead: true] + Key.confirmMarkAllAsRead: true, + Key.currentThemeName: Self.defaultThemeName] AppDefaults.store.register(defaults: defaults) } diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index 68fabfd42..c3936588d 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -68,9 +68,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7))) AccountManager.shared = AccountManager(accountsFolder: documentAccountsFolderPath) - let documentStylesFolder = documentFolder.appendingPathComponent("Styles").absoluteString + let documentStylesFolder = documentFolder.appendingPathComponent("Themes").absoluteString let documentStylesFolderPath = String(documentStylesFolder.suffix(from: documentAccountsFolder.index(documentStylesFolder.startIndex, offsetBy: 7))) - ArticleStylesManager.shared = ArticleStylesManager(folderPath: documentStylesFolderPath) + ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentStylesFolderPath) FeedProviderManager.shared.delegate = ExtensionPointManager.shared diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index 232d62db1..a433546ef 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -543,23 +543,23 @@ private extension WebViewController { func renderPage(_ webView: PreloadedWebView?) { guard let webView = webView else { return } - let style = ArticleStylesManager.shared.currentStyle + let theme = ArticleThemesManager.shared.currentTheme let rendering: ArticleRenderer.Rendering if let articleExtractor = articleExtractor, articleExtractor.state == .processing { - rendering = ArticleRenderer.loadingHTML(style: style) + rendering = ArticleRenderer.loadingHTML(theme: theme) } else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = article { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) } else if let article = article, let extractedArticle = extractedArticle { if isShowingExtractedArticle { - rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) + rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme) } else { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) } } else if let article = article { - rendering = ArticleRenderer.articleHTML(article: article, style: style) + rendering = ArticleRenderer.articleHTML(article: article, theme: theme) } else { - rendering = ArticleRenderer.noSelectionHTML(style: style) + rendering = ArticleRenderer.noSelectionHTML(theme: theme) } let substitutions = [