package com.bottomsheetbehavior; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v4.widget.NestedScrollView; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.RCTEventEmitter; import java.util.Map; import javax.annotation.Nullable; public class BottomSheetBehaviorManager extends ViewGroupManager<BottomSheetBehaviorView> { private final static String REACT_CLASS = "BSBBottomSheetBehaviorAndroid"; public static final int COMMAND_SET_REQUEST_LAYOUT = 1; public static final int COMMAND_SET_BOTTOM_SHEET_STATE = 2; public static final int COMMAND_ATTACH_NESTED_SCROLL_CHILD = 3; @Override public String getName() { return REACT_CLASS; } @Override public BottomSheetBehaviorView createViewInstance(ThemedReactContext context) { BottomSheetBehaviorView bottomSheet = new BottomSheetBehaviorView(context); bottomSheet.behavior.addBottomSheetCallback(new BottomSheetBehaviorListener()); return bottomSheet; } @ReactProp(name = "state", defaultInt = 4) public void setState(BottomSheetBehaviorView view, int state) { try { view.setState(state); } catch (Exception e) { } } @ReactProp(name = "hideable") public void setHideable(BottomSheetBehaviorView view, boolean hideable) { view.setHideable(hideable); } @ReactProp(name = "peekHeight", defaultInt = 50) public void setPeekHeight(BottomSheetBehaviorView view, int peekHeight) { view.setPeekHeight(peekHeight); } @ReactProp(name = "anchorEnabled") public void setAnchorEnabled(BottomSheetBehaviorView view, boolean anchorEnabled) { view.setAnchorEnabled(anchorEnabled); } @ReactProp(name = "anchorPoint", defaultInt = 300) public void setAnchorPoint(BottomSheetBehaviorView view, int anchorPoint) { view.setAnchorPoint(anchorPoint); } @ReactProp(name = "elevation", defaultFloat = 0) public void setElevation(BottomSheetBehaviorView view, float elevation) { view.setBottomSheetElevation(elevation); } @Override public Map<String, Integer> getCommandsMap() { return MapBuilder .of("setRequestLayout", COMMAND_SET_REQUEST_LAYOUT, "setBottomSheetState", COMMAND_SET_BOTTOM_SHEET_STATE, "attachNestedScrollChild", COMMAND_ATTACH_NESTED_SCROLL_CHILD); } @Nullable @Override public Map<String, Object> getExportedCustomBubblingEventTypeConstants() { return MapBuilder.<String, Object>builder() .put( "topStateChange", MapBuilder.of( "phasedRegistrationNames", MapBuilder.of( "bubbled", "onStateChange", "captured", "onStateChangeCapture"))) .put( "topSlide", MapBuilder.of( "phasedRegistrationNames", MapBuilder.of( "bubbled", "onSlide", "captured", "onSlideCapture"))) .build(); } @Override public void receiveCommand(BottomSheetBehaviorView view, int commandType, @Nullable ReadableArray args) { switch (commandType) { case COMMAND_SET_REQUEST_LAYOUT: setRequestLayout(view); return; case COMMAND_SET_BOTTOM_SHEET_STATE: setBottomSheetState(view, args); return; case COMMAND_ATTACH_NESTED_SCROLL_CHILD: int nestedScrollId = args.getInt(0); ViewGroup child = (ViewGroup) view.getRootView().findViewById(nestedScrollId); if (child != null && child instanceof NestedScrollView) { this.attachNestedScrollChild(view, (NestedScrollView) child); } return; default: throw new JSApplicationIllegalArgumentException("Invalid Command"); } } private void setRequestLayout(BottomSheetBehaviorView view) { view.requestLayout(); } private void setBottomSheetState(BottomSheetBehaviorView view, @Nullable ReadableArray args) { if (!args.isNull(0)) { int newState = args.getInt(0); setState(view, newState); } } /** * BottomSheetBehaviorView inherits a NestedScrollView in order to work * with the anchor point, but it breaks any ReactNestedScrollView child, * so we are changing the behavior of ReactNestedScrollView to disable * the nested scroll of the bottom sheet, and enable when the child scroll * reaches the top offset. */ private void attachNestedScrollChild(final BottomSheetBehaviorView parent, final NestedScrollView nestedScroll) { nestedScroll.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_MOVE) { if (nestedScroll.computeVerticalScrollOffset() == 0) { parent.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); } else { parent.stopNestedScroll(); } } return nestedScroll.onTouchEvent(event); } }); } public class BottomSheetBehaviorListener extends RNBottomSheetBehavior.BottomSheetCallback { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { WritableMap event = Arguments.createMap(); event.putInt("state", newState); ReactContext reactContext = (ReactContext) bottomSheet.getContext(); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(bottomSheet.getId(), "topStateChange", event); } @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { WritableMap event = Arguments.createMap(); event.putDouble("offset", slideOffset); ReactContext reactContext = (ReactContext) bottomSheet.getContext(); reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(bottomSheet.getId(), "topSlide", event); } } }