oblador / react-native-lightbox

Images etc in Full Screen Lightbox Popovers for React Native
MIT License
2.82k stars 501 forks source link

Change orientation problem #28

Open dbuarque opened 8 years ago

dbuarque commented 8 years ago

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

nazwa commented 8 years ago

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

dbuarque commented 8 years ago

yes, I implemented my own lightbox component

pcrouillere commented 8 years ago

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

dbuarque commented 8 years ago

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

hielfx commented 7 years ago

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

ibussieres commented 7 years ago

@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 !

chiragpurohit71085 commented 7 years ago

Hi

is there any solution for orientation?

phantom1299 commented 6 years ago

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%'
            }
          }}
srajesh636 commented 4 years ago

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 ?

J-Mann-123 commented 8 months ago

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],
            }),
        },
    ];