From f4aa90872e6faa68f042e9fd661374f6423d8292 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Wed, 27 Mar 2024 21:42:29 +0800 Subject: [PATCH] feat(android): setup BottomSheetBehavior --- android/build.gradle | 1 + .../truesheet/TrueSheetBottomSheetBehavior.kt | 56 +++++++++++++++++++ .../truesheet/TrueSheetRootViewGroup.kt | 2 + .../com/lodev09/truesheet/TrueSheetView.kt | 54 +++++++++++++----- .../lodev09/truesheet/TrueSheetViewManager.kt | 1 + .../lodev09/truesheet/TrueSheetViewModule.kt | 10 ++-- 6 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 android/src/main/java/com/lodev09/truesheet/TrueSheetBottomSheetBehavior.kt diff --git a/android/build.gradle b/android/build.gradle index 96ab6be..cfd1b73 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -91,5 +91,6 @@ dependencies { implementation "com.facebook.react:react-native:+" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "com.google.android.material:material:1.11.0" + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' } diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetBottomSheetBehavior.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetBottomSheetBehavior.kt new file mode 100644 index 0000000..c3c3049 --- /dev/null +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetBottomSheetBehavior.kt @@ -0,0 +1,56 @@ +package com.lodev09.truesheet + +import android.util.Log +import android.view.MotionEvent +import android.view.ViewGroup +import android.widget.ScrollView +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.bottomsheet.BottomSheetBehavior + +class TrueSheetBottomSheetBehavior() : BottomSheetBehavior() { + + private fun isInsideSheet(scrollView: ScrollView, event: MotionEvent): Boolean { + val x = event.x + val y = event.y + + val position = IntArray(2) + scrollView.getLocationOnScreen(position) + + val nestedX = position[0] + val nestedY = position[1] + + val boundRight = nestedX + scrollView.width + val boundBottom = nestedY + scrollView.height + + return (x > nestedX && x < boundRight && y > nestedY && y < boundBottom) || + event.action == MotionEvent.ACTION_CANCEL + } + + // TODO: Pass scrollview explicitly here + override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: T, event: MotionEvent): Boolean { + val isDownEvent = (event.actionMasked == MotionEvent.ACTION_DOWN) + val expanded = state == STATE_EXPANDED + + if (isDownEvent && expanded){ + val container = child.getChildAt(0) as ViewGroup + val content = (container.getChildAt(0) as ViewGroup).getChildAt(0) as ViewGroup + + for(i in 0 until content.childCount){ + val contentChild = content.getChildAt(i) + val scrolled = (contentChild is ScrollView && contentChild.scrollY > 0) + + if(!scrolled) continue + + if (isInsideSheet(contentChild as ScrollView, event)) { + return false + } + } + } + + return super.onInterceptTouchEvent(parent, child, event) + } + + companion object { + const val TAG = "TrueSheetView" + } +} diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetRootViewGroup.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetRootViewGroup.kt index ca8d767..aea348e 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetRootViewGroup.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetRootViewGroup.kt @@ -1,5 +1,6 @@ package com.lodev09.truesheet +import android.annotation.SuppressLint import android.content.Context import android.view.MotionEvent import android.view.View @@ -91,6 +92,7 @@ internal class TrueSheetRootViewGroup(context: Context?) : ReactViewGroup(contex return super.onInterceptTouchEvent(event) } + @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { mJSTouchDispatcher.handleTouchEvent(event, mEventDispatcher) mJSPointerDispatcher?.handleMotionEvent(event, mEventDispatcher, false) diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt index 099622b..25fd75d 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt @@ -1,26 +1,31 @@ package com.lodev09.truesheet import android.content.Context +import android.util.AttributeSet import android.util.Log import android.view.View import android.view.ViewGroup import android.view.ViewStructure import android.view.accessibility.AccessibilityEvent -import android.widget.FrameLayout +import android.widget.RelativeLayout +import androidx.coordinatorlayout.widget.CoordinatorLayout import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.UiThreadUtil -import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.EventDispatcher import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog -class TrueSheetView(context: Context) : ViewGroup(context), LifecycleEventListener { +class TrueSheetView : ViewGroup, LifecycleEventListener { + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + private val sheetRootView: TrueSheetRootViewGroup? private var sheetDialog: BottomSheetDialog? - private lateinit var sheetBehavior: BottomSheetBehavior<*> + private lateinit var sheetBehavior: TrueSheetBottomSheetBehavior private val reactContext: ThemedReactContext get() = context as ThemedReactContext @@ -29,6 +34,7 @@ class TrueSheetView(context: Context) : ViewGroup(context), LifecycleEventListen reactContext.addLifecycleEventListener(this) sheetRootView = TrueSheetRootViewGroup(context) sheetDialog = BottomSheetDialog(context) + sheetBehavior = TrueSheetBottomSheetBehavior() } override fun dispatchProvideStructure(structure: ViewStructure) { @@ -116,28 +122,46 @@ class TrueSheetView(context: Context) : ViewGroup(context), LifecycleEventListen } private fun setupSheetDialog(height: Int) { - val frameLayout = FrameLayout(context) - frameLayout.addView(sheetRootView) + val layout = RelativeLayout(context) + layout.addView(sheetRootView) + + sheetDialog?.setContentView(layout) - sheetDialog?.setContentView(frameLayout) + val viewGroup = layout.parent as ViewGroup + val params = viewGroup.layoutParams as CoordinatorLayout.LayoutParams - sheetBehavior = BottomSheetBehavior.from(frameLayout.parent as View).apply { + sheetBehavior.apply { addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { - override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit + override fun onSlide(bottomSheet: View, slideOffset: Float) { } override fun onStateChanged(bottomSheet: View, newState: Int) { - // TODO + when (newState) { + BottomSheetBehavior.STATE_HIDDEN -> { + dismiss() + } + BottomSheetBehavior.STATE_COLLAPSED -> {} + BottomSheetBehavior.STATE_DRAGGING -> {} + BottomSheetBehavior.STATE_EXPANDED -> {} + BottomSheetBehavior.STATE_HALF_EXPANDED -> {} + BottomSheetBehavior.STATE_SETTLING -> {} + } } }) - // virtually disables 'third' breakpoint - isFitToContents = true + isFitToContents = false + halfExpandedRatio = 0.8f isHideable = true - // skipCollapsed = true - peekHeight = height + + Log.d(TAG, height.toString()) + + // TODO: Account for ScrollView content + peekHeight = 1652 // height } + + params.behavior = sheetBehavior } fun present() { + sheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED sheetDialog?.show() } @@ -146,7 +170,7 @@ class TrueSheetView(context: Context) : ViewGroup(context), LifecycleEventListen } companion object { - const val NAME = "TrueSheetView" + const val TAG = "TrueSheetView" } } diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt index 59372c2..d5871c2 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt @@ -1,5 +1,6 @@ package com.lodev09.truesheet +import android.view.LayoutInflater import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.ViewGroupManager diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewModule.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewModule.kt index 1fede16..41729c6 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewModule.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewModule.kt @@ -13,9 +13,9 @@ import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine -@ReactModule(name = TrueSheetViewModule.NAME) +@ReactModule(name = TrueSheetViewModule.TAG) class TrueSheetViewModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - override fun getName(): String = NAME + override fun getName(): String = TAG private fun withTrueSheetView(tag: Int, closure: (trueSheetView: TrueSheetView) -> Unit) { UiThreadUtil.runOnUiThread { @@ -23,14 +23,14 @@ class TrueSheetViewModule(reactContext: ReactApplicationContext) : ReactContextB val manager = UIManagerHelper.getUIManagerForReactTag(reactApplicationContext, tag) val view = manager?.resolveView(tag) if (view == null) { - Log.d(NAME, "TrueSheetView with tag $tag not found") + Log.d(TAG, "TrueSheetView with tag $tag not found") return@runOnUiThread } if (view is TrueSheetView) { closure(view) } else { - Log.d(NAME, "View is not of type TrueSheetView") + Log.d(TAG, "View is not of type TrueSheetView") } } catch (e: Exception) { e.printStackTrace() @@ -53,6 +53,6 @@ class TrueSheetViewModule(reactContext: ReactApplicationContext) : ReactContextB } companion object { - const val NAME = "TrueSheetView" + const val TAG = "TrueSheetView" } }