commit 54a3d9759c703f67c1c9a33c8763ee0360dbb90d
parent d99fcbc889cea72ec07adccff12a378190605f77
Author: Michael Camilleri <[email protected]>
Date: Wed, 18 Mar 2026 14:06:13 +0900
Allow user to select Light Mode/Dark Mode in macOS version
This commit allows the user to manually select whether they want to run
the app in Light Mode or Dark Mode (or use the System default). In
making this change, I've also realised that I don't like the shadows
that appear and so these are now removed.
Co-Authored-By: Claude 4.6 Opus <[email protected]>
Diffstat:
5 files changed, 59 insertions(+), 10 deletions(-)
diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj
@@ -56,6 +56,7 @@
77970A2323A598E830D95301 /* TaskListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB43816B8E7F083A2AD07F28 /* TaskListView+Toolbar.swift */; };
77FE96F070B1F7FE31A9CE51 /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2174C43654733E9D4023157 /* PlatformTextFieldWidthModifier.swift */; };
785721EB774EAC6BBA26C038 /* PullToClear.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DBAC2A39FA2760D006AAB /* PullToClear.swift */; };
+ 7BB45276D4EB96B8425D2EBD /* AppColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DB90B3400C191460F4F4BD /* AppColors.swift */; };
7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */; };
80BCA654FE694D8A77D9BF93 /* TaskListView+PullToCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD8D4C29E09FEE78AE5AB79 /* TaskListView+PullToCreate.swift */; };
82E75475E13FD847DDDC69D8 /* TaskListView+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA75CA18C6270D68A755AEB /* TaskListView+Drag.swift */; };
@@ -212,6 +213,7 @@
BC845482926A73B0BF820328 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
BFF7D84B54AE70036D205CA4 /* PullToCreate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullToCreate.swift; sourceTree = "<group>"; };
C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
+ C4DB90B3400C191460F4F4BD /* AppColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppColors.swift; sourceTree = "<group>"; };
C611E04943F1D82D6F975592 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
C6812E535A24C599C28F9278 /* Listless watchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Listless watchOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
C71466C5CD1A5BA984352F8D /* Listless iOS Unit Tests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = "Listless iOS Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -296,6 +298,7 @@
42CC0393E2524624AEC54D03 /* Helpers */ = {
isa = PBXGroup;
children = (
+ C4DB90B3400C191460F4F4BD /* AppColors.swift */,
4C79ABB39A40D3E1828716C7 /* AppCommands.swift */,
F15BF645DEDE8D9E94DB508B /* BackgroundClickMonitor.swift */,
D640E7D21735C62A30A26DA4 /* ClickableTextField.swift */,
@@ -744,6 +747,7 @@
buildActionMask = 2147483647;
files = (
99D17075DA3F00F52A18BB4D /* AccentColor.swift in Sources */,
+ 7BB45276D4EB96B8425D2EBD /* AppColors.swift in Sources */,
B7CFDCA5EA48EDE1C768FA21 /* AppCommands.swift in Sources */,
37AEF10712B3325BF9BC72E4 /* BackgroundClickMonitor.swift in Sources */,
DB5FF6C1AA57D4C9BDDD50FD /* ClickableTextField.swift in Sources */,
diff --git a/ListlessMac/Helpers/AppColors.swift b/ListlessMac/Helpers/AppColors.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+extension Color {
+ /// Canvas behind task rows: warm gray in light mode, default window background in dark mode.
+ static let outerBackground = Color(nsColor: NSColor(name: nil) { appearance in
+ appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
+ ? .windowBackgroundColor
+ : NSColor(red: 0.922, green: 0.906, blue: 0.886, alpha: 1) // #EBE7E2
+ })
+}
diff --git a/ListlessMac/ListlessMacApp.swift b/ListlessMac/ListlessMacApp.swift
@@ -13,6 +13,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
private let persistenceController: PersistenceController
private var syncDiagnosticsWindow: NSWindow?
private let coordinators = NSMapTable<NSWindow, WindowCoordinator>.weakToStrongObjects()
+ private static let appearanceModeKey = "appearanceMode"
private var keyWindowCoordinator: WindowCoordinator? {
guard let window = NSApp.keyWindow else { return nil }
@@ -31,6 +32,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
func applicationDidFinishLaunching(_ notification: Notification) {
NSWindow.allowsAutomaticWindowTabbing = false
+ applyAppearanceMode(UserDefaults.standard.integer(forKey: Self.appearanceModeKey))
installMainMenu()
openNewWindow()
}
@@ -55,6 +57,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
switch menuItem.action {
case #selector(handleNewWindow), #selector(handleShowSyncDiagnostics):
return true
+ case #selector(handleAppearanceSystem), #selector(handleAppearanceLight), #selector(handleAppearanceDark):
+ let currentMode = UserDefaults.standard.integer(forKey: Self.appearanceModeKey)
+ let itemMode: Int = switch menuItem.action {
+ case #selector(handleAppearanceLight): 1
+ case #selector(handleAppearanceDark): 2
+ default: 0
+ }
+ menuItem.state = (currentMode == itemMode) ? .on : .off
+ return true
default:
break
}
@@ -132,6 +143,23 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
openSyncDiagnosticsWindow()
}
+ @objc private func handleAppearanceSystem() { setAppearanceMode(0) }
+ @objc private func handleAppearanceLight() { setAppearanceMode(1) }
+ @objc private func handleAppearanceDark() { setAppearanceMode(2) }
+
+ private func setAppearanceMode(_ mode: Int) {
+ UserDefaults.standard.set(mode, forKey: Self.appearanceModeKey)
+ applyAppearanceMode(mode)
+ }
+
+ private func applyAppearanceMode(_ mode: Int) {
+ NSApp.appearance = switch mode {
+ case 1: NSAppearance(named: .aqua)
+ case 2: NSAppearance(named: .darkAqua)
+ default: nil
+ }
+ }
+
private func openNewWindow() {
let defaultContentSize = NSSize(width: 400, height: 350)
let windowCoordinator = WindowCoordinator()
@@ -322,6 +350,22 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
mainMenu.addItem(editMenuItem)
let viewMenu = NSMenu(title: "View")
+
+ let appearanceMenu = NSMenu(title: "Appearance")
+ let systemItem = NSMenuItem(title: "System", action: #selector(handleAppearanceSystem), keyEquivalent: "")
+ systemItem.target = self
+ appearanceMenu.addItem(systemItem)
+ let lightItem = NSMenuItem(title: "Light", action: #selector(handleAppearanceLight), keyEquivalent: "")
+ lightItem.target = self
+ appearanceMenu.addItem(lightItem)
+ let darkItem = NSMenuItem(title: "Dark", action: #selector(handleAppearanceDark), keyEquivalent: "")
+ darkItem.target = self
+ appearanceMenu.addItem(darkItem)
+ let appearanceMenuItem = NSMenuItem(title: "Appearance", action: nil, keyEquivalent: "")
+ appearanceMenuItem.submenu = appearanceMenu
+ viewMenu.addItem(appearanceMenuItem)
+ viewMenu.addItem(NSMenuItem.separator())
+
let fullScreenItem = NSMenuItem(title: "Enter Full Screen", action: #selector(NSWindow.toggleFullScreen(_:)), keyEquivalent: "f")
fullScreenItem.keyEquivalentModifierMask = [.command, .control]
viewMenu.addItem(fullScreenItem)
diff --git a/ListlessMac/Views/TaskListView.swift b/ListlessMac/Views/TaskListView.swift
@@ -396,6 +396,7 @@ struct TaskListView: View, TaskListViewProtocol {
handleBackgroundTap()
}
}
+ .background(Color.outerBackground)
.overlay {
if isCompletelyEmpty && draftPlacement == nil {
Text("Click to create")
diff --git a/ListlessMac/Views/TaskRowView.swift b/ListlessMac/Views/TaskRowView.swift
@@ -111,16 +111,6 @@ struct TaskRowView: View {
onSelect(taskID)
}
.background(selectionBackground)
- .overlay(alignment: .bottom) {
- if !task.isCompleted {
- LinearGradient(
- colors: [.clear, .black.opacity(0.15)],
- startPoint: .top,
- endPoint: .bottom
- )
- .frame(height: 6)
- }
- }
.overlay(alignment: .leading) {
// Colored accent bar on the left edge
Rectangle()