move into repo

This commit is contained in:
Hailey
2024-10-02 17:53:05 -07:00
parent 24bebecf6b
commit d007151f49
16 changed files with 781 additions and 6 deletions
+56
View File
@@ -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>
@@ -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)
}
}
@@ -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()
}
}
}