move into repo
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
group = 'expo.modules.bottomsheet'
|
||||
version = '0.1.0'
|
||||
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
useCoreDependencies()
|
||||
useExpoPublishing()
|
||||
|
||||
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
|
||||
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
|
||||
// Most of the time, you may like to manage the Android SDK versions yourself.
|
||||
def useManagedAndroidSdkVersions = false
|
||||
if (useManagedAndroidSdkVersions) {
|
||||
useDefaultAndroidSdkVersions()
|
||||
} else {
|
||||
buildscript {
|
||||
// Simple helper that allows the root project to override versions declared by this library.
|
||||
ext.safeExtGet = { prop, fallback ->
|
||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||
}
|
||||
}
|
||||
project.android {
|
||||
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet("minSdkVersion", 21)
|
||||
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "expo.modules.bottomsheet"
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "0.1.0"
|
||||
}
|
||||
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.facebook.react:react-native:+"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest>
|
||||
</manifest>
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package expo.modules.bottomsheet
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.view.allViews
|
||||
import com.facebook.react.ReactRootView
|
||||
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) {
|
||||
val sheetState = mutableStateOf(SheetState())
|
||||
|
||||
private var reactRootView: ReactRootView? = null
|
||||
private var innerView: View? = null
|
||||
|
||||
private var sheetView: ComposeView? = null
|
||||
|
||||
private val onStateChange by EventDispatcher()
|
||||
private val onAttemptDismiss by EventDispatcher()
|
||||
|
||||
// Props
|
||||
var preventDismiss = false
|
||||
var minHeight = 0f
|
||||
var maxHeight = 0f
|
||||
|
||||
private var isOpen: Boolean = false
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
|
||||
field = value
|
||||
this.sheetState.value.isOpen = 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 hasInitiallyOpened = false
|
||||
|
||||
// Lifecycle
|
||||
|
||||
override fun addView(child: View?, index: Int) {
|
||||
this.innerView = child
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
this.innerView?.let {
|
||||
val height = it.allViews.last().measuredHeight.toFloat()
|
||||
this.present(height)
|
||||
}
|
||||
}
|
||||
|
||||
private fun destroy() {
|
||||
this.isClosing = false
|
||||
this.isOpen = false
|
||||
|
||||
this.getRootLayout().removeView(this.sheetView)
|
||||
this.sheetView = null
|
||||
|
||||
this.reactRootView = null
|
||||
this.innerView = null
|
||||
}
|
||||
|
||||
// Presentation
|
||||
|
||||
private fun present(contentHeight: Float) {
|
||||
val innerView = this.innerView ?: return
|
||||
|
||||
// For GestureRootView to work, we need to create a ReactRootView for the innerView to be
|
||||
// contained inside of
|
||||
val reactRootView = ReactRootView(context)
|
||||
reactRootView.addView(innerView)
|
||||
this.reactRootView = reactRootView
|
||||
|
||||
this.isOpening = true
|
||||
this.sheetView = ComposeView(context).also {
|
||||
it.setContent {
|
||||
SheetView(
|
||||
state = sheetState,
|
||||
innerView = reactRootView,
|
||||
contentHeight = innerView.height.toFloat(),
|
||||
onDismissRequest = {
|
||||
onAttemptDismiss(mapOf())
|
||||
if (!preventDismiss) {
|
||||
dismiss()
|
||||
}
|
||||
},
|
||||
onExpanded = {
|
||||
isOpening = false
|
||||
isOpen = true
|
||||
hasInitiallyOpened = true
|
||||
},
|
||||
onHidden = {
|
||||
if (hasInitiallyOpened) {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
getRootLayout().addView(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
this.isClosing = true
|
||||
this.destroy()
|
||||
}
|
||||
|
||||
// Utils
|
||||
|
||||
private fun getRootLayout(): FrameLayout {
|
||||
return appContext.currentActivity!!.findViewById(android.R.id.content)
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package expo.modules.bottomsheet
|
||||
|
||||
import android.graphics.Color
|
||||
import expo.modules.kotlin.modules.Module
|
||||
import expo.modules.kotlin.modules.ModuleDefinition
|
||||
|
||||
class BottomSheetModule : Module() {
|
||||
override fun definition() = ModuleDefinition {
|
||||
Name("BlueskyBottomSheet")
|
||||
|
||||
Function("getSafeAreaInset") {
|
||||
return@Function 10 // @TODO
|
||||
}
|
||||
|
||||
View(BottomSheetView::class) {
|
||||
Events(arrayOf(
|
||||
"onStateChange",
|
||||
"onAttemptDismiss",
|
||||
))
|
||||
|
||||
AsyncFunction("dismiss") { view: BottomSheetView ->
|
||||
view.dismiss()
|
||||
}
|
||||
|
||||
Prop("preventDismiss") { view: BottomSheetView, prop: Boolean ->
|
||||
view.preventDismiss = prop
|
||||
}
|
||||
|
||||
Prop("minHeight") { view: BottomSheetView, prop: Float ->
|
||||
view.minHeight = prop
|
||||
}
|
||||
|
||||
Prop("maxHeight") { view: BottomSheetView, prop: Float ->
|
||||
view.maxHeight = prop
|
||||
}
|
||||
|
||||
Prop("cornerRadius") { view: BottomSheetView, prop: Float ->
|
||||
view.sheetState.value.cornerRadius = prop
|
||||
}
|
||||
|
||||
Prop("containerBackgroundColor") { view: BottomSheetView, prop: String ->
|
||||
view.sheetState.value.containerBackgroundColor = Color.parseColor(prop)
|
||||
}
|
||||
|
||||
Prop("preventExpansion") { view: BottomSheetView, prop: Boolean ->
|
||||
view.sheetState.value.preventExpansion = prop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package expo.modules.bottomsheet
|
||||
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
|
||||
data class SheetState(
|
||||
var isOpen: Boolean = false,
|
||||
var cornerRadius: Float? = null,
|
||||
var containerBackgroundColor: Int ? = null,
|
||||
var preventExpansion: Boolean = false,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SheetView(
|
||||
state: MutableState<SheetState>,
|
||||
innerView: View,
|
||||
contentHeight: Float,
|
||||
onDismissRequest: () -> Unit,
|
||||
onExpanded: () -> Unit,
|
||||
onHidden: () -> Unit,
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false)
|
||||
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = onDismissRequest,
|
||||
shape = RoundedCornerShape(
|
||||
topStart = state.value.cornerRadius ?: 0f,
|
||||
topEnd = state.value.cornerRadius ?: 0f,
|
||||
),
|
||||
containerColor = Color(state.value.containerBackgroundColor ?: android.graphics.Color.TRANSPARENT),
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth()
|
||||
.height(contentHeight.dp)
|
||||
// Prevent covering up the handle
|
||||
.padding(top = 34.dp)
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { innerView }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(sheetState.currentValue) {
|
||||
if (sheetState.currentValue == SheetValue.PartiallyExpanded || sheetState.currentValue == SheetValue.Expanded) {
|
||||
onExpanded()
|
||||
} else if (sheetState.currentValue == SheetValue.Hidden) {
|
||||
onHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user