listless

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

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:
MListless.xcodeproj/project.pbxproj | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MListless.xcodeproj/xcshareddata/xcschemes/Listless macOS.xcscheme | 13+++++++++++++
MListless/Views/TaskListView.swift | 104++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
MListless/Views/TaskRowView.swift | 4++++
MListlessMac/ListlessMacApp.swift | 8+++++++-
MListlessMac/Views/TaskRowDragGesture.swift | 57+++++++++++++++++++++------------------------------------
MListlessiOS/Views/TaskRowDragGesture.swift | 39+++------------------------------------
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) - } }