From 595828be945c2b8ec6cf4ff5ae729e2b3319c863 Mon Sep 17 00:00:00 2001 From: "Bat.bat" <45396585+williambj1@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:37:25 +1000 Subject: [PATCH] Add option to toggle appearance --- HeliPort.xcodeproj/project.pbxproj | 12 ++-- HeliPort/AppDelegate.swift | 11 +++- HeliPort/Appearance/Localizable.xcstrings | 66 +++++++++++++++++++ .../Preferences/PrefsGeneralView.swift | 53 +++++++++++++-- .../Supporting files/NSApp+Extensions.swift | 29 ++++++++ ...rminated.swift => String+Extensions.swift} | 6 +- 6 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 HeliPort/Supporting files/NSApp+Extensions.swift rename HeliPort/Supporting files/{String+NonNullTerminated.swift => String+Extensions.swift} (95%) diff --git a/HeliPort.xcodeproj/project.pbxproj b/HeliPort.xcodeproj/project.pbxproj index 67ce6c1..7d53897 100644 --- a/HeliPort.xcodeproj/project.pbxproj +++ b/HeliPort.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 1317990B256986E5006957D8 /* String+NonNullTerminated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1317990A256986E5006957D8 /* String+NonNullTerminated.swift */; }; + 1317990B256986E5006957D8 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1317990A256986E5006957D8 /* String+Extensions.swift */; }; 1380C36124D54BFD00A448CF /* PrefsSavedNetworksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1380C36024D54BFD00A448CF /* PrefsSavedNetworksView.swift */; }; 1380C36524D5580200A448CF /* PrefsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1380C36424D5580200A448CF /* PrefsWindow.swift */; }; 138D3CC824CE635800793AC1 /* Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138D3CC724CE635800793AC1 /* Commands.swift */; }; @@ -16,6 +16,7 @@ 13AF73B624B25E170015867C /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCA4DFAC243A307B002A862A /* StatusMenu.swift */; }; 13C20DFA24D8B6D100B1E713 /* PrefsGeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C20DF924D8B6D100B1E713 /* PrefsGeneralView.swift */; }; 5013B8FB2C5C8C8D002C5006 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5013B8FA2C5C8C8D002C5006 /* Localizable.xcstrings */; }; + 5013B8FD2C5DF244002C5006 /* NSApp+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5013B8FC2C5DF244002C5006 /* NSApp+Extensions.swift */; }; 505EC11D2C5BD89400F4E4EA /* StatusBarIconManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505EC11C2C5BD89400F4E4EA /* StatusBarIconManager.swift */; }; 505EC11F2C5BD8ED00F4E4EA /* StatusBarIconLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505EC11E2C5BD8ED00F4E4EA /* StatusBarIconLegacy.swift */; }; 505EC1212C5BD95700F4E4EA /* StatusBarIconModern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505EC1202C5BD95700F4E4EA /* StatusBarIconModern.swift */; }; @@ -60,7 +61,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 1317990A256986E5006957D8 /* String+NonNullTerminated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+NonNullTerminated.swift"; sourceTree = ""; }; + 1317990A256986E5006957D8 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; 1380C36024D54BFD00A448CF /* PrefsSavedNetworksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsSavedNetworksView.swift; sourceTree = ""; }; 1380C36424D5580200A448CF /* PrefsWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWindow.swift; sourceTree = ""; }; 138D3CC724CE635800793AC1 /* Commands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Commands.swift; sourceTree = ""; }; @@ -68,6 +69,7 @@ 13AB3CA724DE47D10093D283 /* WiFiConfigWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WiFiConfigWindow.swift; sourceTree = ""; }; 13C20DF924D8B6D100B1E713 /* PrefsGeneralView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsGeneralView.swift; sourceTree = ""; }; 5013B8FA2C5C8C8D002C5006 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + 5013B8FC2C5DF244002C5006 /* NSApp+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSApp+Extensions.swift"; sourceTree = ""; }; 505EC11C2C5BD89400F4E4EA /* StatusBarIconManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIconManager.swift; sourceTree = ""; }; 505EC11E2C5BD8ED00F4E4EA /* StatusBarIconLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIconLegacy.swift; sourceTree = ""; }; 505EC1202C5BD95700F4E4EA /* StatusBarIconModern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIconModern.swift; sourceTree = ""; }; @@ -218,9 +220,10 @@ F336D63D24B4986C004C98C4 /* itl_phy_mode+Description.swift */, F336D63F24B7B7D8004C98C4 /* itl80211_security+Description.swift */, F336D63B24B497B6004C98C4 /* NetworkManager+Data.swift */, + 5013B8FC2C5DF244002C5006 /* NSApp+Extensions.swift */, F33A1F3E24C8347F008ED2BD /* NSLocalizedString+Extensions.swift */, F34B2B8C24AA4C1E009AB1BB /* NSMenuItem+Extensions.swift */, - 1317990A256986E5006957D8 /* String+NonNullTerminated.swift */, + 1317990A256986E5006957D8 /* String+Extensions.swift */, ); path = "Supporting files"; sourceTree = ""; @@ -422,7 +425,8 @@ 50F4959824BDD26D00AE4C08 /* LoginItemManager.swift in Sources */, F34B2B8D24AA4C1E009AB1BB /* NSMenuItem+Extensions.swift in Sources */, 50E7700F2C5A7CE100DB1160 /* UpdateManager.swift in Sources */, - 1317990B256986E5006957D8 /* String+NonNullTerminated.swift in Sources */, + 5013B8FD2C5DF244002C5006 /* NSApp+Extensions.swift in Sources */, + 1317990B256986E5006957D8 /* String+Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/HeliPort/AppDelegate.swift b/HeliPort/AppDelegate.swift index 8fe8869..9ddbb60 100644 --- a/HeliPort/AppDelegate.swift +++ b/HeliPort/AppDelegate.swift @@ -24,8 +24,17 @@ class AppDelegate: NSObject, NSApplicationDelegate { let statusBar = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + let legacyUIEnabled = { + if #unavailable(macOS 11) { + return true + } + return UserDefaults.standard.bool(forKey: .DefaultsKey.legacyUI) + }() + + Log.debug("UI appearance: \(legacyUIEnabled ? "legacy" : "modern")") + let iconProvider: StatusBarIconProvider = { - if #available(macOS 11, *) { + if #available(macOS 11, *), !legacyUIEnabled { return StatusBarIconModern() } return StatusBarIconLegacy() diff --git a/HeliPort/Appearance/Localizable.xcstrings b/HeliPort/Appearance/Localizable.xcstrings index 44437ba..b9b1aee 100644 --- a/HeliPort/Appearance/Localizable.xcstrings +++ b/HeliPort/Appearance/Localizable.xcstrings @@ -1987,6 +1987,17 @@ } } }, + "Appearance:" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "外观:" + } + } + } + }, "Auto Join" : { "extractionState" : "manual", "localizations" : { @@ -3965,6 +3976,17 @@ } } }, + "HeliPort Restart Required" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "HeliPort 需要重启" + } + } + } + }, "HeliPort running at an unexpected path" : { "extractionState" : "manual", "localizations" : { @@ -4666,6 +4688,17 @@ } } }, + "Later" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "稍后" + } + } + } + }, "Launch At Login" : { "extractionState" : "manual", "localizations" : { @@ -6530,6 +6563,17 @@ } } }, + "Restart" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重启" + } + } + } + }, "Saved Networks:" : { "extractionState" : "manual", "localizations" : { @@ -6946,6 +6990,17 @@ } } }, + "Switching appearance requires a restart of the application to take effect." : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换外观需要重新启动应用程序才能生效。" + } + } + } + }, "The bug report will be generated in the seleted folder" : { "extractionState" : "manual", "localizations" : { @@ -7611,6 +7666,17 @@ } } }, + "Use Legacy UI" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用传统界面" + } + } + } + }, "Username:" : { "extractionState" : "manual", "localizations" : { diff --git a/HeliPort/Appearance/Preferences/PrefsGeneralView.swift b/HeliPort/Appearance/Preferences/PrefsGeneralView.swift index a9b6e24..ae542c3 100644 --- a/HeliPort/Appearance/Preferences/PrefsGeneralView.swift +++ b/HeliPort/Appearance/Preferences/PrefsGeneralView.swift @@ -29,6 +29,7 @@ class PrefsGeneralView: NSView { target: self, action: #selector(checkboxChanged(_:))) checkbox.identifier = .autoUpdateId + checkbox.state = UpdateManager.sharedUpdater.automaticallyChecksForUpdates ? .on : .off return checkbox }() @@ -37,6 +38,29 @@ class PrefsGeneralView: NSView { target: self, action: #selector(checkboxChanged(_:))) checkbox.identifier = .autoDownloadId + checkbox.state = UpdateManager.sharedUpdater.automaticallyDownloadsUpdates ? .on : .off + return checkbox + }() + + let appearanceLabel: NSTextField = { + let view = NSTextField(labelWithString: .appearance) + view.alignment = .right + return view + }() + + lazy var legacyUICheckbox: NSButton = { + let checkbox = NSButton(checkboxWithTitle: .useLegacyUI, + target: self, + action: #selector(self.checkboxChanged(_:))) + checkbox.identifier = .legacyUIId + + if #available(macOS 11, *) { + checkbox.state = UserDefaults.standard.bool(forKey: .DefaultsKey.legacyUI) ? .on : .off + } else { + checkbox.state = .on + checkbox.isEnabled = false + } + return checkbox }() @@ -53,13 +77,10 @@ class PrefsGeneralView: NSView { override init(frame frameRect: NSRect) { super.init(frame: frameRect) - autoUpdateCheckbox.target = self - autoDownloadCheckbox.target = self gridView.addRow(with: [updatesLabel]) gridView.addColumn(with: [autoUpdateCheckbox, autoDownloadCheckbox]) - - autoUpdateCheckbox.state = UpdateManager.sharedUpdater.automaticallyChecksForUpdates ? .on : .off - autoDownloadCheckbox.state = UpdateManager.sharedUpdater.automaticallyDownloadsUpdates ? .on : .off + let appearanceRow = gridView.addRow(with: [appearanceLabel, legacyUICheckbox]) + appearanceRow.topPadding = 5 addSubview(gridView) setupConstraints() @@ -90,6 +111,17 @@ extension PrefsGeneralView { UpdateManager.sharedUpdater.automaticallyChecksForUpdates = sender.state == .on case .autoDownloadId: UpdateManager.sharedUpdater.automaticallyDownloadsUpdates = sender.state == .on + case .legacyUIId: + if #available(macOS 11, *) { + UserDefaults.standard.set(sender.state == .on, forKey: .DefaultsKey.legacyUI) + let alert = CriticalAlert(message: .heliportRestart, + informativeText: .restartInfoText, + options: [.restart, .later]) + + if alert.show() == .alertFirstButtonReturn { + NSApp.restartApp() + } + } default: break } @@ -99,10 +131,21 @@ extension PrefsGeneralView { private extension NSUserInterfaceItemIdentifier { static let autoUpdateId = NSUserInterfaceItemIdentifier(rawValue: "AutoUpdateCheckbox") static let autoDownloadId = NSUserInterfaceItemIdentifier(rawValue: "AutoDownloadCheckbox") + + static let legacyUIId = NSUserInterfaceItemIdentifier(rawValue: "legacyUICheckbox") } private extension String { static let startup = NSLocalizedString("Updates:") static let autoCheckUpdate = NSLocalizedString("Automatically check for updates.") static let autoDownload = NSLocalizedString("Automatically download new updates.") + + static let appearance = NSLocalizedString("Appearance:") + static let useLegacyUI = NSLocalizedString("Use Legacy UI") + + static let heliportRestart = NSLocalizedString("HeliPort Restart Required") + static let restartInfoText = + NSLocalizedString("Switching appearance requires a restart of the application to take effect.") + static let restart = NSLocalizedString("Restart") + static let later = NSLocalizedString("Later") } diff --git a/HeliPort/Supporting files/NSApp+Extensions.swift b/HeliPort/Supporting files/NSApp+Extensions.swift new file mode 100644 index 0000000..77191c1 --- /dev/null +++ b/HeliPort/Supporting files/NSApp+Extensions.swift @@ -0,0 +1,29 @@ +// +// NSApp+Extensions.swift +// HeliPort +// +// Created by Bat.bat on 29/7/2024. +// Copyright © 2024 OpenIntelWireless. All rights reserved. +// + +import Cocoa + +extension NSApplication { + @available(macOS 10.15, *) + func restartApp() { + let config = NSWorkspace.OpenConfiguration() + config.createsNewApplicationInstance = true + + NSWorkspace.shared.openApplication(at: Bundle.main.bundleURL, + configuration: config, + completionHandler: { _, error in + if let error { + Log.error("Failed to restart the app: \(error)") + } else { + DispatchQueue.main.async { + NSApp.terminate(nil) + } + } + }) + } +} diff --git a/HeliPort/Supporting files/String+NonNullTerminated.swift b/HeliPort/Supporting files/String+Extensions.swift similarity index 95% rename from HeliPort/Supporting files/String+NonNullTerminated.swift rename to HeliPort/Supporting files/String+Extensions.swift index a9273b9..df6aa42 100644 --- a/HeliPort/Supporting files/String+NonNullTerminated.swift +++ b/HeliPort/Supporting files/String+Extensions.swift @@ -16,6 +16,10 @@ import Foundation public extension String { + enum DefaultsKey { + static let legacyUI = "legacyUIEnabled" + } + static func getSSIDFromCString(cString: UnsafePointer) -> String { var string = String(cString: cString) // Fixes memory leak, see https://stackoverflow.com/a/37584615 @@ -36,9 +40,7 @@ public extension String { } return string } -} -public extension String { init(cCharArray: T) { self = withUnsafeBytes(of: cCharArray) { $0.withMemoryRebound(to: CChar.self) { String(cString: $0.baseAddress!)}