mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 03:05:19 +00:00
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:
@@ -83,16 +83,23 @@ struct ContentView: View {
|
|||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
VStack(spacing: 0) {
|
||||||
ZStack {
|
mainHeaderView
|
||||||
// Base layer - Main public chat (always visible)
|
|
||||||
mainChatView
|
|
||||||
.onAppear { viewModel.currentColorScheme = colorScheme }
|
.onAppear { viewModel.currentColorScheme = colorScheme }
|
||||||
.onChange(of: colorScheme) { newValue in
|
.onChange(of: colorScheme) { newValue in
|
||||||
viewModel.currentColorScheme = newValue
|
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 {
|
if viewModel.selectedPrivateChatPeer != nil {
|
||||||
privateChatView
|
privateChatView
|
||||||
.frame(width: geometry.size.width)
|
.frame(width: geometry.size.width)
|
||||||
@@ -130,7 +137,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Right edge swipe zone for easier sidebar opening (iOS-native behavior)
|
// Right edge swipe zone for easier sidebar opening (iOS-native behavior)
|
||||||
if !showSidebar {
|
if !showSidebar && viewModel.selectedPrivateChatPeer == nil {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Color.clear
|
Color.clear
|
||||||
@@ -142,15 +149,9 @@ struct ContentView: View {
|
|||||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||||
if translationWidth < 0 {
|
if translationWidth < 0 {
|
||||||
let newOffset = max(translationWidth, -300)
|
let newOffset = max(translationWidth, -300)
|
||||||
if abs(newOffset - sidebarDragOffset) > 2 {
|
|
||||||
var transaction = Transaction()
|
|
||||||
transaction.disablesAnimations = true
|
|
||||||
withTransaction(transaction) {
|
|
||||||
sidebarDragOffset = newOffset
|
sidebarDragOffset = newOffset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||||
let velocity = value.velocity.width.isNaN ? 0 : value.velocity.width
|
let velocity = value.velocity.width.isNaN ? 0 : value.velocity.width
|
||||||
@@ -168,7 +169,8 @@ struct ContentView: View {
|
|||||||
.allowsHitTesting(true)
|
.allowsHitTesting(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sidebar overlay
|
// Sidebar overlay (only over messages area, not input)
|
||||||
|
if viewModel.selectedPrivateChatPeer == nil {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
// Tap to dismiss area
|
// Tap to dismiss area
|
||||||
Color.clear
|
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)
|
#if os(macOS)
|
||||||
.frame(minWidth: 600, minHeight: 400)
|
.frame(minWidth: 600, minHeight: 400)
|
||||||
#endif
|
#endif
|
||||||
@@ -951,7 +964,7 @@ struct ContentView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
.simultaneousGesture(
|
.gesture(
|
||||||
DragGesture(minimumDistance: 10)
|
DragGesture(minimumDistance: 10)
|
||||||
.onChanged { value in
|
.onChanged { value in
|
||||||
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
let translationWidth = value.translation.width.isNaN ? 0 : value.translation.width
|
||||||
@@ -962,15 +975,9 @@ struct ContentView: View {
|
|||||||
|
|
||||||
if translationWidth > 0 {
|
if translationWidth > 0 {
|
||||||
let newOffset = min(translationWidth, 300)
|
let newOffset = min(translationWidth, 300)
|
||||||
if abs(newOffset - sidebarDragOffset) > 2 {
|
|
||||||
var transaction = Transaction()
|
|
||||||
transaction.disablesAnimations = true
|
|
||||||
withTransaction(transaction) {
|
|
||||||
sidebarDragOffset = newOffset
|
sidebarDragOffset = newOffset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
let translationWidth = 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 translationHeight = value.translation.height.isNaN ? 0 : value.translation.height
|
||||||
@@ -999,77 +1006,6 @@ struct ContentView: View {
|
|||||||
|
|
||||||
// MARK: - View Components
|
// 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 {
|
private var privateChatView: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
// Vertical separator bar
|
// Vertical separator bar
|
||||||
|
|||||||
Reference in New Issue
Block a user