Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dimmed prop #14

Merged
merged 12 commits into from
Apr 26, 2024
69 changes: 63 additions & 6 deletions android/src/main/java/com/lodev09/truesheet/TrueSheetDialog.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.lodev09.truesheet

import android.annotation.SuppressLint
import android.graphics.Color
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import com.facebook.react.uimanager.ThemedReactContext
Expand All @@ -12,14 +14,30 @@ import com.lodev09.truesheet.core.Utils

data class SizeInfo(val index: Int, val value: Float)

@SuppressLint("ClickableViewAccessibility")
class TrueSheetDialog(private val reactContext: ThemedReactContext, private val rootSheetView: RootSheetView) :
BottomSheetDialog(reactContext) {

private var keyboardManager = KeyboardManager(reactContext)

var maxScreenHeight: Int = 0
var contentHeight: Int = 0
var footerHeight: Int = 0
/**
* Specify whether the sheet background is dimmed.
* Set to `false` to allow interaction with the background components.
*/
var dimmed = true

/**
* The size index that the sheet should start to dim the background.
* This is ignored if `dimmed` is set to `false`.
*/
var dimmedIndex = 0

/**
* The maximum window height
*/
var maxScreenHeight = 0
var contentHeight = 0
var footerHeight = 0
var maxSheetHeight: Int? = null

var footerView: ViewGroup? = null
Expand All @@ -45,7 +63,46 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val
maxScreenHeight = Utils.screenHeight(reactContext)
}

/**
* Setup dimmed sheet.
* `dimmedIndex` will further customize the dimming behavior.
*/
fun setupDimmedBackground(sizeIndex: Int) {
window?.apply {
val view = findViewById<View>(com.google.android.material.R.id.touch_outside)

if (dimmed && sizeIndex >= dimmedIndex) {
// Remove touch listener
view.setOnTouchListener(null)

// Add the dimmed background
setFlags(
WindowManager.LayoutParams.FLAG_DIM_BEHIND,
WindowManager.LayoutParams.FLAG_DIM_BEHIND
)

setCanceledOnTouchOutside(true)
} else {
// Override the background touch and pass it to the components outside
view.setOnTouchListener { v, event ->
event.setLocation(event.rawX - v.x, event.rawY - v.y)
reactContext.currentActivity?.dispatchTouchEvent(event)
false
}

// Remove the dimmed background
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)

setCanceledOnTouchOutside(false)
}
}
}

/**
* Present the sheet.
*/
fun show(sizeIndex: Int) {
setupDimmedBackground(sizeIndex)
if (isShowing) {
setStateForSizeIndex(sizeIndex)
} else {
Expand All @@ -63,7 +120,7 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val
}

/**
* Set the state based on the given size index.
* Set the state based for the given size index.
*/
private fun setStateForSizeIndex(index: Int) {
behavior.state = getStateForSizeIndex(index)
Expand Down Expand Up @@ -119,7 +176,7 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val
}

/**
* Determines the state based on the given size index.
* Determines the state based from the given size index.
*/
private fun getStateForSizeIndex(index: Int) =
when (sizes.size) {
Expand Down Expand Up @@ -170,7 +227,7 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val
}

/**
* Configure the sheet based on size preferences.
* Configure the sheet based from the size preference.
*/
fun configure() {
// Configure sheet sizes
Expand Down
53 changes: 38 additions & 15 deletions android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TrueSheetView(context: Context) :
/**
* Current activeIndex.
*/
private var activeIndex: Int = 0
private var currentSizeIndex: Int = 0

/**
* Promise callback to be invoked after `present` is called.
Expand Down Expand Up @@ -77,19 +77,21 @@ class TrueSheetView(context: Context) :
positionFooter()
}

// Resolve the present promise
presentPromise?.let { promise ->
promise()
presentPromise = null
}

// dispatch onPresent event
eventDispatcher?.dispatchEvent(PresentEvent(surfaceId, id, sheetDialog.getSizeInfoForIndex(activeIndex)))
eventDispatcher?.dispatchEvent(PresentEvent(surfaceId, id, sheetDialog.getSizeInfoForIndex(currentSizeIndex)))
}

// Setup listener when the dialog has been dismissed.
setOnDismissListener {
unregisterKeyboardManager()

// Resolve the dismiss promise
dismissPromise?.let { promise ->
promise()
dismissPromise = null
Expand All @@ -106,27 +108,32 @@ class TrueSheetView(context: Context) :
footerView?.let {
val y = (maxScreenHeight - sheetView.top - footerHeight).toFloat()
if (slideOffset >= 0) {
// Sheet is expanding
it.y = y
} else {
// Sheet is collapsing
it.y = y - footerHeight * slideOffset
}
}
}

override fun onStateChanged(view: View, newState: Int) {
val sizeInfo = getSizeInfoForState(newState)
if (sizeInfo != null && sizeInfo.index != activeIndex) {
// Invoke promise when sheet resized programmatically
presentPromise?.let { promise ->
promise()
presentPromise = null
}
if (!isShowing) return

activeIndex = sizeInfo.index
val sizeInfo = getSizeInfoForState(newState)
if (sizeInfo == null || sizeInfo.index == currentSizeIndex) return

// dispatch onSizeChange event
eventDispatcher?.dispatchEvent(SizeChangeEvent(surfaceId, id, sizeInfo))
// Invoke promise when sheet resized programmatically
presentPromise?.let { promise ->
promise()
presentPromise = null
}

currentSizeIndex = sizeInfo.index
setupDimmedBackground(sizeInfo.index)

// dispatch onSizeChange event
eventDispatcher?.dispatchEvent(SizeChangeEvent(surfaceId, id, sizeInfo))
}
}
)
Expand Down Expand Up @@ -231,8 +238,21 @@ class TrueSheetView(context: Context) :
configureIfShowing()
}

fun setDimmed(dimmed: Boolean) {
sheetDialog.dimmed = dimmed
if (sheetDialog.isShowing) {
sheetDialog.setupDimmedBackground(currentSizeIndex)
}
}

fun setDimmedIndex(index: Int) {
sheetDialog.dimmedIndex = index
if (sheetDialog.isShowing) {
sheetDialog.setupDimmedBackground(currentSizeIndex)
}
}

fun setDismissible(dismissible: Boolean) {
sheetDialog.behavior.isHideable = dismissible
sheetDialog.setCancelable(dismissible)
}

Expand All @@ -246,7 +266,7 @@ class TrueSheetView(context: Context) :
*/
fun present(sizeIndex: Int, promiseCallback: () -> Unit) {
if (!sheetDialog.isShowing) {
activeIndex = sizeIndex
currentSizeIndex = sizeIndex
}

presentPromise = promiseCallback
Expand All @@ -258,7 +278,10 @@ class TrueSheetView(context: Context) :
*/
fun dismiss(promiseCallback: () -> Unit) {
dismissPromise = promiseCallback
sheetDialog.dismiss()

// Note: We are not calling `sheetDialog.dismiss()` here.
// This is to properly set the behavior state.
sheetDialog.behavior.state = BottomSheetBehavior.STATE_HIDDEN
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ class TrueSheetViewManager : ViewGroupManager<TrueSheetView>() {
view.setDismissible(dismissible)
}

@ReactProp(name = "dimmed")
fun setDimmed(view: TrueSheetView, dimmed: Boolean) {
view.setDimmed(dimmed)
}

@ReactProp(name = "dimmedIndex")
fun setDimmedIndex(view: TrueSheetView, index: Int) {
view.setDimmedIndex(index)
}

@ReactProp(name = "contentHeight")
fun setContentHeight(view: TrueSheetView, height: Double) {
view.setContentHeight(Utils.toPixel(height))
Expand Down
Binary file added docs/docs/guides/dimming/dimming.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions docs/docs/guides/dimming/dimming.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: Dimming
description: Control the sheet's dimming behavior.
---

import dimming from './dimming.gif'

One of the most common use cases for a Bottom Sheet is to present it while still allowing users to interact with background components, such as in a Maps app.

In this guide, you can configure `TrueSheet` to achieve this exact functionality.

<img alt="dimming" src={dimming} width="300"/>

## How?

You can easily disable the dimmed background of the sheet by setting [`dimmed`](/reference/props#dimmed) to `false`.

```tsx {5}
export const App = () => {
return (
<TrueSheet
sizes={['auto', '69%', 'large']}
dimmed={false}
>
<View />
</TrueSheet>
)
}
```

### Dimmed by Size Index

To further customize the dimming behavior, [`dimmedIndex`](/reference/props#dimmedindex) is also available. Set the [size](/reference/props#sizes) `index` at which you want the sheet to start dimming.

```tsx {5}
export const App = () => {
return (
<TrueSheet
sizes={['auto', '69%', 'large']}
dimmedIndex={1} // Dim will start at 69% ✅
>
<View />
</TrueSheet>
)
}
```

:::info
`dimmedIndex` is ignored if `dimmed` is set to `false`.
:::
2 changes: 1 addition & 1 deletion docs/docs/guides/resizing/resizing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ TrueSheet.resize('resizing-sheet', 1)
```
:::

:::note
:::info
`sizes` can only support up to 3 sizes. **_collapsed_**, **_half-expanded_**, and **_expanded_**.
:::

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ yarn add @lodev09/react-native-true-sheet
npm i @lodev09/react-native-true-sheet
```

:::note
:::info

This package is not compatible with [Expo Go](https://docs.expo.dev/get-started/expo-go/). Use this with [Expo CNG](https://docs.expo.dev/workflow/continuous-native-generation/) instead.

Expand Down
22 changes: 21 additions & 1 deletion docs/docs/reference/01-props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Array of sizes you want the sheet to support. See [this guide](/guides/resizing)
| - | - | - | - |
| [`SheetSize[]`](/reference/types#sheetsize) | `["medium", "large"]` | ✅ | ✅ |

:::note
:::info
A sheet can only support up to **3** sizes only! AKA **_collapsed_**, **_half-expanded_**, and **_expanded_**.
:::

Expand Down Expand Up @@ -74,6 +74,26 @@ If set to `false`, the sheet will prevent interactive dismissal via dragging or
| - | - | - | - |
| `boolean` | `true` | ✅ | ✅ |

### `dimmed`

Specify whether the sheet background is dimmed. Set to `false` to allow interaction with the background components.

| Type | Default | 🍎 | 🤖 |
| - | - | - | - |
| `boolean` | `true` | ✅ | ✅ |

### `dimmedIndex`

The size index that the sheet should start to dim the background.

| Type | Default | 🍎 | 🤖 |
| - | - | - | - |
| `number` | `0` | ✅ | ✅ |

:::info
This is ignored if `dimmed` is set to `false`.
:::

### `grabber`

Shows a grabber (or handle). Native on IOS and styled `View` on Android.
Expand Down
12 changes: 11 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import React, { useRef } from 'react'
import { View, type ViewStyle } from 'react-native'
import { TrueSheet } from '@lodev09/react-native-true-sheet'

import { BasicSheet, FlatListSheet, GestureSheet, PromptSheet, ScrollViewSheet } from './sheets'
import {
BasicSheet,
FlatListSheet,
GestureSheet,
UndimmedSheet,
PromptSheet,
ScrollViewSheet,
} from './sheets'
import { Button } from './components'
import { BLUE } from './utils'

Expand All @@ -12,6 +19,7 @@ export default function App() {
const scrollViewSheet = useRef<TrueSheet>(null)
const flatListSheet = useRef<TrueSheet>(null)
const gestureSheet = useRef<TrueSheet>(null)
const undimmedSheet = useRef<TrueSheet>(null)

const presentBasicSheet = async (index = 0) => {
await basicSheet.current?.present(index)
Expand All @@ -25,12 +33,14 @@ export default function App() {
<Button text="TrueSheet ScrollView" onPress={() => scrollViewSheet.current?.present()} />
<Button text="TrueSheet FlatList" onPress={() => flatListSheet.current?.present()} />
<Button text="TrueSheet Gestures" onPress={() => gestureSheet.current?.present()} />
<Button text="TrueSheet Inline" onPress={() => undimmedSheet.current?.present()} />

<BasicSheet ref={basicSheet} />
<PromptSheet ref={promptSheet} />
<ScrollViewSheet ref={scrollViewSheet} />
<FlatListSheet ref={flatListSheet} />
<GestureSheet ref={gestureSheet} />
<UndimmedSheet ref={undimmedSheet} />
</View>
)
}
Expand Down
Loading