react-native-lightbox icon indicating copy to clipboard operation
react-native-lightbox copied to clipboard

Change orientation problem

Open dbuarque opened this issue 8 years ago • 10 comments

Hey guys,

I am try to fix an issue when switch the device orientation. I have tried to use the activeProps but it only change the size of the image while is necessary to change the LightboxOvelay also.

Any idea how to fix this?

simulator screen shot mar 27 2016 2 04 27 pm

dbuarque avatar Mar 27 '16 17:03 dbuarque

Hi @dbuarque, did you ever find a solution to this problem? I've been fighting the same issue all weekend now, with little success.

nazwa avatar May 08 '16 18:05 nazwa

yes, I implemented my own lightbox component

dbuarque avatar May 09 '16 01:05 dbuarque

@dbuarque Does you lightbox component available somewhere ? :) Thanks in advance !

pcrouillere avatar Oct 03 '16 14:10 pcrouillere

Hi @pcrouillere, not yet, I need some time to prepare to release

dbuarque avatar Oct 03 '16 14:10 dbuarque

I am also waiting for that lightbox component so I could use it in my projects

hielfx avatar Feb 13 '17 12:02 hielfx

@dbuarque I'd love to have it, too. Even if you don't publish it as an actual react-native component, just the code is better than nothing !

ibussieres avatar Feb 28 '17 16:02 ibussieres

Hi

is there any solution for orientation?

chiragpurohit71085 avatar Nov 01 '17 14:11 chiragpurohit71085

I solved it by adding an event listener to Dimensions and changing device width and height whenever the orientation changes. Following is the altered LightboxOverlay.js:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {
  Animated,
  Dimensions,
  Modal,
  PanResponder,
  Platform,
  StatusBar,
  StyleSheet,
  Text,
  TouchableOpacity,
  View
} from 'react-native'

const DRAG_DISMISS_THRESHOLD = 150
const STATUS_BAR_OFFSET = Platform.OS === 'android' ? -25 : 0
const isIOS = Platform.OS === 'ios'

const styles = StyleSheet.create({
  background: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: '100%',
    height: '100%'
  },
  open: {
    position: 'absolute',
    flex: 1,
    justifyContent: 'center',
    // Android pan handlers crash without this declaration:
    backgroundColor: 'transparent'
  },
  header: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: '100%',
    backgroundColor: 'transparent'
  },
  closeButton: {
    fontSize: 35,
    color: 'white',
    lineHeight: 40,
    width: 40,
    textAlign: 'center',
    shadowOffset: {
      width: 0,
      height: 0
    },
    shadowRadius: 1.5,
    shadowColor: 'black',
    shadowOpacity: 0.8
  }
})

export default class LightboxOverlay extends Component {
  static propTypes = {
    origin: PropTypes.shape({
      x: PropTypes.number,
      y: PropTypes.number,
      width: PropTypes.number,
      height: PropTypes.number
    }),
    springConfig: PropTypes.shape({
      tension: PropTypes.number,
      friction: PropTypes.number
    }),
    backgroundColor: PropTypes.string,
    isOpen: PropTypes.bool,
    renderHeader: PropTypes.func,
    onOpen: PropTypes.func,
    onClose: PropTypes.func,
    swipeToDismiss: PropTypes.bool
  }

  static defaultProps = {
    springConfig: { tension: 30, friction: 7 },
    backgroundColor: 'black'
  }

  constructor(props) {
    super(props)

    this.state = {
      isAnimating: false,
      isPanning: false,
      target: {
        x: 0,
        y: 0,
        opacity: 1
      },
      pan: new Animated.Value(0),
      openVal: new Animated.Value(0)
    }

    Dimensions.addEventListener('change', () => {
      this.setState({
        windowHeight: Dimensions.get('window').height,
        windowWidth: Dimensions.get('window').width
      })
    })
  }

  componentWillMount() {
    this._panResponder = PanResponder.create({
      // Ask to be the responder:
      onStartShouldSetPanResponder: (evt, gestureState) => !this.state.isAnimating,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => !this.state.isAnimating,
      onMoveShouldSetPanResponder: (evt, gestureState) => !this.state.isAnimating,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => !this.state.isAnimating,

      onPanResponderGrant: (evt, gestureState) => {
        this.state.pan.setValue(0)
        this.setState({ isPanning: true })
      },
      onPanResponderMove: Animated.event([null, { dy: this.state.pan }]),
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {
        if (Math.abs(gestureState.dy) > DRAG_DISMISS_THRESHOLD) {
          this.setState({
            isPanning: false,
            target: {
              y: gestureState.dy,
              x: gestureState.dx,
              opacity: 1 - Math.abs(gestureState.dy / this.state.windowHeight)
            }
          })
          this.close()
        } else {
          Animated.spring(this.state.pan, { toValue: 0, ...this.props.springConfig }).start(() => {
            this.setState({ isPanning: false })
          })
        }
      }
    })
  }

  componentDidMount() {
    if (this.props.isOpen) {
      this.open()
    }
  }

  open = () => {
    if (isIOS) {
      StatusBar.setHidden(true, 'fade')
    }
    this.state.pan.setValue(0)
    this.setState({
      isAnimating: true,
      target: {
        x: 0,
        y: 0,
        opacity: 1
      }
    })

    Animated.spring(this.state.openVal, { toValue: 1, ...this.props.springConfig }).start(() =>
      this.setState({ isAnimating: false })
    )
  }

  close = () => {
    if (isIOS) {
      StatusBar.setHidden(false, 'fade')
    }
    this.setState({
      isAnimating: true
    })
    Animated.spring(this.state.openVal, { toValue: 0, ...this.props.springConfig }).start(() => {
      this.setState({
        isAnimating: false
      })
      this.props.onClose()
    })
  }

  componentWillReceiveProps(props) {
    if (this.props.isOpen != props.isOpen && props.isOpen) {
      this.open()
    }
  }

  render() {
    const { isOpen, renderHeader, swipeToDismiss, origin, backgroundColor } = this.props

    const { isPanning, isAnimating, openVal, target } = this.state

    const lightboxOpacityStyle = {
      opacity: openVal.interpolate({ inputRange: [0, 1], outputRange: [0, target.opacity] })
    }

    let handlers
    if (swipeToDismiss) {
      handlers = this._panResponder.panHandlers
    }

    let dragStyle
    if (isPanning) {
      dragStyle = {
        top: this.state.pan
      }
      lightboxOpacityStyle.opacity = this.state.pan.interpolate({
        inputRange: [-this.state.windowHeight, 0, this.state.windowHeight],
        outputRange: [0, 1, 0]
      })
    }

    const openStyle = [
      styles.open,
      {
        left: openVal.interpolate({ inputRange: [0, 1], outputRange: [origin.x, target.x] }),
        top: openVal.interpolate({
          inputRange: [0, 1],
          outputRange: [origin.y + STATUS_BAR_OFFSET, target.y + STATUS_BAR_OFFSET]
        }),
        width: openVal.interpolate({
          inputRange: [0, 1],
          outputRange: [origin.width, this.state.windowWidth]
        }),
        height: openVal.interpolate({
          inputRange: [0, 1],
          outputRange: [origin.height, this.state.windowHeight]
        })
      }
    ]

    const background = (
      <Animated.View
        style={[styles.background, { backgroundColor: backgroundColor }, lightboxOpacityStyle]}
      />
    )
    const header = (
      <Animated.View style={[styles.header, lightboxOpacityStyle]}>
        {renderHeader ? (
          renderHeader(this.close)
        ) : (
          <TouchableOpacity onPress={this.close}>
            <Text style={styles.closeButton}>×</Text>
          </TouchableOpacity>
        )}
      </Animated.View>
    )
    const content = (
      <Animated.View style={[openStyle, dragStyle]} {...handlers}>
        {this.props.children}
      </Animated.View>
    )

    if (this.props.navigator) {
      return (
        <View>
          {background}
          {content}
          {header}
        </View>
      )
    }

    return (
      <Modal visible={isOpen} transparent={true} onRequestClose={() => this.close()}>
        {background}
        {content}
        {header}
      </Modal>
    )
  }
}

Edit:

I pass activeProps as follows:

activeProps={{
            style: {
              flex: 1,
              width: '100%',
              height: '100%'
            }
          }}

phantom1299 avatar Jun 07 '18 13:06 phantom1299

Hi @oblador, any update on the above issue it's almost like 4 years old bug and still no resolution. Did any one find a solution for the above bug ?

srajesh636 avatar Apr 19 '20 19:04 srajesh636

I found a solution which uses 2 new values named deviceWidth and deviceHeight which are wrapped in a useState hook. Also I added a useEffect hook which listens to event changes.

If you paste this whole code block into LightboxOverlay.js then it should work.

node_modules > react-native-lightbox > dist > LightboxOverlay.js

I will paste the whole code and the parts I added below:

import React, { useRef, useEffect, useState } from "react";
import { Animated, Dimensions, PanResponder, Platform, StyleSheet, StatusBar, TouchableOpacity, Text, Modal, } from "react-native";
import { useGesture, useNextTick } from "./hooks";
const { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get("window");
const isIOS = Platform.OS === "ios";
const getDefaultTarget = () => ({ x: 0, y: 0, opacity: 1 });
const styles = StyleSheet.create({
    background: {
        position: "absolute",
        top: 0,
        left: 0,
        width: WINDOW_WIDTH,
        height: WINDOW_HEIGHT,
    },
    open: {
        position: "absolute",
        flex: 1,
        justifyContent: "center",
        // Android pan handlers crash without this declaration:
        backgroundColor: "transparent",
    },
    header: {
        position: "absolute",
        top: 0,
        left: 0,
        width: WINDOW_WIDTH,
        backgroundColor: "transparent",
    },
    closeButton: {
        fontSize: 35,
        color: "white",
        lineHeight: 60,
        width: 70,
        textAlign: "center",
        shadowOffset: {
            width: 0,
            height: 0,
        },
        shadowRadius: 1.5,
        shadowColor: "black",
        shadowOpacity: 0.8,
    },
});
const LightboxOverlay = ({ useNativeDriver, dragDismissThreshold, springConfig, isOpen, onClose, willClose, didOpen, swipeToDismiss, origin, backgroundColor, renderHeader, modalProps, children, doubleTapZoomEnabled, doubleTapGapTimer, doubleTapCallback, doubleTapZoomToCenter, doubleTapMaxZoom, doubleTapZoomStep, doubleTapInitialScale, doubleTapAnimationDuration, longPressGapTimer, longPressCallback }) => {
    const _panResponder = useRef();
    const pan = useRef(new Animated.Value(0));
    const openVal = useRef(new Animated.Value(0));
    const handlers = useRef();
    const [gesture, animations] = useGesture({
        useNativeDriver,
        doubleTapZoomEnabled,
        doubleTapGapTimer,
        doubleTapCallback,
        doubleTapZoomToCenter,
        doubleTapMaxZoom,
        doubleTapZoomStep,
        doubleTapInitialScale,
        doubleTapAnimationDuration,
        longPressGapTimer,
        longPressCallback
    });
    const [deviceWidth, setDeviceWidth] = useState(Dimensions.get('window').width);
    const [deviceHeight, setDeviceHeight] = useState(Dimensions.get('window').height);
    const [{ isAnimating, isPanning, target }, setState] = useState({
        isAnimating: false,
        isPanning: false,
        target: getDefaultTarget(),
    });
    const handleCloseNextTick = useNextTick(onClose);
    const close = () => {
        willClose();
        if (isIOS) {
            StatusBar.setHidden(false, "fade");
        }
        gesture.reset();
        setState((s) => ({
            ...s,
            isAnimating: true,
        }));
        Animated.spring(openVal.current, {
            toValue: 0,
            ...springConfig,
            useNativeDriver,
        }).start(({ finished }) => {
            if (finished) {
                setState((s) => ({ ...s, isAnimating: false }));
                handleCloseNextTick();
            }
        });
    };
    const open = () => {
        if (isIOS) {
            StatusBar.setHidden(true, "fade");
        }
        pan.current.setValue(0);
        setState((s) => ({
            ...s,
            isAnimating: true,
            target: getDefaultTarget(),
        }));
        Animated.spring(openVal.current, {
            toValue: 1,
            ...springConfig,
            useNativeDriver,
        }).start(({ finished }) => {
            if (finished) {
                setState((s) => ({ ...s, isAnimating: false }));
                didOpen();
            }
        });
    };
    const initPanResponder = () => {
        _panResponder.current = PanResponder.create({
            // Ask to be the responder:
            onStartShouldSetPanResponder: () => !isAnimating,
            onStartShouldSetPanResponderCapture: () => !isAnimating,
            onMoveShouldSetPanResponder: () => !isAnimating,
            onMoveShouldSetPanResponderCapture: () => !isAnimating,
            onPanResponderGrant: (e, gestureState) => {
                gesture.init();
                pan.current.setValue(0);
                setState((s) => ({ ...s, isPanning: true }));
                gesture.onLongPress(e, gestureState);
                gesture.onDoubleTap(e, gestureState);
            },
            onPanResponderMove: Animated.event([null, { dy: pan.current }], {
                useNativeDriver,
            }),
            onPanResponderTerminationRequest: () => true,
            onPanResponderRelease: (evt, gestureState) => {
                gesture.release();
                if (gesture.isDoubleTaped)
                    return;
                if (gesture.isLongPressed)
                    return;
                if (Math.abs(gestureState.dy) > dragDismissThreshold) {
                    setState((s) => ({
                        ...s,
                        isPanning: false,
                        target: {
                            y: gestureState.dy,
                            x: gestureState.dx,
                            opacity: 1 - Math.abs(gestureState.dy / WINDOW_HEIGHT),
                        },
                    }));
                    close();
                }
                else {
                    Animated.spring(pan.current, {
                        toValue: 0,
                        ...springConfig,
                        useNativeDriver,
                    }).start(({ finished }) => {
                        finished && setState((s) => ({ ...s, isPanning: false }));
                    });
                }
            },
        });
    };
    useEffect(() => {
        initPanResponder();
    }, [useNativeDriver, isAnimating]);
    useEffect(() => {
        const onChange = ({ window }) => {
            setDeviceWidth(window.width);
            setDeviceHeight(window.height);
        };
        const removeEventListener = () => {
            if (Dimensions.removeEventListener) {
                Dimensions.removeEventListener('change', onChange);
            }
        };
        Dimensions.addEventListener('change', onChange);
        return removeEventListener;
    }, []);
    useEffect(() => {
        isOpen && open();
    }, [isOpen]);
    useEffect(() => {
        if (_panResponder.current && swipeToDismiss) {
            handlers.current = _panResponder.current.panHandlers;
        }
    }, [swipeToDismiss, _panResponder.current]);
    const lightboxOpacityStyle = {
        opacity: openVal.current.interpolate({
            inputRange: [0, 1],
            outputRange: [0, target.opacity],
        }),
    };
    let dragStyle;
    if (isPanning) {
        dragStyle = {
            top: pan.current,
        };
        lightboxOpacityStyle.opacity = pan.current.interpolate({
            inputRange: [-WINDOW_HEIGHT, 0, WINDOW_HEIGHT],
            outputRange: [0, 1, 0],
        });
    }
    const openStyle = [
        styles.open,
        {
            left: openVal.current.interpolate({
                inputRange: [0, 1],
                outputRange: [origin.x, target.x],
            }),
            top: openVal.current.interpolate({
                inputRange: [0, 1],
                outputRange: [origin.y, target.y],
            }),
            width: openVal.current.interpolate({
                inputRange: [0, 1],
                outputRange: [origin.width, deviceWidth],
            }),
            height: openVal.current.interpolate({
                inputRange: [0, 1],
                outputRange: [origin.height, deviceHeight],
            }),
        },
    ];

    const background = (<Animated.View style={[styles.background, {
        backgroundColor,
        width: deviceWidth,
        height: deviceHeight,
    }, lightboxOpacityStyle]}></Animated.View>);
    const header = (<Animated.View style={[styles.header, lightboxOpacityStyle]}>
        {renderHeader ? (renderHeader(close)) : (<TouchableOpacity onPress={close}>
            <Text style={styles.closeButton}>×</Text>
        </TouchableOpacity>)}
    </Animated.View>);
    const content = (<Animated.View style={[openStyle, dragStyle, animations]} {...handlers.current}>
        {children}
    </Animated.View>);
    return (<Modal visible={isOpen} transparent={true} onRequestClose={close} {...modalProps}>
        {background}
        {content}
        {header}
    </Modal>);
};
export default LightboxOverlay;

useEffect hook for listening for changes in window.width and window.height

 useEffect(() => {
        const onChange = ({ window }) => {
            setDeviceWidth(window.width);
            setDeviceHeight(window.height);
        };
        const removeEventListener = () => {
            if (Dimensions.removeEventListener) {
                Dimensions.removeEventListener('change', onChange);
            }
        };
        Dimensions.addEventListener('change', onChange);
        return removeEventListener;
    }, []);

useState hooks for deviceWidth and deviceHeight

const [deviceWidth, setDeviceWidth] = useState(Dimensions.get('window').width);
const [deviceHeight, setDeviceHeight] = useState(Dimensions.get('window').height);

Then in openStyle, in width and height I changed the outputRange to use deviceWidth and deviceHeight

const openStyle = [
        styles.open,
        {
            left: openVal.current.interpolate({
                inputRange: [0, 1],
                outputRange: [origin.x, target.x],
            }),
            top: openVal.current.interpolate({
                inputRange: [0, 1],
                outputRange: [origin.y, target.y],
            }),
            width: openVal.current.interpolate({
                inputRange: [0, 1],
                outputRange: [origin.width, deviceWidth],
            }),
            height: openVal.current.interpolate({
                inputRange: [0, 1],
                outputRange: [origin.height, deviceHeight],
            }),
        },
    ];

J-Mann-123 avatar Feb 15 '24 04:02 J-Mann-123