Fix send button tap responsiveness and sidebar drag jitter (#783)

Restructured ContentView layout to prevent sidebar from covering input box
and removed gesture conflicts that caused jitter during slow drags.

Changes:
- Moved sidebar overlay to only cover messages area, not input box
- Input box now always accessible below sidebar (not covered by overlay)
- Removed blocking drag gesture from mainChatView
- Changed sidebar gesture from simultaneousGesture to gesture for priority
- Removed animation-disabling transactions that amplified touch noise
- Removed 2pt threshold checks that caused visible jumps

Result: Send button taps immediately, sidebar slides smoothly without jitter.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
jack
2025-10-09 23:45:05 +02:00
committed by GitHub
co-authored by jack
parent 302c741d58
commit d994ccf012
+27 -91
View File
@@ -83,16 +83,23 @@ struct ContentView: View {
// MARK: - Body
var body: some View {
GeometryReader { geometry in
ZStack {
// Base layer - Main public chat (always visible)
mainChatView
VStack(spacing: 0) {
mainHeaderView
.onAppear { viewModel.currentColorScheme = colorScheme }
.onChange(of: colorScheme) { newValue in
viewModel.currentColorScheme = newValue
}
// Private chat slide-over
Divider()
// Messages area with overlays
GeometryReader { geometry in
ZStack {
// Base layer - public messages
messagesView(privatePeer: nil, isAtBottom: $isAtBottomPublic)
.background(backgroundColor)
// Private chat slide-over (full screen with own header/input)
if viewModel.selectedPrivateChatPeer != nil {
privateChatView
.frame(width: geometry.size.width)
@@ -130,7 +137,7 @@ struct ContentView: View {
}
// Right edge swipe zone for easier sidebar opening (iOS-native behavior)
if !showSidebar {
if !showSidebar && viewModel.selectedPrivateChatPeer == nil {
HStack {
Spacer()
Color.clear
@@ -142,15 +149,9 @@ struct ContentView: View {
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
@@ -168,7 +169,8 @@ struct ContentView: View {
.allowsHitTesting(true)
}
// Sidebar overlay
// Sidebar overlay (only over messages area, not input)
if viewModel.selectedPrivateChatPeer == nil {
HStack(spacing: 0) {
// Tap to dismiss area
Color.clear
@@ -195,6 +197,17 @@ struct ContentView: View {
}())
}
}
}
Divider()
// Input always accessible below sidebar (only in public chat)
if viewModel.selectedPrivateChatPeer == nil {
inputView
}
}
.background(backgroundColor)
.foregroundColor(textColor)
#if os(macOS)
.frame(minWidth: 600, minHeight: 400)
#endif
@@ -951,7 +964,7 @@ struct ContentView: View {
Spacer()
}
.background(backgroundColor)
.simultaneousGesture(
.gesture(
DragGesture(minimumDistance: 10)
.onChanged { value in
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
@@ -962,15 +975,9 @@ struct ContentView: View {
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
@@ -999,77 +1006,6 @@ struct ContentView: View {
// MARK: - View Components
private var mainChatView: some View {
VStack(spacing: 0) {
mainHeaderView
Divider()
messagesView(privatePeer: nil, isAtBottom: $isAtBottomPublic)
Divider()
inputView
}
.background(backgroundColor)
.foregroundColor(textColor)
.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 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 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 translationWidth < -100 || (translationWidth < -50 && velocity < -500) {
showSidebar = true
sidebarDragOffset = 0
} else {
sidebarDragOffset = 0
}
} else {
if translationWidth > 100 || (translationWidth > 50 && velocity > 500) {
showSidebar = false
sidebarDragOffset = 0
} else {
sidebarDragOffset = 0
}
}
}
}
)
}
private var privateChatView: some View {
HStack(spacing: 0) {
// Vertical separator bar