From 98b9080b1f6d07d181ae25aa8a338c56e23a2af7 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 8 Sep 2021 00:28:13 -0500 Subject: [PATCH] Implement initial alert dialog when importing a theme --- Mac/AppDelegate.swift | 50 ++++++++++++++- Shared/ArticleStyles/ArticleTheme.swift | 63 +++++++++++++++---- .../ArticleStyles/ArticleThemesManager.swift | 46 +++++--------- 3 files changed, 112 insertions(+), 47 deletions(-) diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 998d553c7..9ee0e57c4 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -319,6 +319,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, AccountManager.shared.receiveRemoteNotification(userInfo: userInfo) } + func application(_ sender: NSApplication, openFile filename: String) -> Bool { + guard filename.hasSuffix(".nnwtheme") else { return false } + importTheme(filename: filename) + return true + } + func applicationWillTerminate(_ notification: Notification) { shuttingDown = true saveState() @@ -769,7 +775,6 @@ private extension AppDelegate { } func objectsForInspector() -> [Any]? { - guard let window = NSApplication.shared.mainWindow, let windowController = window.windowController as? MainWindowController else { return nil } @@ -782,7 +787,6 @@ private extension AppDelegate { } func updateSortMenuItems() { - let sortByNewestOnTop = AppDefaults.shared.timelineSortDirection == .orderedDescending sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on @@ -792,6 +796,48 @@ private extension AppDelegate { let groupByFeedEnabled = AppDefaults.shared.timelineGroupByFeed groupArticlesByFeedMenuItem.state = groupByFeedEnabled ? .on : .off } + + func importTheme(filename: String) { + guard let window = mainWindowController?.window else { return } + + let theme = ArticleTheme(path: filename) + + let alert = NSAlert() + alert.alertStyle = .informational + + let localizedMessageText = NSLocalizedString("Install “%@” by %@?", comment: "Theme message text") + alert.messageText = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name, theme.creatorName) as String + + var attrs = [NSAttributedString.Key : Any]() + attrs[.font] = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) + attrs[.foregroundColor] = NSColor.textColor + + let titleParagraphStyle = NSMutableParagraphStyle() + titleParagraphStyle.alignment = .center + attrs[.paragraphStyle] = titleParagraphStyle + + let websiteText = NSMutableAttributedString() + websiteText.append(NSAttributedString(string: NSLocalizedString("Author's Website", comment: "Author's Website"), attributes: attrs)) + websiteText.append(NSAttributedString(string: "\n")) + + attrs[.link] = theme.creatorHomePage + websiteText.append(NSAttributedString(string: theme.creatorHomePage, attributes: attrs)) + + let textView = NSTextView(frame: CGRect(x: 0, y: 0, width: 200, height: 15)) + textView.isEditable = false + textView.drawsBackground = false + textView.textStorage?.setAttributedString(websiteText) + alert.accessoryView = textView + + alert.addButton(withTitle: NSLocalizedString("Install Style", comment: "Install Style")) + alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Install Style")) + + alert.beginSheetModal(for: window) { [weak self] result in + if result == NSApplication.ModalResponse.alertFirstButtonReturn { + guard let self = self else { return } + } + } + } } /* diff --git a/Shared/ArticleStyles/ArticleTheme.swift b/Shared/ArticleStyles/ArticleTheme.swift index 53c548ab0..707fac309 100644 --- a/Shared/ArticleStyles/ArticleTheme.swift +++ b/Shared/ArticleStyles/ArticleTheme.swift @@ -11,10 +11,33 @@ import Foundation struct ArticleTheme: Equatable { static let defaultTheme = ArticleTheme() + static let nnwThemeSuffix = ".nnwtheme" + + private static let defaultThemeName = NSLocalizedString("Default", comment: "Default") + private static let unknownValue = NSLocalizedString("Unknown", comment: "Unknown Value") + let path: String? let template: String? let css: String? - let info: NSDictionary? + + var name: String { + guard let path = path else { return Self.defaultThemeName } + return Self.themeNameForPath(path) + } + + var creatorHomePage: String { + return info?["CreatorHomePage"] as? String ?? Self.unknownValue + } + + var creatorName: String { + return info?["CreatorName"] as? String ?? Self.unknownValue + } + + var version: String { + return info?["Version"] as? String ?? "0.0" + } + + private let info: NSDictionary? init() { self.path = nil; @@ -23,14 +46,14 @@ struct ArticleTheme: Equatable { let sharedCSSPath = Bundle.main.path(forResource: "shared", ofType: "css")! let platformCSSPath = Bundle.main.path(forResource: "styleSheet", ofType: "css")! - if let sharedCSS = stringAtPath(sharedCSSPath), let platformCSS = stringAtPath(platformCSSPath) { + if let sharedCSS = Self.stringAtPath(sharedCSSPath), let platformCSS = Self.stringAtPath(platformCSSPath) { css = sharedCSS + "\n" + platformCSS } else { css = nil } let templatePath = Bundle.main.path(forResource: "template", ofType: "html")! - template = stringAtPath(templatePath) + template = Self.stringAtPath(templatePath) } init(path: String) { @@ -41,26 +64,40 @@ struct ArticleTheme: Equatable { self.info = NSDictionary(contentsOfFile: infoPath) let cssPath = (path as NSString).appendingPathComponent("stylesheet.css") - self.css = stringAtPath(cssPath) + self.css = Self.stringAtPath(cssPath) let templatePath = (path as NSString).appendingPathComponent("template.html") - self.template = stringAtPath(templatePath) + self.template = Self.stringAtPath(templatePath) } else { - self.css = stringAtPath(path) + self.css = Self.stringAtPath(path) self.template = nil self.info = nil } } -} + + static func stringAtPath(_ f: String) -> String? { + if !FileManager.default.fileExists(atPath: f) { + return nil + } -private func stringAtPath(_ f: String) -> String? { - - if !FileManager.default.fileExists(atPath: f) { + if let s = try? NSString(contentsOfFile: f, usedEncoding: nil) as String { + return s + } return nil } - if let s = try? NSString(contentsOfFile: f, usedEncoding: nil) as String { - return s + static func filenameWithThemeSuffixRemoved(_ filename: String) -> String { + return filename.stripping(suffix: Self.nnwThemeSuffix) } - return nil + + static func themeNameForPath(_ f: String) -> String { + let filename = (f as NSString).lastPathComponent + return filenameWithThemeSuffixRemoved(filename) + } + + static func pathIsPathForThemeName(_ themeName: String, path: String) -> Bool { + let filename = (path as NSString).lastPathComponent + return filenameWithThemeSuffixRemoved(filename) == themeName + } + } diff --git a/Shared/ArticleStyles/ArticleThemesManager.swift b/Shared/ArticleStyles/ArticleThemesManager.swift index 1d2e317b2..c9cf10e0d 100644 --- a/Shared/ArticleStyles/ArticleThemesManager.swift +++ b/Shared/ArticleStyles/ArticleThemesManager.swift @@ -17,9 +17,6 @@ import RSCore let ArticleThemeNamesDidChangeNotification = "ArticleThemeNamesDidChangeNotification" let CurrentArticleThemeDidChangeNotification = "CurrentArticleThemeDidChangeNotification" -private let themesInResourcesFolderName = "Themes" -private let nnwThemeSuffix = ".nnwtheme" - final class ArticleThemesManager { static var shared: ArticleThemesManager! @@ -80,7 +77,7 @@ final class ArticleThemesManager { // MARK : Internal private func updateThemeNames() { - let updatedThemeNames = allThemePaths(folderPath).map { themeNameForPath($0) } + let updatedThemeNames = allThemePaths(folderPath).map { ArticleTheme.themeNameForPath($0) } if updatedThemeNames != themeNames { themeNames = updatedThemeNames @@ -104,7 +101,6 @@ final class ArticleThemesManager { } private func updateCurrentTheme() { - var themeName = currentThemeName if !themeNames.contains(themeName) { themeName = AppDefaults.defaultThemeName @@ -121,33 +117,19 @@ final class ArticleThemesManager { 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 - } + private func allThemePaths(_ folder: String) -> [String] { + let filepaths = FileManager.default.filePaths(inFolder: folder) + return filepaths?.filter { $0.hasSuffix(ArticleTheme.nnwThemeSuffix) } ?? [] } - return nil + + private func pathForThemeName(_ themeName: String, folder: String) -> String? { + for onePath in allThemePaths(folder) { + if ArticleTheme.pathIsPathForThemeName(themeName, path: onePath) { + return onePath + } + } + return nil + } + }