Skip to content

Commit

Permalink
Merge branch 'hailey/dialogs-pt1' into samuel/simplify-gif-select
Browse files Browse the repository at this point in the history
  • Loading branch information
haileyok committed Oct 3, 2024
2 parents b44a2a3 + 38d8b17 commit 753bb43
Show file tree
Hide file tree
Showing 21 changed files with 364 additions and 178 deletions.
9 changes: 1 addition & 8 deletions modules/bottom-sheet/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,10 @@ android {
lintOptions {
abortOnError false
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.8"
}
}

dependencies {
implementation project(':expo-modules-core')
implementation "androidx.compose.material3:material3:1.3.0"
implementation "androidx.compose.material:material:1.7.2"
implementation 'com.google.android.material:material:1.12.0'
implementation "com.facebook.react:react-native:+"
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class BottomSheetModule : Module() {
}

Prop("containerBackgroundColor") { view: BottomSheetView, prop: String ->
view.sheetState.value.containerBackgroundColor = Color.parseColor(prop)
// view.sheetState.value.containerBackgroundColor = Color.parseColor(prop)
}

Prop("cornerRadius") { view: BottomSheetView, prop: Float ->
view.sheetState.value.cornerRadius = prop
// view.sheetState.value.cornerRadius = prop
}

Prop("minHeight") { view: BottomSheetView, prop: Float ->
Expand All @@ -51,7 +51,7 @@ class BottomSheetModule : Module() {
}

Prop("preventExpansion") { view: BottomSheetView, prop: Boolean ->
view.sheetState.value.preventExpansion = prop
view.preventExpansion = prop
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
package expo.modules.bottomsheet

import android.content.Context
import android.view.View
import android.widget.FrameLayout
import androidx.core.view.allViews
import com.facebook.react.ReactRootView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.viewevent.EventDispatcher
import expo.modules.kotlin.views.ExpoView

class BottomSheetView(
context: Context,
appContext: AppContext,
) : ExpoView(context, appContext) {

private var reactRootView: ReactRootView? = null
private var innerView: View? = null
private var dialog: BottomSheetDialog? = null

private val screenHeight = context.resources.displayMetrics.heightPixels.toFloat()

private val onAttemptDismiss by EventDispatcher()
private val onSnapPointChange by EventDispatcher()
private val onStateChange by EventDispatcher()

// Props
var preventDismiss = false
set(value) {
field = value
this.dialog?.setCancelable(!value)
}
var preventExpansion = false

var minHeight = 0f
set (value) {
field = if (value < 0) {
0f
} else {
value
}
}

var maxHeight = this.screenHeight
set (value) {
field = if (value > this.screenHeight) {
this.screenHeight.toFloat()
} else {
value
}
}

private var isOpen: Boolean = false
set(value) {
field = value
onStateChange(
mapOf(
"state" to if (value) "open" else "closed",
),
)
}

private var isOpening: Boolean = false
set(value) {
field = value
if (value) {
onStateChange(mapOf(
"state" to "opening"
))
}
}

private var isClosing: Boolean = false
set(value) {
field = value
if (value) {
onStateChange(mapOf(
"state" to "closing"
))
}
}

private var selectedSnapPoint = 0
set(value) {
if (field == value) return

field = value
onSnapPointChange(
mapOf(
"snapPoint" to value,
),
)
}

// Lifecycle

init {
SheetManager.add(this)
}

override fun addView(
child: View?,
index: Int,
) {
this.innerView = child
}

override fun onLayout(
changed: Boolean,
l: Int,
t: Int,
r: Int,
b: Int,
) {
this.present()
}

private fun destroy() {
this.isClosing = false
this.isOpen = false
this.dialog = null
this.reactRootView = null
this.innerView = null
SheetManager.remove(this)
}

// Presentation

private fun present() {
if (this.isOpen || this.isOpening || this.isClosing) return

val innerView = this.innerView ?: return
val contentHeight = this.getContentHeight()

// Needs to be a react root view for RNGH to work
val rootView = ReactRootView(context)
rootView.addView(innerView)
this.reactRootView = rootView

val dialog = BottomSheetDialog(context)
dialog.setContentView(rootView)
dialog.setCancelable(!preventDismiss)
dialog.setOnShowListener {
val bottomSheet = dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
bottomSheet?.let {
// Let the outside view handle the background color on its own, the default for this is
// white and we don't want that.
it.setBackgroundColor(0)

val behavior = BottomSheetBehavior.from(it)

behavior.isFitToContents = true
behavior.halfExpandedRatio = this.clampRatio(this.getTargetHeight() / this.screenHeight)
if (contentHeight > this.screenHeight) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
this.selectedSnapPoint = 2
} else {
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
this.selectedSnapPoint = 1
}
behavior.skipCollapsed = true
behavior.isDraggable = true
behavior.isHideable = true

behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> {
selectedSnapPoint = 2
}
BottomSheetBehavior.STATE_COLLAPSED -> {
selectedSnapPoint = 1
}
BottomSheetBehavior.STATE_HALF_EXPANDED -> {
selectedSnapPoint = 1
}
BottomSheetBehavior.STATE_HIDDEN -> {
selectedSnapPoint = 0
}
}
}

override fun onSlide(bottomSheet: View, slideOffset: Float) { }
})
}
}
dialog.setOnDismissListener {
this.isClosing = true
this.destroy()
}

this.isOpening = true
dialog.show()
this.dialog = dialog
}

fun updateLayout() {
val dialog = this.dialog ?: return
val contentHeight = this.getContentHeight()

val bottomSheet = dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
bottomSheet?.let {
val behavior = BottomSheetBehavior.from(it)

behavior.halfExpandedRatio = this.clampRatio(this.getTargetHeight() / this.screenHeight)

if (contentHeight > this.screenHeight && behavior.state != BottomSheetBehavior.STATE_EXPANDED) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
} else if (contentHeight < this.screenHeight && behavior.state != BottomSheetBehavior.STATE_HALF_EXPANDED) {
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}
}
}

fun dismiss() {
this.dialog?.dismiss()
}

// Util

private fun getContentHeight(): Float {
val innerView = this.innerView ?: return 0f
innerView.allViews.forEach {
if (it.javaClass.simpleName == "RNGestureHandlerRootView") {
return it.height.toFloat() + 50f
}
}
return 0f
}

private fun getTargetHeight(): Float {
val contentHeight = this.getContentHeight()
val height = if (contentHeight > maxHeight) {
maxHeight
} else if (contentHeight < minHeight) {
minHeight
} else {
contentHeight
}
return height
}

private fun clampRatio(ratio: Float): Float {
if (ratio < 0.01) {
return 0.01f
} else if (ratio > 0.99) {
return 0.99f
}
return ratio
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package expo.modules.bottomsheet

import java.lang.ref.WeakReference

class SheetManager {
companion object {
private val sheets = mutableSetOf<WeakReference<BottomSheetView>>()

fun add(view: BottomSheetView) {
sheets.add(WeakReference(view))
}

fun remove(view: BottomSheetView) {
sheets.forEach {
if (it.get() == view) {
sheets.remove(it)
return
}
}
}

fun dismissAll() {
sheets.forEach {
it.get()?.dismiss()
}
}
}
}

This file was deleted.

Loading

0 comments on commit 753bb43

Please sign in to comment.