Skip to content

Commit

Permalink
Feature: minimum padding (#394)
Browse files Browse the repository at this point in the history
* feat: minimum padding

* android

* change minPadding to edges, wip

* ios

* wip

* RCTConvert works

* fabric Android works

* cleanup

* readme
  • Loading branch information
Shaninnik authored Jun 20, 2023
1 parent c4f7ca8 commit 65c5a99
Show file tree
Hide file tree
Showing 23 changed files with 434 additions and 112 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ For example if you don't want insets to apply to the top edge because the view d
<SafeAreaView edges={['right', 'bottom', 'left']} />
```

Optionally it can be set to an object `{ top?: EdgeMode, right?: EdgeMode, bottom?: EdgeMode, left?: EdgeMode }` where `EdgeMode = 'off' | 'additive' | 'maximum'`. Additive is a default mode and is the same as passing and edge in the array: `finalPadding = safeArea + padding`. Maximum mode will use safe area inset or padding/margin (depends on `mode`) if safe area is less: `finalPadding = max(safeArea, padding)`. For example if you want a floating UI element that should be at the bottom safe area edge on devices with safe area or 24px from the bottom of the screen on devices without safe area or if safe area is less than 24px:

```js
<SafeAreaView style={{paddingBottom: 24}} edges={{bottom: 'maximum'}} />
```

##### `mode`

Optional, `padding` (default) or `margin`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class SafeAreaView(context: Context?) :
ReactViewGroup(context), ViewTreeObserver.OnPreDrawListener, HasFabricViewStateManager {
private var mMode = SafeAreaViewMode.PADDING
private var mInsets: EdgeInsets? = null
private var mEdges: EnumSet<SafeAreaViewEdges>? = null
private var mEdges: SafeAreaViewEdges? = null
private var mProviderView: View? = null
private val mFabricViewStateManager = FabricViewStateManager()

Expand All @@ -30,7 +30,7 @@ class SafeAreaView(context: Context?) :
private fun updateInsets() {
val insets = mInsets
if (insets != null) {
val edges = mEdges ?: EnumSet.allOf(SafeAreaViewEdges::class.java)
val edges = mEdges ?: SafeAreaViewEdges(SafeAreaViewEdgeModes.ADDITIVE, SafeAreaViewEdgeModes.ADDITIVE, SafeAreaViewEdgeModes.ADDITIVE, SafeAreaViewEdgeModes.ADDITIVE)
if (mFabricViewStateManager.hasStateWrapper()) {
mFabricViewStateManager.setState {
val map = Arguments.createMap()
Expand Down Expand Up @@ -96,7 +96,7 @@ class SafeAreaView(context: Context?) :
updateInsets()
}

fun setEdges(edges: EnumSet<SafeAreaViewEdges>?) {
fun setEdges(edges: SafeAreaViewEdges) {
mEdges = edges
updateInsets()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package com.th3rdwave.safeareacontext

enum class SafeAreaViewEdges {
TOP,
RIGHT,
BOTTOM,
LEFT
import java.util.*

enum class SafeAreaViewEdgeModes {
OFF,
ADDITIVE,
MAXIMUM
}

data class SafeAreaViewEdges(val top: SafeAreaViewEdgeModes,
val right: SafeAreaViewEdgeModes,
val bottom: SafeAreaViewEdgeModes,
val left: SafeAreaViewEdgeModes)

class Safe
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import java.util.*
data class SafeAreaViewLocalData(
val insets: EdgeInsets,
val mode: SafeAreaViewMode,
val edges: EnumSet<SafeAreaViewEdges>
val edges: SafeAreaViewEdges,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.th3rdwave.safeareacontext

import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.StateWrapper
Expand All @@ -12,6 +13,7 @@ import com.facebook.react.views.view.ReactViewGroup
import com.facebook.react.views.view.ReactViewManager
import java.util.*


@ReactModule(name = SafeAreaViewManager.REACT_CLASS)
class SafeAreaViewManager : ReactViewManager(), RNCSafeAreaViewManagerInterface<SafeAreaView> {
override fun getName() = REACT_CLASS
Expand Down Expand Up @@ -39,33 +41,21 @@ class SafeAreaViewManager : ReactViewManager(), RNCSafeAreaViewManagerInterface<
}

@ReactProp(name = "edges")
override fun setEdges(view: SafeAreaView, propList: ReadableArray?) {
val edges = EnumSet.noneOf(SafeAreaViewEdges::class.java)
override fun setEdges(view: SafeAreaView, propList: ReadableMap?) {
if (propList != null) {
for (i in 0 until propList.size()) {
when (propList.getString(i)) {
"top" -> {
edges.add(SafeAreaViewEdges.TOP)
}
"right" -> {
edges.add(SafeAreaViewEdges.RIGHT)
}
"bottom" -> {
edges.add(SafeAreaViewEdges.BOTTOM)
}
"left" -> {
edges.add(SafeAreaViewEdges.LEFT)
}
}
}
view.setEdges(edges)
view.setEdges(SafeAreaViewEdges(
top = propList.getString("top")?.let { SafeAreaViewEdgeModes.valueOf(it.uppercase()) } ?: SafeAreaViewEdgeModes.OFF,
right = propList.getString("right")?.let { SafeAreaViewEdgeModes.valueOf(it.uppercase()) } ?: SafeAreaViewEdgeModes.OFF,
bottom = propList.getString("bottom")?.let { SafeAreaViewEdgeModes.valueOf(it.uppercase()) } ?: SafeAreaViewEdgeModes.OFF,
left = propList.getString("left")?.let { SafeAreaViewEdgeModes.valueOf(it.uppercase()) } ?: SafeAreaViewEdgeModes.OFF
))
}
}

override fun updateState(
view: ReactViewGroup,
props: ReactStylesDiffMap?,
stateWrapper: StateWrapper?
view: ReactViewGroup,
props: ReactStylesDiffMap?,
stateWrapper: StateWrapper?
): Any? {
(view as SafeAreaView).fabricViewStateManager.setStateWrapper(stateWrapper)
return null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.th3rdwave.safeareacontext

import kotlin.math.max
import com.facebook.react.bridge.Dynamic
import com.facebook.react.bridge.ReadableType
import com.facebook.react.uimanager.*
Expand Down Expand Up @@ -65,20 +66,26 @@ class SafeAreaViewShadowNode : LayoutShadowNode() {
left = PixelUtil.toPixelFromDIP(left)
val edges = localData.edges
val insets = localData.insets
val insetTop: Float = if (edges.contains(SafeAreaViewEdges.TOP)) insets.top else 0.0f
val insetRight: Float = if (edges.contains(SafeAreaViewEdges.RIGHT)) insets.right else 0.0f
val insetBottom: Float = if (edges.contains(SafeAreaViewEdges.BOTTOM)) insets.bottom else 0.0f
val insetLeft: Float = if (edges.contains(SafeAreaViewEdges.LEFT)) insets.left else 0.0f
if (localData.mode == SafeAreaViewMode.PADDING) {
super.setPadding(Spacing.TOP, insetTop + top)
super.setPadding(Spacing.RIGHT, insetRight + right)
super.setPadding(Spacing.BOTTOM, insetBottom + bottom)
super.setPadding(Spacing.LEFT, insetLeft + left)
super.setPadding(Spacing.TOP, getEdgeValue(edges.top, insets.top, top))
super.setPadding(Spacing.RIGHT, getEdgeValue(edges.right, insets.right, right))
super.setPadding(Spacing.BOTTOM, getEdgeValue(edges.bottom, insets.bottom, bottom))
super.setPadding(Spacing.LEFT, getEdgeValue(edges.left, insets.left, left))
} else {
super.setMargin(Spacing.TOP, insetTop + top)
super.setMargin(Spacing.RIGHT, insetRight + right)
super.setMargin(Spacing.BOTTOM, insetBottom + bottom)
super.setMargin(Spacing.LEFT, insetLeft + left)
super.setMargin(Spacing.TOP, getEdgeValue(edges.top, insets.top, top))
super.setMargin(Spacing.RIGHT, getEdgeValue(edges.right, insets.right, right))
super.setMargin(Spacing.BOTTOM, getEdgeValue(edges.bottom, insets.bottom, bottom))
super.setMargin(Spacing.LEFT, getEdgeValue(edges.left, insets.left, left))
}
}

private fun getEdgeValue(edgeMode: SafeAreaViewEdgeModes, insetValue: Float, edgeValue: Float): Float {
if (edgeMode == SafeAreaViewEdgeModes.OFF) {
return edgeValue
} else if (edgeMode == SafeAreaViewEdgeModes.MAXIMUM) {
return max(insetValue, edgeValue)
} else {
return insetValue + edgeValue
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.BaseViewManagerInterface;

Expand All @@ -26,7 +26,7 @@ public void setProperty(T view, String propName, @Nullable Object value) {
mViewManager.setMode(view, (String) value);
break;
case "edges":
mViewManager.setEdges(view, (ReadableArray) value);
mViewManager.setEdges(view, (ReadableMap) value);
break;
default:
super.setProperty(view, propName, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;

public interface RNCSafeAreaViewManagerInterface<T extends View> {
void setMode(T view, @Nullable String value);
void setEdges(T view, @Nullable ReadableArray value);
void setEdges(T view, @Nullable ReadableMap value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ inline YGValue valueFromEdges(YGStyle::Edges edges, YGEdge edge, YGEdge axis) {
return edges[YGEdgeAll];
}

inline float getEdgeValue(std::string edgeMode, float insetValue, float edgeValue) {
if (edgeMode == "off") {
return edgeValue;
} else if (edgeMode == "maximum") {
return fmax(insetValue, edgeValue);
} else {
return insetValue + edgeValue;
}
}

void RNCSafeAreaViewShadowNode::adjustLayoutWithState() {
ensureUnsealed();

Expand Down Expand Up @@ -53,23 +63,11 @@ void RNCSafeAreaViewShadowNode::adjustLayoutWithState() {
right =
valueFromEdges(props.yogaStyle.margin(), YGEdgeRight, YGEdgeHorizontal);
}
if (std::find(edges.begin(), edges.end(), "top") != edges.end()) {
top = yogaStyleValueFromFloat(
stateData.insets.top + (top.unit == YGUnitPoint ? top.value : 0));
}
if (std::find(edges.begin(), edges.end(), "left") != edges.end()) {
left = yogaStyleValueFromFloat(
stateData.insets.left + (left.unit == YGUnitPoint ? left.value : 0));
}
if (std::find(edges.begin(), edges.end(), "right") != edges.end()) {
right = yogaStyleValueFromFloat(
stateData.insets.right + (right.unit == YGUnitPoint ? right.value : 0));
}
if (std::find(edges.begin(), edges.end(), "bottom") != edges.end()) {
bottom = yogaStyleValueFromFloat(
stateData.insets.bottom +
(bottom.unit == YGUnitPoint ? bottom.value : 0));
}

top = yogaStyleValueFromFloat(getEdgeValue(edges.top, stateData.insets.top, (top.unit == YGUnitPoint ? top.value : 0)));
left = yogaStyleValueFromFloat(getEdgeValue(edges.left, stateData.insets.left, (left.unit == YGUnitPoint ? left.value : 0)));
right = yogaStyleValueFromFloat(getEdgeValue(edges.right, stateData.insets.right, (right.unit == YGUnitPoint ? right.value : 0)));
bottom = yogaStyleValueFromFloat(getEdgeValue(edges.bottom, stateData.insets.bottom, (bottom.unit == YGUnitPoint ? bottom.value : 0)));

YGStyle adjustedStyle = getConcreteProps().yogaStyle;
if (props.mode == RNCSafeAreaViewMode::Padding) {
Expand Down
14 changes: 13 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import { DevSettings, View, Text, StatusBar } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import HooksExample from './HooksExample';
import SafeAreaViewExample from './SafeAreaViewExample';
import SafeAreaViewEdgesExample from './SafeAreaViewEdgesExample';
// import ReactNavigationExample from './ReactNavigationExample';
import ReactNavigationNativeStackExample from './ReactNavigationNativeStackExample';

const STORAGE_KEY = 'rnsac-current-example-v2';

type Example = 'safe-area-view' | 'hooks' | 'react-navigation' | 'native-stack';
type Example =
| 'safe-area-view'
| 'safe-area-view-edges'
| 'hooks'
| 'react-navigation'
| 'native-stack';

export default function App() {
const [currentExample, setCurrentExample] = React.useState<Example | null>(
Expand Down Expand Up @@ -40,6 +46,9 @@ export default function App() {
DevSettings.addMenuItem('Show SafeAreaView Example', () => {
setCurrentExample('safe-area-view');
});
DevSettings.addMenuItem('Show SafeAreaView Edges Example', () => {
setCurrentExample('safe-area-view-edges');
});
DevSettings.addMenuItem('Show Hooks Example', () => {
setCurrentExample('hooks');
});
Expand All @@ -56,6 +65,9 @@ export default function App() {
case 'safe-area-view':
content = <SafeAreaViewExample />;
break;
case 'safe-area-view-edges':
content = <SafeAreaViewEdgesExample />;
break;
case 'hooks':
content = <HooksExample />;
break;
Expand Down
Loading

0 comments on commit 65c5a99

Please sign in to comment.