mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:05:19 +00:00
Swiping to close the sidebar (#678)
* enabled drag gesture to close the sidebar view * removed extraneous onChanged block * Fix sidebar swipe gestures to work with ScrollView Improvements: - Use simultaneousGesture() instead of gesture() to work alongside ScrollView - Add horizontal-only detection (width > height * 1.5) to prevent interfering with vertical scrolling - Add visual feedback during drag with sidebarDragOffset updates - Add 20pt right edge zone for easier swipe-to-open activation (iOS-native behavior) - Lower threshold for edge swipe (-50pt instead of -100pt) Both swipe-to-open and swipe-to-close now work reliably: - Swipe left from anywhere (or from right edge) to open sidebar - Swipe right on sidebar to close it - Vertical scrolling unaffected * Fix sidebar drag offset direction Changed offset calculation from: showSidebar ? -dragOffset : width - dragOffset To: showSidebar ? dragOffset : width + dragOffset This fixes the issue where dragging right to close the sidebar would make it fly to the left side of the screen before closing. Now the sidebar correctly follows the finger during drag: - When closing: moves right (toward off-screen) - When opening: moves left (toward on-screen) * Optimize sidebar drag performance for smooth 60fps Performance improvements: - Remove .animation() modifier that was conflicting with drag updates - Use Transaction with disablesAnimations during drag for instant updates - Throttle state updates to only fire when offset changes >2pt - Always render sidebar (avoid conditional view creation overhead) - Only animate on gesture end, not during drag This eliminates the lag/jank during swipe by ensuring: 1. No animation conflicts during manual drag 2. Direct offset updates follow finger immediately 3. Stable view hierarchy (no conditional rendering) 4. Reduced state update frequency The sidebar now feels buttery smooth at 60fps. --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
+130
-33
@@ -129,6 +129,45 @@ struct ContentView: View {
|
||||
)
|
||||
}
|
||||
|
||||
// Right edge swipe zone for easier sidebar opening (iOS-native behavior)
|
||||
if !showSidebar {
|
||||
HStack {
|
||||
Spacer()
|
||||
Color.clear
|
||||
.frame(width: 20)
|
||||
.contentShape(Rectangle())
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 5)
|
||||
.onChanged { value in
|
||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||
if translationWidth < 0 {
|
||||
let newOffset = max(translationWidth, -300)
|
||||
if abs(newOffset - sidebarDragOffset) > 2 {
|
||||
var transaction = Transaction()
|
||||
transaction.disablesAnimations = true
|
||||
withTransaction(transaction) {
|
||||
sidebarDragOffset = newOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onEnded { value in
|
||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||
let velocity = value.velocity.width.isNaN ? 0 : value.velocity.width
|
||||
withAnimation(.easeOut(duration: TransportConfig.uiAnimationMediumSeconds)) {
|
||||
if translationWidth < -50 || velocity < -300 {
|
||||
showSidebar = true
|
||||
sidebarDragOffset = 0
|
||||
} else {
|
||||
sidebarDragOffset = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.allowsHitTesting(true)
|
||||
}
|
||||
|
||||
// Sidebar overlay
|
||||
HStack(spacing: 0) {
|
||||
// Tap to dismiss area
|
||||
@@ -141,31 +180,19 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Only render sidebar content when it's visible or animating
|
||||
if showSidebar || sidebarDragOffset != 0 {
|
||||
sidebarView
|
||||
#if os(macOS)
|
||||
.frame(width: min(300, max(0, geometry.size.width.isNaN ? 300 : geometry.size.width) * 0.4))
|
||||
#else
|
||||
.frame(width: max(0, geometry.size.width.isNaN ? 300 : geometry.size.width) * 0.7)
|
||||
#endif
|
||||
.transition(.move(edge: .trailing))
|
||||
} else {
|
||||
// Empty placeholder when hidden
|
||||
Color.clear
|
||||
#if os(macOS)
|
||||
.frame(width: min(300, max(0, geometry.size.width.isNaN ? 300 : geometry.size.width) * 0.4))
|
||||
#else
|
||||
.frame(width: max(0, geometry.size.width.isNaN ? 300 : geometry.size.width) * 0.7)
|
||||
#endif
|
||||
}
|
||||
// Always render sidebar to avoid layout recalculation during drag
|
||||
sidebarView
|
||||
#if os(macOS)
|
||||
.frame(width: min(300, max(0, geometry.size.width.isNaN ? 300 : geometry.size.width) * 0.4))
|
||||
#else
|
||||
.frame(width: max(0, geometry.size.width.isNaN ? 300 : geometry.size.width) * 0.7)
|
||||
#endif
|
||||
}
|
||||
.offset(x: {
|
||||
let dragOffset = sidebarDragOffset.isNaN ? 0 : sidebarDragOffset
|
||||
let width = geometry.size.width.isNaN ? 0 : max(0, geometry.size.width)
|
||||
return showSidebar ? -dragOffset : width - dragOffset
|
||||
return showSidebar ? dragOffset : width + dragOffset
|
||||
}())
|
||||
.animation(.easeInOut(duration: TransportConfig.uiAnimationSidebarSeconds), value: showSidebar)
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
@@ -921,9 +948,52 @@ struct ContentView: View {
|
||||
.id(viewModel.allPeers.map { "\($0.peerID)-\($0.isConnected)" }.joined())
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.background(backgroundColor)
|
||||
Spacer()
|
||||
}
|
||||
.background(backgroundColor)
|
||||
.simultaneousGesture(
|
||||
DragGesture(minimumDistance: 10)
|
||||
.onChanged { value in
|
||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||
let translationHeight = value.translation.height.isNaN ? 0 : value.translation.height
|
||||
|
||||
// Only handle horizontal drags on the sidebar
|
||||
guard abs(translationWidth) > abs(translationHeight) * 1.5 else { return }
|
||||
|
||||
if translationWidth > 0 {
|
||||
let newOffset = min(translationWidth, 300)
|
||||
if abs(newOffset - sidebarDragOffset) > 2 {
|
||||
var transaction = Transaction()
|
||||
transaction.disablesAnimations = true
|
||||
withTransaction(transaction) {
|
||||
sidebarDragOffset = newOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onEnded { value in
|
||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||
let translationHeight = value.translation.height.isNaN ? 0 : value.translation.height
|
||||
let velocity = value.velocity.width.isNaN ? 0 : value.velocity.width
|
||||
|
||||
// Only handle if drag is predominantly horizontal
|
||||
guard abs(translationWidth) > abs(translationHeight) * 1.5 else {
|
||||
sidebarDragOffset = 0
|
||||
return
|
||||
}
|
||||
|
||||
withAnimation(.easeOut(duration: TransportConfig.uiAnimationMediumSeconds)) {
|
||||
if translationWidth > 100 || (translationWidth > 50 && velocity > 500) {
|
||||
showSidebar = false
|
||||
sidebarDragOffset = 0
|
||||
} else {
|
||||
sidebarDragOffset = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,29 +1009,56 @@ struct ContentView: View {
|
||||
}
|
||||
.background(backgroundColor)
|
||||
.foregroundColor(textColor)
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.simultaneousGesture(
|
||||
DragGesture(minimumDistance: 10)
|
||||
.onChanged { value in
|
||||
let translation = value.translation.width.isNaN ? 0 : value.translation.width
|
||||
if !showSidebar && translation < 0 {
|
||||
sidebarDragOffset = max(translation, -300)
|
||||
} else if showSidebar && translation > 0 {
|
||||
sidebarDragOffset = min(-300 + translation, 0)
|
||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||
let translationHeight = value.translation.height.isNaN ? 0 : value.translation.height
|
||||
|
||||
// Only handle if drag is predominantly horizontal (prevents interfering with scroll)
|
||||
guard abs(translationWidth) > abs(translationHeight) * 1.5 else { return }
|
||||
|
||||
if !showSidebar && translationWidth < 0 {
|
||||
let newOffset = max(translationWidth, -300)
|
||||
if abs(newOffset - sidebarDragOffset) > 2 {
|
||||
var transaction = Transaction()
|
||||
transaction.disablesAnimations = true
|
||||
withTransaction(transaction) {
|
||||
sidebarDragOffset = newOffset
|
||||
}
|
||||
}
|
||||
} else if showSidebar && translationWidth > 0 {
|
||||
let newOffset = min(-300 + translationWidth, 0)
|
||||
if abs(newOffset - sidebarDragOffset) > 2 {
|
||||
var transaction = Transaction()
|
||||
transaction.disablesAnimations = true
|
||||
withTransaction(transaction) {
|
||||
sidebarDragOffset = newOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onEnded { value in
|
||||
let translation = value.translation.width.isNaN ? 0 : value.translation.width
|
||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||
let translationHeight = value.translation.height.isNaN ? 0 : value.translation.height
|
||||
let velocity = value.velocity.width.isNaN ? 0 : value.velocity.width
|
||||
|
||||
// Only handle if drag is predominantly horizontal
|
||||
guard abs(translationWidth) > abs(translationHeight) * 1.5 else {
|
||||
sidebarDragOffset = 0
|
||||
return
|
||||
}
|
||||
|
||||
withAnimation(.easeOut(duration: TransportConfig.uiAnimationMediumSeconds)) {
|
||||
if !showSidebar {
|
||||
if translation < -100 || (translation < -50 && velocity < -500) {
|
||||
if translationWidth < -100 || (translationWidth < -50 && velocity < -500) {
|
||||
showSidebar = true
|
||||
sidebarDragOffset = 0
|
||||
} else {
|
||||
sidebarDragOffset = 0
|
||||
}
|
||||
} else {
|
||||
if translation > 100 || (translation > 50 && velocity > 500) {
|
||||
if translationWidth > 100 || (translationWidth > 50 && velocity > 500) {
|
||||
showSidebar = false
|
||||
sidebarDragOffset = 0
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user