← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


commit 88cad792b010172e156c691364d738bd8f11d47b
parent d2a80337b3b9cdb11e8a8968a5ca74d05b643938
Author: Michael Camilleri <[email protected]>
Date:   Sat, 14 Feb 2026 13:28:51 +0900

Experiment with gradient colours

Co-Authored-By: Claude 4.5 Sonnet <[email protected]>

Diffstat

MListless.xcodeproj/project.pbxproj | 4----
MListless/Views/TaskListView.swift | 12++++++++++++
MListlessMac/Views/TaskRowView.swift | 14+++++++++++++-
MListlessiOS/Views/TaskRowSwipeGesture.swift | 2+-
DListlessiOS/Views/TaskRowTapGesture.swift | 39---------------------------------------
MListlessiOS/Views/TaskRowView.swift | 30+++++++++++++++++++++++-------

6 files changed, 49 insertions(+), 52 deletions(-)

diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj

@@ -36,7 +36,6 @@
 		D878CD3A552C6A9685A30AA8 /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6B0195EA0176B72DE9B092 /* PlatformTextFieldWidthModifier.swift */; };
 		D8EFF49E9156083D675D47F0 /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */; };
 		D9DA553485AD0CA28D5BE0C8 /* TaskListView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FF5B9F5B979D54D5DEE192 /* TaskListView+Toolbar.swift */; };
-		DAB0D2F8881B234DDE2B0BF6 /* TaskRowTapGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8829E89B1F50224FCAE10F84 /* TaskRowTapGesture.swift */; };
 		DC71C45D92524C3893BF9FDB /* PlatformTextFieldWidthModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */; };
 		DC73A39A269AB495BCE1AC48 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14858BDFD1FD5119F1F24A6 /* PersistenceController.swift */; };
 		ECD5E7EA05AE1C00B38C939E /* TaskStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967F7ECEB3915CEDCE584872 /* TaskStoreTests.swift */; };
@@ -78,7 +77,6 @@
 		7C73E9D4C42CCABBF0F33543 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
 		81880970CCFC2CB00B65047E /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
 		82A3509AD32A54434BCC8017 /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; };
-		8829E89B1F50224FCAE10F84 /* TaskRowTapGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowTapGesture.swift; sourceTree = "<group>"; };
 		917597025B3D5D18E33982D3 /* ColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtensions.swift; sourceTree = "<group>"; };
 		9262207DAC21619BD9EDEE15 /* Listless.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Listless.entitlements; sourceTree = "<group>"; };
 		93FF5B9F5B979D54D5DEE192 /* TaskListView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TaskListView+Toolbar.swift"; sourceTree = "<group>"; };
@@ -142,7 +140,6 @@
 				93FF5B9F5B979D54D5DEE192 /* TaskListView+Toolbar.swift */,
 				1B3D0B4710C7A0EE4F227851 /* TaskRowDragGesture.swift */,
 				44C16D36971A140364159FB9 /* TaskRowSwipeGesture.swift */,
-				8829E89B1F50224FCAE10F84 /* TaskRowTapGesture.swift */,
 				199CC2F58DD7CBA3F2229366 /* TaskRowView.swift */,
 			);
 			path = Views;
@@ -388,7 +385,6 @@
 				42E4CDE1D17463554CC4F41F /* TaskListView.swift in Sources */,
 				D6B3DBDD6A3F0A6E166CFFD5 /* TaskRowDragGesture.swift in Sources */,
 				7B2CD636BA3B63A586F93E31 /* TaskRowSwipeGesture.swift in Sources */,
-				DAB0D2F8881B234DDE2B0BF6 /* TaskRowTapGesture.swift in Sources */,
 				7E366008FF9335A77FE1636D /* TaskRowView.swift in Sources */,
 				91EDF52C7C5C0B35E9D8B51E /* TaskStore.swift in Sources */,
 			);

diff --git a/Listless/Views/TaskListView.swift b/Listless/Views/TaskListView.swift

@@ -129,6 +129,18 @@ struct TaskListView: View {
                 handleDrop(items: items)
             }
         }
+        // .background(
+        //     LinearGradient(
+        //         colors: [
+        //             Color(hue: 0.98, saturation: 0.85, brightness: 1.0),
+        //             Color(hue: 0.88, saturation: 0.75, brightness: 0.95),
+        //             Color(hue: 0.72, saturation: 0.65, brightness: 0.85),
+        //         ],
+        //         startPoint: .top,
+        //         endPoint: .bottom
+        //     )
+        //     .ignoresSafeArea()
+        // )
         .contentShape(Rectangle())
         .onTapGesture {
             handleBackgroundTap()

diff --git a/ListlessMac/Views/TaskRowView.swift b/ListlessMac/Views/TaskRowView.swift

@@ -135,6 +135,16 @@ 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()
@@ -193,7 +203,9 @@ struct TaskRowView: View {
 
     @ViewBuilder
     private var selectionBackground: some View {
-        if isSelected {
+        if task.isCompleted {
+            Color(nsColor: .windowBackgroundColor)
+        } else if isSelected {
             RoundedRectangle(cornerRadius: 6, style: .continuous)
                 .fill(Color.accentColor.opacity(0.2))
         }

diff --git a/ListlessiOS/Views/TaskRowSwipeGesture.swift b/ListlessiOS/Views/TaskRowSwipeGesture.swift

@@ -95,7 +95,7 @@ struct TaskRowSwipeGesture: ViewModifier {
                         Spacer()
                         Image(systemName: "trash.fill")
                             .font(.system(size: 24))
-                            .foregroundStyle(.white)
+                            .foregroundStyle(isTriggered ? .black : .white)
                             .padding(.trailing, 20)
                     }
                 }

diff --git a/ListlessiOS/Views/TaskRowTapGesture.swift b/ListlessiOS/Views/TaskRowTapGesture.swift

@@ -1,39 +0,0 @@
-import SwiftUI
-
-extension View {
-    func taskTapGesture(
-        onCheckboxTap: @escaping () -> Void,
-        onTextTap: @escaping () -> Void
-    ) -> some View {
-        self.modifier(
-            TaskRowTapGesture(
-                onCheckboxTap: onCheckboxTap,
-                onTextTap: onTextTap
-            ))
-    }
-}
-
-struct TaskRowTapGesture: ViewModifier {
-    let onCheckboxTap: () -> Void
-    let onTextTap: () -> Void
-
-    private let checkboxZoneWidth: CGFloat = 48  // Checkbox + padding area
-
-    func body(content: Content) -> some View {
-        content
-            .contentShape(Rectangle())
-            .onTapGesture(coordinateSpace: .local) { location in
-                handleTap(at: location)
-            }
-    }
-
-    private func handleTap(at location: CGPoint) {
-        if location.x <= checkboxZoneWidth {
-            // Tap in checkbox zone
-            onCheckboxTap()
-        } else {
-            // Tap in text zone - TextField will handle focus natively
-            onTextTap()
-        }
-    }
-}

diff --git a/ListlessiOS/Views/TaskRowView.swift b/ListlessiOS/Views/TaskRowView.swift

@@ -63,6 +63,7 @@ struct TaskRowView: View {
 
             TextField("Task", text: $editingTitle)
                 .focused($focusedField, equals: .task(taskID))
+                .font(.system(size: 18))
                 .foregroundStyle(task.isCompleted ? Color.secondary : Color.primary)
                 .strikethrough(task.isCompleted, color: .secondary)
                 .disabled(task.isCompleted)
@@ -72,7 +73,7 @@ struct TaskRowView: View {
                     onEndEdit(taskID, true)
                 }
         }
-        .padding(.vertical, 8)
+        .padding(.vertical, 14)
         .padding(.horizontal, 16)
         .frame(maxWidth: .infinity, alignment: .leading)
         .contentShape(Rectangle())
@@ -94,6 +95,21 @@ struct TaskRowView: View {
             }
         }
         .background(selectionBackground)
+        .overlay(alignment: .top) {
+            if !task.isCompleted {
+                VStack(spacing: 0) {
+                    LinearGradient(
+                        colors: [.black.opacity(0.10), .clear],
+                        startPoint: .bottom,
+                        endPoint: .top
+                    )
+                    .frame(height: 6)
+                    Rectangle()
+                        .fill(.black.opacity(0.4))
+                        .frame(height: 0.5)
+                }
+            }
+        }
         .onAppear {
             editingTitle = task.title
         }
@@ -128,12 +144,12 @@ struct TaskRowView: View {
         )
     }
 
+    @ViewBuilder
     private var selectionBackground: some View {
-        Color(uiColor: .systemBackground)
-            .overlay {
-                if isSelected {
-                    Color.accentColor.opacity(0.2)
-                }
-            }
+        if task.isCompleted {
+            Color(uiColor: .systemBackground)
+        } else if isSelected {
+            Color.accentColor.opacity(0.2)
+        }
     }
 }