From 58f377375fee27039b0e712ad76f583338ac37a0 Mon Sep 17 00:00:00 2001 From: Jovanni Lo Date: Sun, 19 Jan 2025 03:19:07 +0800 Subject: [PATCH] Fix: Sheet background (#114) * chore: cleanup * fix: ios background color * chore: tidy * fix: add edge-to-edge to sample sheets * fix(android): drag bug * fix: default initial index * fix: android background --- .gitignore | 3 + .../com/lodev09/truesheet/TrueSheetDialog.kt | 40 +++- .../com/lodev09/truesheet/TrueSheetView.kt | 18 +- .../lodev09/truesheet/TrueSheetViewManager.kt | 18 +- .../lodev09/truesheet/core/RootSheetView.kt | 27 +-- .../java/com/lodev09/truesheet/core/Utils.kt | 4 +- docs/docs/reference/01-props.mdx | 4 + example/ios/.xcode.env.local | 1 - example/ios/Podfile | 2 + example/ios/Podfile.lock | 31 +-- example/src/sheets/BasicSheet.tsx | 1 + example/src/sheets/BlankSheet.tsx | 1 + example/src/sheets/FlatListSheet.tsx | 1 + example/src/sheets/GestureSheet.tsx | 1 + example/src/sheets/PromptSheet.tsx | 1 + example/src/sheets/ScrollViewSheet.tsx | 1 + ios/Extensions/UIColor+withName.swift | 204 ++++++++++++++++++ ios/TrueSheetView.swift | 20 +- ios/TrueSheetViewController.swift | 25 ++- ios/TrueSheetViewManager.m | 1 + src/TrueSheet.tsx | 14 +- src/TrueSheet.types.ts | 3 +- 22 files changed, 338 insertions(+), 83 deletions(-) delete mode 100644 example/ios/.xcode.env.local create mode 100644 ios/Extensions/UIColor+withName.swift diff --git a/.gitignore b/.gitignore index 9b8e033..18858d1 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,6 @@ android/generated # Docs .vercel + +# Misc +.xcode.env.local diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetDialog.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetDialog.kt index 60a2933..4db509d 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetDialog.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetDialog.kt @@ -2,6 +2,8 @@ package com.lodev09.truesheet import android.annotation.SuppressLint import android.graphics.Color +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RoundRectShape import android.view.View import android.view.ViewGroup import android.view.WindowManager @@ -38,6 +40,7 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val * The maximum window height */ var maxScreenHeight = 0 + var contentHeight = 0 var footerHeight = 0 var maxSheetHeight: Int? = null @@ -57,12 +60,15 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val behavior.isHideable = value } + var cornerRadius: Float = 0f + var backgroundColor: Int = Color.WHITE var footerView: ViewGroup? = null var sizes: Array = arrayOf("medium", "large") init { setContentView(rootSheetView) + sheetView = rootSheetView.parent as ViewGroup sheetView.setBackgroundColor(Color.TRANSPARENT) @@ -93,6 +99,18 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val } } + /** + * Setup background color and corner radius. + */ + fun setupBackground() { + val outerRadii = FloatArray(8) { cornerRadius } + val background = ShapeDrawable(RoundRectShape(outerRadii, null, null)) + + // Use current background color + background.paint.color = backgroundColor + sheetView.background = background + } + /** * Setup dimmed sheet. * `dimmedIndex` will further customize the dimming behavior. @@ -171,11 +189,11 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val * Get the height value based on the size config value. */ private fun getSizeHeight(size: Any): Int { - val height = + val height: Int = when (size) { - is Double -> Utils.toPixel(size) + is Double -> Utils.toPixel(size).toInt() - is Int -> Utils.toPixel(size.toDouble()) + is Int -> Utils.toPixel(size.toDouble()).toInt() is String -> { when (size) { @@ -200,7 +218,7 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val if (fixedHeight == null) { 0 } else { - Utils.toPixel(fixedHeight) + Utils.toPixel(fixedHeight).toInt() } } } @@ -278,7 +296,7 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val isFitToContents = true // m3 max width 640dp - maxWidth = Utils.toPixel(640.0) + maxWidth = Utils.toPixel(640.0).toInt() when (sizes.size) { 1 -> { @@ -311,29 +329,29 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val when (sizes.size) { 1 -> { when (state) { - BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(0, Utils.toDIP(behavior.maxHeight)) + BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(0, Utils.toDIP(behavior.maxHeight.toFloat())) else -> null } } 2 -> { when (state) { - BottomSheetBehavior.STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(behavior.peekHeight)) - BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(1, Utils.toDIP(behavior.maxHeight)) + BottomSheetBehavior.STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(behavior.peekHeight.toFloat())) + BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(1, Utils.toDIP(behavior.maxHeight.toFloat())) else -> null } } 3 -> { when (state) { - BottomSheetBehavior.STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(behavior.peekHeight)) + BottomSheetBehavior.STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(behavior.peekHeight.toFloat())) BottomSheetBehavior.STATE_HALF_EXPANDED -> { val height = behavior.halfExpandedRatio * maxScreenHeight - SizeInfo(1, Utils.toDIP(height.toInt())) + SizeInfo(1, Utils.toDIP(height)) } - BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(2, Utils.toDIP(behavior.maxHeight)) + BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(2, Utils.toDIP(behavior.maxHeight.toFloat())) else -> null } diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt index 19d45f7..edb735f 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt @@ -36,7 +36,7 @@ class TrueSheetView(context: Context) : /** * Current activeIndex. */ - private var currentSizeIndex: Int = 0 + private var currentSizeIndex: Int = -1 /** * Promise callback to be invoked after `present` is called. @@ -70,7 +70,7 @@ class TrueSheetView(context: Context) : // Configure Sheet Dialog sheetDialog.apply { setOnSizeChangeListener { w, h -> - eventDispatcher?.dispatchEvent(ContainerSizeChangeEvent(surfaceId, id, Utils.toDIP(w), Utils.toDIP(h))) + eventDispatcher?.dispatchEvent(ContainerSizeChangeEvent(surfaceId, id, Utils.toDIP(w.toFloat()), Utils.toDIP(h.toFloat()))) } // Setup listener when the dialog has been presented. @@ -289,6 +289,20 @@ class TrueSheetView(context: Context) : } } + fun setCornerRadius(radius: Float) { + if (sheetDialog.cornerRadius == radius) return + + sheetDialog.cornerRadius = radius + sheetDialog.setupBackground() + } + + fun setBackground(color: Int) { + if (sheetDialog.backgroundColor == color) return + + sheetDialog.backgroundColor = color + sheetDialog.setupBackground() + } + fun setSoftInputMode(mode: Int) { sheetDialog.window?.apply { this.setSoftInputMode(mode) diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt index ba83d21..0d67aaa 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.graphics.Color import android.util.Log import android.view.WindowManager import com.facebook.react.bridge.ReadableArray @@ -41,7 +42,7 @@ class TrueSheetViewManager : ViewGroupManager() { @ReactProp(name = "maxHeight") fun setMaxHeight(view: TrueSheetView, height: Double) { - view.setMaxHeight(Utils.toPixel(height)) + view.setMaxHeight(Utils.toPixel(height).toInt()) } @ReactProp(name = "dismissible") @@ -81,12 +82,23 @@ class TrueSheetViewManager : ViewGroupManager() { @ReactProp(name = "contentHeight") fun setContentHeight(view: TrueSheetView, height: Double) { - view.setContentHeight(Utils.toPixel(height)) + view.setContentHeight(Utils.toPixel(height).toInt()) } @ReactProp(name = "footerHeight") fun setFooterHeight(view: TrueSheetView, height: Double) { - view.setFooterHeight(Utils.toPixel(height)) + view.setFooterHeight(Utils.toPixel(height).toInt()) + } + + @ReactProp(name = "cornerRadius") + fun setCornerRadius(view: TrueSheetView, radius: Double) { + view.setCornerRadius(Utils.toPixel(radius)) + } + + @ReactProp(name = "background") + fun setBackground(view: TrueSheetView, colorName: String) { + val color = runCatching { Color.parseColor(colorName) }.getOrDefault(Color.WHITE) + view.setBackground(color) } @ReactProp(name = "sizes") diff --git a/android/src/main/java/com/lodev09/truesheet/core/RootSheetView.kt b/android/src/main/java/com/lodev09/truesheet/core/RootSheetView.kt index bd055ab..b711ea9 100644 --- a/android/src/main/java/com/lodev09/truesheet/core/RootSheetView.kt +++ b/android/src/main/java/com/lodev09/truesheet/core/RootSheetView.kt @@ -27,13 +27,12 @@ import com.facebook.react.views.view.ReactViewGroup class RootSheetView(private val context: Context?) : ReactViewGroup(context), RootView { - private var hasAdjustedSize = false private var viewWidth = 0 private var viewHeight = 0 private val jSTouchDispatcher = JSTouchDispatcher(this) private var jSPointerDispatcher: JSPointerDispatcher? = null - public var sizeChangeListener: ((w: Int, h: Int) -> Unit)? = null + var sizeChangeListener: ((w: Int, h: Int) -> Unit)? = null var eventDispatcher: EventDispatcher? = null @@ -43,37 +42,31 @@ class RootSheetView(private val context: Context?) : } } + private val reactContext: ThemedReactContext + get() = context as ThemedReactContext + + private fun updateContainerSize() { + sizeChangeListener?.let { it(viewWidth, viewHeight) } + } + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) viewWidth = w viewHeight = h - updateFirstChildView() - } - private fun updateFirstChildView() { - if (childCount > 0) { - hasAdjustedSize = false - sizeChangeListener?.let { it(viewWidth, viewHeight) } - } else { - hasAdjustedSize = true - } + updateContainerSize() } override fun addView(child: View, index: Int, params: LayoutParams) { super.addView(child, index, params) - if (hasAdjustedSize) { - updateFirstChildView() - } + updateContainerSize() } override fun handleException(t: Throwable) { reactContext.reactApplicationContext.handleException(RuntimeException(t)) } - private val reactContext: ThemedReactContext - get() = context as ThemedReactContext - override fun onInterceptTouchEvent(event: MotionEvent): Boolean { eventDispatcher?.let { jSTouchDispatcher.handleTouchEvent(event, it) } jSPointerDispatcher?.handleMotionEvent(event, eventDispatcher, true) diff --git a/android/src/main/java/com/lodev09/truesheet/core/Utils.kt b/android/src/main/java/com/lodev09/truesheet/core/Utils.kt index 684ab4e..e3274be 100644 --- a/android/src/main/java/com/lodev09/truesheet/core/Utils.kt +++ b/android/src/main/java/com/lodev09/truesheet/core/Utils.kt @@ -57,8 +57,8 @@ object Utils { } } - fun toDIP(value: Int): Float = PixelUtil.toDIPFromPixel(value.toFloat()) - fun toPixel(value: Double): Int = PixelUtil.toPixelFromDIP(value).toInt() + fun toDIP(value: Float): Float = PixelUtil.toDIPFromPixel(value) + fun toPixel(value: Double): Float = PixelUtil.toPixelFromDIP(value) fun withPromise(promise: Promise, closure: () -> Any?) { try { diff --git a/docs/docs/reference/01-props.mdx b/docs/docs/reference/01-props.mdx index bd1b527..4f5ef17 100644 --- a/docs/docs/reference/01-props.mdx +++ b/docs/docs/reference/01-props.mdx @@ -51,6 +51,10 @@ The sheet's background color. | - | - | - | - | | `ColorValue` | `"white"` | ✅ | ✅ | +:::info +This prop only supports HEX and named colors. Example: `#282e37ff`, `blue`. +::: + ### `cornerRadius` The sheet corner radius. diff --git a/example/ios/.xcode.env.local b/example/ios/.xcode.env.local deleted file mode 100644 index 5878b3f..0000000 --- a/example/ios/.xcode.env.local +++ /dev/null @@ -1 +0,0 @@ -export NODE_BINARY=/opt/homebrew/Cellar/node/23.1.0/bin/node diff --git a/example/ios/Podfile b/example/ios/Podfile index bc35858..ad89d0f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,3 +1,5 @@ +ENV['RCT_NEW_ARCH_ENABLED'] = '1' + # Resolve react_native_pods.rb with node to allow for hoisting require Pod::Executable.execute_command('node', ['-p', 'require.resolve( diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a6086e8..08612a9 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1244,7 +1244,7 @@ PODS: - Yoga - react-native-maps (1.20.1): - React-Core - - react-native-true-sheet (0.1.0): + - react-native-true-sheet (1.0.3): - DoubleConversion - glog - hermes-engine @@ -1532,27 +1532,6 @@ PODS: - React-logger (= 0.76.3) - React-perflogger (= 0.76.3) - React-utils (= 0.76.3) - - RNGestureHandler (2.21.2): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - RNReanimated (3.16.3): - DoubleConversion - glog @@ -1710,7 +1689,6 @@ DEPENDENCIES: - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCodegen (from `build/generated/ios`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNReanimated (from `../node_modules/react-native-reanimated`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -1848,8 +1826,6 @@ EXTERNAL SOURCES: :path: build/generated/ios ReactCommon: :path: "../node_modules/react-native/ReactCommon" - RNGestureHandler: - :path: "../node_modules/react-native-gesture-handler" RNReanimated: :path: "../node_modules/react-native-reanimated" Yoga: @@ -1892,7 +1868,7 @@ SPEC CHECKSUMS: React-Mapbuffer: ad1ba0205205a16dbff11b8ade6d1b3959451658 React-microtasksnativemodule: e771eb9eb6ace5884ee40a293a0e14a9d7a4343c react-native-maps: ee1e65647460c3d41e778071be5eda10e3da6225 - react-native-true-sheet: 4a449f0688b0d769ef20c0e21e21d178b8fbd6dd + react-native-true-sheet: 00be9fe24ef77f41f9c70efbeb30bb5ac91c2893 React-nativeconfig: aeed6e2a8ac02b2df54476afcc7c663416c12bf7 React-NativeModulesApple: c5b7813da94136f50ef084fa1ac077332dcfc658 React-perflogger: 6afb7eebf7d9521cc70481688ccddf212970e9d3 @@ -1920,11 +1896,10 @@ SPEC CHECKSUMS: React-utils: 2bcaf4f4dfe361344bce2fae428603d518488630 ReactCodegen: ae99a130606068ed40d1d9c0d5f25fda142a0647 ReactCommon: 89c87b343deacc8610b099ac764848f0ce937e3e - RNGestureHandler: 0e5ae8d72ef4afb855e98dcdbe60f27d938abe13 RNReanimated: 006a5d3961bf09c1e96d62ed436e02b2e43b89bb SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 3deb2471faa9916c8a82dda2a22d3fba2620ad37 -PODFILE CHECKSUM: 5eaf14a39e31872a9c12e5d368593ff6b4a71d73 +PODFILE CHECKSUM: b38a1c0f527446c0db5a072821b4af1c2506e252 COCOAPODS: 1.16.2 diff --git a/example/src/sheets/BasicSheet.tsx b/example/src/sheets/BasicSheet.tsx index 1c47bac..959694d 100644 --- a/example/src/sheets/BasicSheet.tsx +++ b/example/src/sheets/BasicSheet.tsx @@ -45,6 +45,7 @@ export const BasicSheet = forwardRef((props: BasicSheetProps, ref: Ref console.log('Basic sheet dismissed!')} onPresent={({ index, value }) => diff --git a/example/src/sheets/BlankSheet.tsx b/example/src/sheets/BlankSheet.tsx index e698ef3..87505be 100644 --- a/example/src/sheets/BlankSheet.tsx +++ b/example/src/sheets/BlankSheet.tsx @@ -13,6 +13,7 @@ export const BlankSheet = forwardRef((props: BlankSheetProps, ref: Ref console.log('Sheet FlatList dismissed!')} onPresent={() => console.log(`Sheet FlatList presented!`)} {...props} diff --git a/example/src/sheets/GestureSheet.tsx b/example/src/sheets/GestureSheet.tsx index 9d1232b..27bc17f 100644 --- a/example/src/sheets/GestureSheet.tsx +++ b/example/src/sheets/GestureSheet.tsx @@ -49,6 +49,7 @@ export const GestureSheet = forwardRef((props: GestureSheetProps, ref: Ref console.log('Sheet ScrollView dismissed!')} onPresent={() => console.log(`Sheet ScrollView presented!`)} FooterComponent={