commit 0cbbf73afb234730b23a1229d16969827c2422eb
parent 3593ebb10a03e6a706c7afc55ccb7c927bb8d02b
Author: Michael Camilleri <[email protected]>
Date: Thu, 5 Feb 2026 20:21:02 +0900
Task creation apparently working
Co-Authored-By: Claude 4.5 Sonnet <[email protected]>
Diffstat:
7 files changed, 234 insertions(+), 101 deletions(-)
diff --git a/Listless.xcodeproj/project.pbxproj b/Listless.xcodeproj/project.pbxproj
@@ -11,6 +11,7 @@
15B71073767FB4766A6BA2BE /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41D6E0CED14D79F31C45062 /* HoverCursorModifier.swift */; };
3ABE52A15C2059D8D5570528 /* TaskStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEE364304587D280C5672 /* TaskStore.swift */; };
42E4CDE1D17463554CC4F41F /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537A913AC421BAEF60D26D9C /* TaskListView.swift */; };
+ 585A9E665730E3C4FD32AE8D /* KeyboardNavigationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F99C67C30185163A51DCD268 /* KeyboardNavigationTests.swift */; };
5B60B409CE4BA668DB30A65D /* Listless.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C093494053E6C348F245D4EC /* Listless.xcdatamodeld */; };
614FCCA450EC0BFFD8B40640 /* ListlessMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */; };
6C252050E62AED3A0A684EBF /* KeyboardNavigationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7B37EF6A2389656D105FF8 /* KeyboardNavigationModifier.swift */; };
@@ -33,6 +34,16 @@
FEC96DAA2BF8BB5D6D8504EB /* HoverCursorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A3509AD32A54434BCC8017 /* HoverCursorModifier.swift */; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ CBF0AD7D47832BDE6A4FB05B /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 3256C2BF8F1DAF371DA32120 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 0FB4F07A37999BBC6DFE4DBB;
+ remoteInfo = "Listless macOS";
+ };
+/* End PBXContainerItemProxy section */
+
/* Begin PBXFileReference section */
007D500D2EFF3E66B780ADE0 /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
01E141436176F83594E2F26B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
@@ -42,6 +53,7 @@
1B3D0B4710C7A0EE4F227851 /* TaskRowDragGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowDragGesture.swift; sourceTree = "<group>"; };
1DA467DF2E59BDBE6EEF6A7D /* ListlessMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListlessMacApp.swift; sourceTree = "<group>"; };
3313FEDB101EECA4B344EEF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
+ 3F0FC3AFB591F222FB63C13B /* Listless macOS UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Listless macOS UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
48AE1AE43296C1692FA6F755 /* PlatformScrollIndicatorsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformScrollIndicatorsModifier.swift; sourceTree = "<group>"; };
537A913AC421BAEF60D26D9C /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
5B0E22B8F7B2B7283CAF749E /* Listless macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Listless macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -60,6 +72,7 @@
D41D6E0CED14D79F31C45062 /* HoverCursorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverCursorModifier.swift; sourceTree = "<group>"; };
DC3DEE364304587D280C5672 /* TaskStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskStore.swift; sourceTree = "<group>"; };
EF6B0195EA0176B72DE9B092 /* PlatformTextFieldWidthModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformTextFieldWidthModifier.swift; sourceTree = "<group>"; };
+ F99C67C30185163A51DCD268 /* KeyboardNavigationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardNavigationTests.swift; sourceTree = "<group>"; };
FBB8A3BEB346267B30B4675F /* TaskItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskItem.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -79,6 +92,7 @@
isa = PBXGroup;
children = (
126108860D7878DDC3BECC4B /* Listless iOS.app */,
+ 3F0FC3AFB591F222FB63C13B /* Listless macOS UITests.xctest */,
5B0E22B8F7B2B7283CAF749E /* Listless macOS.app */,
);
name = Products;
@@ -128,6 +142,14 @@
path = ListlessiOS;
sourceTree = "<group>";
};
+ A4CAFE5C963E75C444972E10 /* ListlessMacUITests */ = {
+ isa = PBXGroup;
+ children = (
+ F99C67C30185163A51DCD268 /* KeyboardNavigationTests.swift */,
+ );
+ path = ListlessMacUITests;
+ sourceTree = "<group>";
+ };
AA563E991F69ED14DCD9A1AB /* Infrastructure */ = {
isa = PBXGroup;
children = (
@@ -173,6 +195,7 @@
58051CBDE2390F9E13647235 /* Listless */,
954AAB8DDFF6E6D6FD6A0A2C /* ListlessiOS */,
D5B197AFF26144948D032299 /* ListlessMac */,
+ A4CAFE5C963E75C444972E10 /* ListlessMacUITests */,
3936BDEE64D16E6C4C85B3DD /* Products */,
);
sourceTree = "<group>";
@@ -214,6 +237,24 @@
productReference = 126108860D7878DDC3BECC4B /* Listless iOS.app */;
productType = "com.apple.product-type.application";
};
+ 4DDF5F8F5508AA35A41BECAB /* Listless macOS UITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9FF482EB568895AF0B3F35CC /* Build configuration list for PBXNativeTarget "Listless macOS UITests" */;
+ buildPhases = (
+ 34DA2E4A88D1AFCE78FCE279 /* Sources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 180D7333E25210955007E660 /* PBXTargetDependency */,
+ );
+ name = "Listless macOS UITests";
+ packageProductDependencies = (
+ );
+ productName = "Listless macOS UITests";
+ productReference = 3F0FC3AFB591F222FB63C13B /* Listless macOS UITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -231,6 +272,11 @@
DevelopmentTeam = 7TD7PZBNXP;
ProvisioningStyle = Automatic;
};
+ 4DDF5F8F5508AA35A41BECAB = {
+ DevelopmentTeam = 7TD7PZBNXP;
+ ProvisioningStyle = Automatic;
+ TestTargetID = 0FB4F07A37999BBC6DFE4DBB;
+ };
};
};
buildConfigurationList = CAACA40A09D5F78ECE7A0EDF /* Build configuration list for PBXProject "Listless" */;
@@ -249,11 +295,20 @@
targets = (
34A03D42B91730DEAC2EBD8E /* Listless iOS */,
0FB4F07A37999BBC6DFE4DBB /* Listless macOS */,
+ 4DDF5F8F5508AA35A41BECAB /* Listless macOS UITests */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
+ 34DA2E4A88D1AFCE78FCE279 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 585A9E665730E3C4FD32AE8D /* KeyboardNavigationTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
409D108909CBEC2F69B56D0E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -294,7 +349,34 @@
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ 180D7333E25210955007E660 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 0FB4F07A37999BBC6DFE4DBB /* Listless macOS */;
+ targetProxy = CBF0AD7D47832BDE6A4FB05B /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin XCBuildConfiguration section */
+ 086CB8FDA1EC9D4A988C382E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEVELOPMENT_TEAM = 7TD7PZBNXP;
+ GENERATE_INFOPLIST_FILE = YES;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = net.inqk.listless.mac.uitests;
+ SDKROOT = macosx;
+ TEST_TARGET_NAME = "Listless macOS";
+ };
+ name = Release;
+ };
2D4E3CC5FF8E6299F754CCFC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -313,6 +395,25 @@
};
name = Debug;
};
+ 31AB47694981749AC7C9E541 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEVELOPMENT_TEAM = 7TD7PZBNXP;
+ GENERATE_INFOPLIST_FILE = YES;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = net.inqk.listless.mac.uitests;
+ SDKROOT = macosx;
+ TEST_TARGET_NAME = "Listless macOS";
+ };
+ name = Debug;
+ };
A5A377EA0FE470803E2B6BA1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -500,6 +601,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
+ 9FF482EB568895AF0B3F35CC /* Build configuration list for PBXNativeTarget "Listless macOS UITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 31AB47694981749AC7C9E541 /* Debug */,
+ 086CB8FDA1EC9D4A988C382E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Debug;
+ };
B7581C7E1D90658668F3AAE2 /* Build configuration list for PBXNativeTarget "Listless iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/Listless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme b/Listless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme
@@ -39,7 +39,20 @@
</BuildableReference>
</MacroExpansion>
<Testables>
+ <TestableReference
+ skipped = "NO"
+ parallelizable = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "4DDF5F8F5508AA35A41BECAB"
+ BuildableName = "Listless macOS UITests.xctest"
+ BlueprintName = "Listless macOS UITests"
+ ReferencedContainer = "container:Listless.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
</Testables>
+ <CommandLineArguments>
+ </CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
diff --git a/Listless/Views/TaskListView.swift b/Listless/Views/TaskListView.swift
@@ -21,6 +21,7 @@ struct TaskListView: View {
@State private var draggedTaskID: UUID?
@State private var visualOrder: [UUID]?
@State private var editingTaskID: UUID?
+ @State private var justCreatedTaskID: UUID?
init(store: TaskStore = TaskStore()) {
_store = State(wrappedValue: store)
@@ -38,19 +39,20 @@ struct TaskListView: View {
}
ForEach(completedTasks) { task in
+ let taskID = task.id
TaskRowView(
task: task,
- taskID: task.id,
- isSelected: selectedTaskID == task.id,
- isEditing: editingTaskID == task.id,
+ taskID: taskID,
+ isSelected: selectedTaskID == taskID,
+ isEditing: editingTaskID == taskID,
focusedField: $focusedField,
onToggle: toggleCompletion(_:),
onSubmit: handleSubmit(_:),
onTitleChange: updateTitle(_:_:),
onDelete: deleteTask(_:),
- onSelect: { selectTask(task.id) },
- onStartEdit: { startEditing(task.id) },
- onEndEdit: { endEditing() }
+ onSelect: { selectTask(taskID) },
+ onStartEdit: { startEditing(taskID) },
+ onEndEdit: { endEditing(taskID) }
)
}
@@ -60,26 +62,26 @@ struct TaskListView: View {
}
ForEach(displayActiveTasks) { task in
+ let taskID = task.id
TaskRowView(
task: task,
- taskID: task.id,
- isSelected: selectedTaskID == task.id,
- isEditing: editingTaskID == task.id,
+ taskID: taskID,
+ isSelected: selectedTaskID == taskID,
+ isEditing: editingTaskID == taskID,
focusedField: $focusedField,
onToggle: toggleCompletion(_:),
onSubmit: handleSubmit(_:),
onTitleChange: updateTitle(_:_:),
onDelete: deleteTask(_:),
- onSelect: { selectTask(task.id) },
- onStartEdit: { startEditing(task.id) },
- onEndEdit: { endEditing() }
+ onSelect: { selectTask(taskID) },
+ onStartEdit: { startEditing(taskID) },
+ onEndEdit: { endEditing(taskID) }
+ )
+ .taskDragGesture(
+ isActive: !task.isCompleted,
+ taskID: task.id,
+ onDragStart: { startDrag(taskID: task.id) }
)
- .onDrag {
- startDrag(taskID: task.id)
- return NSItemProvider(object: task.id.uuidString as NSString)
- } preview: {
- Color.clear.frame(width: 1, height: 1)
- }
.overlay {
if draggedTaskID != nil && draggedTaskID != task.id {
VStack(spacing: 0) {
@@ -140,6 +142,7 @@ struct TaskListView: View {
.focusable()
.focused($focusedField, equals: .scrollView)
.focusEffectDisabled()
+ .accessibilityIdentifier("task-list-scrollview")
.keyboardNavigation(
onUpArrow: navigateUp,
onDownArrow: navigateDown,
@@ -180,13 +183,33 @@ struct TaskListView: View {
}
private func createTaskAndFocus() {
+ // Clear any lingering drag state
+ draggedTaskID = nil
+ visualOrder = nil
+
+ // Create Core Data task
let task = store.createTask(title: "")
+
+ // Protect from immediate deletion
+ justCreatedTaskID = task.id
selectedTaskID = task.id
- startEditing(task.id)
+
+ // Set editing state to trigger TextField render
+ editingTaskID = task.id
+
+ // Wait for view to update, then focus the TextField
+ DispatchQueue.main.async {
+ self.focusedField = .task(task.id)
+ // Clear the just-created protection once focused
+ self.justCreatedTaskID = nil
+ }
}
private func handleBackgroundTap() {
- if focusedField != nil || selectedTaskID != nil {
+ // Check if a task is focused (not just scrollView)
+ let isTaskFocused = if case .task = focusedField { true } else { false }
+
+ if isTaskFocused || selectedTaskID != nil {
focusScrollView()
selectedTaskID = nil
} else {
@@ -208,7 +231,14 @@ struct TaskListView: View {
}
private func deleteIfEmpty(taskID: UUID) {
- guard let task = tasks.first(where: { $0.id == taskID }) else { return }
+ // Don't delete tasks that were just created
+ if taskID == justCreatedTaskID {
+ return
+ }
+
+ guard let task = tasks.first(where: { $0.id == taskID }) else {
+ return
+ }
let trimmedTitle = task.title.trimmingCharacters(in: .whitespacesAndNewlines)
guard trimmedTitle.isEmpty else { return }
deleteTask(task)
@@ -221,6 +251,11 @@ struct TaskListView: View {
private func updateTitle(_ task: TaskItem, _ title: String) {
guard task.title != title else { return }
store.update(taskID: task.id, title: title)
+
+ // Clear the justCreated flag once user starts typing
+ if task.id == justCreatedTaskID && !title.isEmpty {
+ justCreatedTaskID = nil
+ }
}
private func toggleCompletion(_ task: TaskItem) {
@@ -247,7 +282,9 @@ struct TaskListView: View {
}
private func navigateUp() -> KeyPress.Result {
- guard focusedField == .scrollView else { return .ignored }
+ guard focusedField == .scrollView else {
+ return .ignored
+ }
guard let currentID = selectedTaskID else {
selectedTaskID = activeTasks.last?.id
@@ -302,8 +339,12 @@ struct TaskListView: View {
}
private func unfocusTextField() -> KeyPress.Result {
- guard case .task = focusedField else { return .ignored }
- endEditing()
+ guard case .task = focusedField else {
+ return .ignored
+ }
+ if let editingID = editingTaskID {
+ endEditing(editingID)
+ }
focusScrollView()
return .handled
}
@@ -311,7 +352,11 @@ struct TaskListView: View {
// MARK: - Focus Management
private func focusScrollView() {
- focusedField = .scrollView
+ // Try clearing focus first, then setting to scrollView
+ focusedField = nil
+ DispatchQueue.main.async {
+ self.focusedField = .scrollView
+ }
}
private func focusTextField(_ taskID: UUID) {
@@ -323,10 +368,13 @@ struct TaskListView: View {
focusedField = .task(taskID)
}
- private func endEditing() {
- if let editingID = editingTaskID {
- deleteIfEmpty(taskID: editingID)
+ private func endEditing(_ taskID: UUID) {
+ // Only clear editingTaskID if it matches this task
+ guard editingTaskID == taskID else {
+ return
}
+
+ deleteIfEmpty(taskID: taskID)
editingTaskID = nil
}
diff --git a/Listless/Views/TaskRowView.swift b/Listless/Views/TaskRowView.swift
@@ -54,6 +54,8 @@ struct TaskRowView: View {
.frame(width: 20, height: 20)
}
.buttonStyle(.borderless)
+ .accessibilityIdentifier("task-checkbox")
+ .accessibilityValue(task.isCompleted ? "checkmark.circle.fill" : "circle")
if isEditing {
TextField("New task", text: $editingTitle, axis: .vertical)
@@ -66,6 +68,7 @@ struct TaskRowView: View {
}
.frame(maxWidth: .infinity, alignment: .leading)
.disabled(task.isCompleted)
+ .accessibilityIdentifier("task-textfield")
} else {
HStack(spacing: 0) {
Text(task.title.isEmpty ? "New task" : task.title)
@@ -78,6 +81,7 @@ struct TaskRowView: View {
onStartEdit()
}
}
+ .accessibilityIdentifier("task-text-\(task.title)")
Spacer(minLength: 0)
}
diff --git a/ListlessMac/ListlessMacApp.swift b/ListlessMac/ListlessMacApp.swift
@@ -2,7 +2,13 @@ import SwiftUI
@main
struct ListlessMacApp: App {
- private let persistenceController = PersistenceController.shared
+ private let persistenceController: PersistenceController
+
+ init() {
+ // Use in-memory storage during UI tests
+ let isUITesting = ProcessInfo.processInfo.arguments.contains("UI_TESTING")
+ persistenceController = isUITesting ? PersistenceController(inMemory: true) : .shared
+ }
var body: some Scene {
WindowGroup {
diff --git a/ListlessMac/Views/TaskRowDragGesture.swift b/ListlessMac/Views/TaskRowDragGesture.swift
@@ -5,18 +5,12 @@ extension View {
func taskDragGesture(
isActive: Bool,
taskID: UUID,
- taskTitle: String,
- onDragStart: @escaping () -> Void,
- onDragEnd: @escaping () -> Void,
- onDrop: @escaping (UUID) -> Void
+ onDragStart: @escaping () -> Void
) -> some View {
self.modifier(TaskRowDragGesture(
isActive: isActive,
taskID: taskID,
- taskTitle: taskTitle,
- onDragStart: onDragStart,
- onDragEnd: onDragEnd,
- onDrop: onDrop
+ onDragStart: onDragStart
))
}
}
@@ -24,46 +18,37 @@ extension View {
struct TaskRowDragGesture: ViewModifier {
let isActive: Bool
let taskID: UUID
- let taskTitle: String
let onDragStart: () -> Void
- let onDragEnd: () -> Void
- let onDrop: (UUID) -> Void
+
+ @State private var dragStarted = false
func body(content: Content) -> some View {
if isActive {
content
.onDrag {
- onDragStart()
+ if !dragStarted {
+ dragStarted = true
+ onDragStart()
+ }
return NSItemProvider(object: taskID.uuidString as NSString)
} preview: {
- dragPreview
- }
- .dropDestination(for: String.self) { items, location in
- guard let droppedUUIDString = items.first,
- let droppedUUID = UUID(uuidString: droppedUUIDString) else {
- return false
- }
- DispatchQueue.main.async {
- onDrop(droppedUUID)
- onDragEnd()
- }
- return true
+ Color.clear.frame(width: 1, height: 1)
}
+ .simultaneousGesture(
+ DragGesture(minimumDistance: 2)
+ .onChanged { _ in
+ if !dragStarted {
+ dragStarted = true
+ onDragStart()
+ }
+ }
+ .onEnded { _ in
+ dragStarted = false
+ }
+ )
} else {
content
}
}
- private var dragPreview: some View {
- HStack(spacing: 12) {
- Image(systemName: "circle")
- .frame(width: 20, height: 20)
- Text(taskTitle.isEmpty ? "New task" : taskTitle)
- .font(.body)
- }
- .padding(.vertical, 8)
- .padding(.horizontal, 16)
- .background(Color(nsColor: .controlBackgroundColor))
- .cornerRadius(6)
- }
}
diff --git a/ListlessiOS/Views/TaskRowDragGesture.swift b/ListlessiOS/Views/TaskRowDragGesture.swift
@@ -5,18 +5,12 @@ extension View {
func taskDragGesture(
isActive: Bool,
taskID: UUID,
- taskTitle: String,
- onDragStart: @escaping () -> Void,
- onDragEnd: @escaping () -> Void,
- onDrop: @escaping (UUID) -> Void
+ onDragStart: @escaping () -> Void
) -> some View {
self.modifier(TaskRowDragGesture(
isActive: isActive,
taskID: taskID,
- taskTitle: taskTitle,
- onDragStart: onDragStart,
- onDragEnd: onDragEnd,
- onDrop: onDrop
+ onDragStart: onDragStart
))
}
}
@@ -24,10 +18,7 @@ extension View {
struct TaskRowDragGesture: ViewModifier {
let isActive: Bool
let taskID: UUID
- let taskTitle: String
let onDragStart: () -> Void
- let onDragEnd: () -> Void
- let onDrop: (UUID) -> Void
func body(content: Content) -> some View {
if isActive {
@@ -36,34 +27,10 @@ struct TaskRowDragGesture: ViewModifier {
onDragStart()
return NSItemProvider(object: taskID.uuidString as NSString)
} preview: {
- dragPreview
- }
- .dropDestination(for: String.self) { items, location in
- guard let droppedUUIDString = items.first,
- let droppedUUID = UUID(uuidString: droppedUUIDString) else {
- return false
- }
- DispatchQueue.main.async {
- onDrop(droppedUUID)
- onDragEnd()
- }
- return true
+ Color.clear.frame(width: 1, height: 1)
}
} else {
content
}
}
-
- private var dragPreview: some View {
- HStack(spacing: 12) {
- Image(systemName: "circle")
- .frame(width: 20, height: 20)
- Text(taskTitle.isEmpty ? "New task" : taskTitle)
- .font(.body)
- }
- .padding(.vertical, 8)
- .padding(.horizontal, 16)
- .background(Color(uiColor: .systemBackground))
- .cornerRadius(6)
- }
}