listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

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) + } } }