diff --git a/Strand/Liquid/LiquidTodayView.swift b/Strand/Liquid/LiquidTodayView.swift index 4791434a91..8532eb16a9 100644 --- a/Strand/Liquid/LiquidTodayView.swift +++ b/Strand/Liquid/LiquidTodayView.swift @@ -265,13 +265,14 @@ struct LiquidTodayView: View { } ) } - /// Horizontal swipe between days (left = older, right = newer), clamped to [today, earliest]. + /// Horizontal swipe between days (right = older, left = newer — `TodayView.daySwipeDelta`, #2378), + /// clamped to [today, earliest]. private var daySwipeGesture: some Gesture { DragGesture(minimumDistance: 24) .onEnded { value in let dx = value.translation.width, dy = value.translation.height guard abs(dx) > abs(dy) * 1.5, abs(dx) > 50 else { return } - let delta = dx < 0 ? 1 : -1 + let delta = TodayView.daySwipeDelta(dx: dx) let next = Self.clampedDayOffset(current: selectedDayOffset, delta: delta, maxOffset: earliestDayOffset) guard next != selectedDayOffset else { return } diff --git a/Strand/Screens/TodayView.swift b/Strand/Screens/TodayView.swift index eccee5c77c..0fd6604e49 100644 --- a/Strand/Screens/TodayView.swift +++ b/Strand/Screens/TodayView.swift @@ -705,6 +705,20 @@ struct TodayView: View { return min(upper, max(0, current + delta)) } + /// #2378 - the day step a horizontal swipe of `dx` points asks for: +1 OLDER, -1 NEWER. + /// + /// Rightward (dx > 0) is OLDER and leftward is NEWER, which is direct manipulation — dragging the + /// content left brings the page to its right, the later day, into view — and the direction the + /// Kotlin twin already takes (`dayNavSwipeTarget`, pinned by `DayNavTest`). Apple ran the opposite + /// way in both shells, so the same gesture moved the day backwards here and forwards there. + /// + /// Pure and shared by both Apple shells so the direction is pinned by a test rather than living + /// twice inside gesture closures, which is how the two platforms drifted apart unnoticed. + /// Mirror EXACTLY in Kotlin. + static func daySwipeDelta(dx: CGFloat) -> Int { + dx > 0 ? 1 : -1 + } + /// #16 - whole days-back offset for a date chosen in the day-nav picker, measured from the LOGICAL day /// (not raw Date()). Pure + unit-testable so the 00:00-04:00 rollover case is locked: in that window the /// logical day is the PREVIOUS calendar day, so anchoring the offset here (rather than on raw Date()) @@ -1251,8 +1265,8 @@ struct TodayView: View { let dy = value.translation.height // Horizontal-dominant and far enough to count as a deliberate day flip. guard abs(dx) > abs(dy) * 1.5, abs(dx) > 50 else { return } - // Swipe LEFT (dx < 0) -> OLDER day (+1 offset); swipe RIGHT -> NEWER day (-1 offset). - let delta = dx < 0 ? 1 : -1 + // Swipe RIGHT (dx > 0) -> OLDER day (+1 offset); swipe LEFT -> NEWER day (-1 offset). + let delta = Self.daySwipeDelta(dx: dx) let next = Self.clampedDayOffset(current: selectedDayOffset, delta: delta, maxOffset: earliestDayOffset) guard next != selectedDayOffset else { return } diff --git a/StrandTests/TodayDayNavClampTests.swift b/StrandTests/TodayDayNavClampTests.swift index 61270e8cc8..e6f3d80e48 100644 --- a/StrandTests/TodayDayNavClampTests.swift +++ b/StrandTests/TodayDayNavClampTests.swift @@ -93,4 +93,30 @@ final class TodayDayNavClampTests: XCTestCase { let picked = cal.date(from: DateComponents(year: 2026, month: 6, day: 24))! XCTAssertEqual(TodayView.pickedDayOffset(pickedDate: picked, anchorLogicalDay: logical), 4) } + + // MARK: - daySwipeDelta (#2378) + + /// A rightward drag reveals the past, so it steps OLDER (+1). Twin of the Kotlin + /// `DayNavTest.rightwardSwipeGoesOlder`, which pins the same direction for `dayNavSwipeTarget`. + func testRightwardSwipeStepsOlder() { + XCTAssertEqual(TodayView.daySwipeDelta(dx: 120), 1) + XCTAssertEqual(TodayView.daySwipeDelta(dx: 51), 1) + } + + /// A leftward drag brings the later day in from the right, so it steps NEWER (-1). Twin of the + /// Kotlin `DayNavTest.leftwardSwipeGoesNewerClampedAtToday` (the clamp itself is covered above). + func testLeftwardSwipeStepsNewer() { + XCTAssertEqual(TodayView.daySwipeDelta(dx: -120), -1) + XCTAssertEqual(TodayView.daySwipeDelta(dx: -51), -1) + } + + /// Composed with the clamp: from today a leftward swipe cannot reach a future day, and a rightward + /// one reaches yesterday — the two halves the gesture closures actually chain. + func testSwipeDeltaComposedWithClamp() { + let older = TodayView.daySwipeDelta(dx: 200) + XCTAssertEqual(TodayView.clampedDayOffset(current: 0, delta: older, maxOffset: 30), 1) + let newer = TodayView.daySwipeDelta(dx: -200) + XCTAssertEqual(TodayView.clampedDayOffset(current: 0, delta: newer, maxOffset: 30), 0) + XCTAssertEqual(TodayView.clampedDayOffset(current: 5, delta: newer, maxOffset: 30), 4) + } }