← All repositories

listless

A simple list app for Apple platforms

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


commit 2d158a50fc69bb391a6bcc75d11eba4403e27158
parent 31a6c4911c5d2ec1011a29d0a050ea174b76255c
Author: Michael Camilleri <[email protected]>
Date:   Sun, 15 Feb 2026 05:32:54 +0900

Use accent colours in iOS

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

Diffstat

MListless/Views/TaskListView.swift | 2+-
MListlessiOS/Views/AppColors.swift | 8++++----
MListlessiOS/Views/TaskRowSwipeGesture.swift | 7+++++--
MListlessiOS/Views/TaskRowView.swift | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----

4 files changed, 73 insertions(+), 11 deletions(-)

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

@@ -126,7 +126,7 @@ struct TaskListView: View {
             }
             .frame(maxWidth: .infinity, alignment: .topLeading)
             #if os(iOS)
-            .padding(.horizontal, 16)
+            .padding(.trailing, 16)
             .padding(.vertical, 12)
             #endif
             .dropDestination(for: String.self) { items, location in

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

@@ -2,15 +2,15 @@ import SwiftUI
 import UIKit
 
 extension Color {
-    /// Warm gray canvas shown behind task cards and beneath completed-task text.
+    /// Canvas behind task cards: warm gray in light mode, black in dark mode.
     static let outerBackground = Color(uiColor: UIColor { traits in
         traits.userInterfaceStyle == .dark
-            ? UIColor(red: 0.173, green: 0.165, blue: 0.153, alpha: 1)  // #2C2A27
+            ? .black
             : UIColor(red: 0.922, green: 0.906, blue: 0.886, alpha: 1)  // #EBE7E2
     })
 
-    /// Stark card background: white in light mode, black in dark mode.
+    /// Card surface: white in light mode, elevated dark gray in dark mode.
     static let taskCard = Color(uiColor: UIColor { traits in
-        traits.userInterfaceStyle == .dark ? .black : .white
+        traits.userInterfaceStyle == .dark ? UIColor.secondarySystemBackground : .white
     })
 }

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

@@ -9,6 +9,7 @@ extension View {
         swipeOffset: Binding<CGFloat>,
         swipeDirection: Binding<TaskRowSwipeGesture.SwipeDirection>,
         isTriggered: Binding<Bool>,
+        completeColor: Color = .green,
         onComplete: @escaping () -> Void,
         onDelete: @escaping () -> Void,
         onSwipeActiveChanged: @escaping (Bool) -> Void = { _ in }
@@ -21,6 +22,7 @@ extension View {
                 swipeOffset: swipeOffset,
                 swipeDirection: swipeDirection,
                 isTriggered: isTriggered,
+                completeColor: completeColor,
                 onComplete: onComplete,
                 onDelete: onDelete,
                 onSwipeActiveChanged: onSwipeActiveChanged
@@ -35,6 +37,7 @@ struct TaskRowSwipeGesture: ViewModifier {
     @Binding var swipeOffset: CGFloat
     @Binding var swipeDirection: SwipeDirection
     @Binding var isTriggered: Bool
+    let completeColor: Color
     let onComplete: () -> Void
     let onDelete: () -> Void
     let onSwipeActiveChanged: (Bool) -> Void
@@ -85,8 +88,8 @@ struct TaskRowSwipeGesture: ViewModifier {
     @ViewBuilder
     private var swipeBackground: some View {
         if swipeDirection == .right {
-            // Complete action — plain green background
-            Color.green.opacity(backgroundOpacity(offset: swipeOffset))
+            // Complete action — accent color background
+            completeColor.opacity(backgroundOpacity(offset: swipeOffset))
         } else if swipeDirection == .left {
             // Delete action (red background, trash icon)
             Color.red.opacity(backgroundOpacity(offset: swipeOffset))

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

@@ -3,6 +3,8 @@ import SwiftUI
 struct TaskRowView: View {
     let task: TaskItem
     let taskID: UUID
+    let index: Int
+    let totalTasks: Int
     let isSelected: Bool
     let onToggle: (TaskItem) -> Void
     let onTitleChange: (TaskItem, String) -> Void
@@ -17,6 +19,7 @@ struct TaskRowView: View {
     @State private var swipeOffset: CGFloat = 0
     @State private var swipeDirection: TaskRowSwipeGesture.SwipeDirection = .none
     @State private var isSwipeTriggered: Bool = false
+    @State private var cachedAccentColor: Color = .clear
 
     init(
         task: TaskItem,
@@ -35,6 +38,8 @@ struct TaskRowView: View {
     ) {
         self.task = task
         self.taskID = taskID
+        self.index = index
+        self.totalTasks = totalTasks
         self.isSelected = isSelected
         self.onToggle = onToggle
         self.onTitleChange = onTitleChange
@@ -95,15 +100,32 @@ struct TaskRowView: View {
             }
         }
         .background(cardBackground)
-        .clipShape(RoundedRectangle(cornerRadius: task.isCompleted ? 0 : 14))
+        .clipShape(
+            UnevenRoundedRectangle(
+                topLeadingRadius: 0, bottomLeadingRadius: 0,
+                bottomTrailingRadius: task.isCompleted ? 0 : 14,
+                topTrailingRadius: task.isCompleted ? 0 : 14
+            )
+        )
         .overlay {
             if isSelected && !task.isCompleted {
-                RoundedRectangle(cornerRadius: 14)
-                    .strokeBorder(Color.accentColor, lineWidth: 2)
+                UnevenRoundedRectangle(
+                    topLeadingRadius: 0, bottomLeadingRadius: 0,
+                    bottomTrailingRadius: 14, topTrailingRadius: 14
+                )
+                .strokeBorder(Color.accentColor, lineWidth: 2)
+            }
+        }
+        .overlay(alignment: .leading) {
+            if !task.isCompleted {
+                Rectangle()
+                    .fill(cachedAccentColor)
+                    .frame(width: 8)
             }
         }
         .onAppear {
             editingTitle = task.title
+            cachedAccentColor = computeAccentColor()
         }
         .onChange(of: editingTitle) {
             guard !task.isCompleted else { return }
@@ -114,6 +136,9 @@ struct TaskRowView: View {
                 editingTitle = newValue
             }
         }
+        .onChange(of: "\(index)-\(totalTasks)") { _, _ in
+            cachedAccentColor = computeAccentColor()
+        }
         .taskSwipeGesture(
             isActive: true,
             isEditing: isCurrentlyEditing,
@@ -121,10 +146,44 @@ struct TaskRowView: View {
             swipeOffset: $swipeOffset,
             swipeDirection: $swipeDirection,
             isTriggered: $isSwipeTriggered,
+            completeColor: cachedAccentColor,
             onComplete: { onToggle(task) },
             onDelete: { onDelete(task) }
         )
-        .clipShape(RoundedRectangle(cornerRadius: task.isCompleted ? 0 : 14))
+        .clipShape(
+            UnevenRoundedRectangle(
+                topLeadingRadius: 0, bottomLeadingRadius: 0,
+                bottomTrailingRadius: task.isCompleted ? 0 : 14,
+                topTrailingRadius: task.isCompleted ? 0 : 14
+            )
+        )
+    }
+
+    private func computeAccentColor() -> Color {
+        guard !task.isCompleted else { return .clear }
+        guard totalTasks > 1 else { return Color(hue: 0.98, saturation: 0.85, brightness: 1.0) }
+
+        // Gradient: coral/red → pink/magenta → purple/blue (matches macOS)
+        let progress = Double(index) / Double(totalTasks - 1)
+        let topColor = Color(hue: 0.98, saturation: 0.85, brightness: 1.0)
+        let midColor = Color(hue: 0.88, saturation: 0.75, brightness: 0.95)
+        let bottomColor = Color(hue: 0.72, saturation: 0.65, brightness: 0.85)
+
+        if progress < 0.5 {
+            return interpolateColor(from: topColor, to: midColor, progress: progress * 2.0)
+        } else {
+            return interpolateColor(from: midColor, to: bottomColor, progress: (progress - 0.5) * 2.0)
+        }
+    }
+
+    private func interpolateColor(from: Color, to: Color, progress: Double) -> Color {
+        let fromHSB = PlatformColor(from).hsba
+        let toHSB = PlatformColor(to).hsba
+        return Color(
+            hue: fromHSB.hue + (toHSB.hue - fromHSB.hue) * progress,
+            saturation: fromHSB.saturation + (toHSB.saturation - fromHSB.saturation) * progress,
+            brightness: fromHSB.brightness + (toHSB.brightness - fromHSB.brightness) * progress
+        )
     }
 
     @ViewBuilder