|
@@ -0,0 +1,515 @@
|
|
|
+import React, { useState, useEffect, useRef, useMemo } from 'react';
|
|
|
+import {
|
|
|
+ View,
|
|
|
+ Text,
|
|
|
+ StyleSheet,
|
|
|
+ Dimensions,
|
|
|
+ PanResponder,
|
|
|
+ ScrollView,
|
|
|
+ Image,
|
|
|
+ Platform,
|
|
|
+ StatusBar,
|
|
|
+ TouchableOpacity,
|
|
|
+ Share
|
|
|
+} from 'react-native';
|
|
|
+import Modal from 'react-native-modal';
|
|
|
+
|
|
|
+import { Slider, Icon } from 'react-native-elements';
|
|
|
+import Animated, { Easing } from 'react-native-reanimated';
|
|
|
+import { State, PanGestureHandler } from 'react-native-gesture-handler';
|
|
|
+import { getStatusBarHeight } from '../utils/StatusBarHeight';
|
|
|
+import C from 'rn-class';
|
|
|
+import PlayerControls from './PlayerControls';
|
|
|
+import PlayerContents from './PlayerContents';
|
|
|
+import PlayerHeader from './PlayerHeader';
|
|
|
+import { getInset } from 'react-native-safe-area-view';
|
|
|
+
|
|
|
+const { width, height } = Dimensions.get('window');
|
|
|
+const statusBarHeight = getStatusBarHeight(true);
|
|
|
+const minHeight = height * 0.1;
|
|
|
+const midBound = height - height / 2.5;
|
|
|
+const upperBound = height * 0.75;
|
|
|
+//const destination = height - height * 0.2
|
|
|
+//const destination = height - height*0.16 -2 - getStatusBarHeight(true);
|
|
|
+//getInset('bottom')
|
|
|
+// height > 560
|
|
|
+// ?height * 0.75 - getInset('bottom')
|
|
|
+// :height * 0.70;
|
|
|
+
|
|
|
+const {
|
|
|
+ Value,
|
|
|
+ event,
|
|
|
+ Extrapolate,
|
|
|
+ cond,
|
|
|
+ Clock,
|
|
|
+ eq,
|
|
|
+ set,
|
|
|
+ add,
|
|
|
+ sub,
|
|
|
+ multiply,
|
|
|
+ lessThan,
|
|
|
+ clockRunning,
|
|
|
+ startClock,
|
|
|
+ spring,
|
|
|
+ stopClock,
|
|
|
+ interpolate,
|
|
|
+ timing,
|
|
|
+ neq,
|
|
|
+ or,
|
|
|
+ and,
|
|
|
+ greaterThan
|
|
|
+} = Animated;
|
|
|
+
|
|
|
+const shadow = {
|
|
|
+ alignItems: 'center',
|
|
|
+ shadowColor: 'black',
|
|
|
+ shadowOffset: { width: 0, height: 0 },
|
|
|
+ shadowOpacity: 0.18,
|
|
|
+ shadowRadius: 2
|
|
|
+};
|
|
|
+const shadow2 = {
|
|
|
+ alignItems: 'center',
|
|
|
+ shadowColor: '#000',
|
|
|
+ shadowOffset: {
|
|
|
+ width: 0,
|
|
|
+ height: 7
|
|
|
+ },
|
|
|
+ shadowOpacity: 0.41,
|
|
|
+ shadowRadius: 9.11
|
|
|
+
|
|
|
+ //elevation: 14
|
|
|
+};
|
|
|
+
|
|
|
+const shadowShare = {
|
|
|
+ shadowColor: '#000',
|
|
|
+ shadowOffset: {
|
|
|
+ width: 0,
|
|
|
+ height: 7
|
|
|
+ },
|
|
|
+ shadowOpacity: 0.41,
|
|
|
+ shadowRadius: 9.11,
|
|
|
+ elevation: 14
|
|
|
+};
|
|
|
+
|
|
|
+const shadowImage = {
|
|
|
+ shadowColor: '#000',
|
|
|
+ shadowOffset: {
|
|
|
+ width: 0,
|
|
|
+ height: 2
|
|
|
+ },
|
|
|
+ shadowOpacity: 0.25,
|
|
|
+ shadowRadius: 3.84,
|
|
|
+
|
|
|
+ elevation: 5
|
|
|
+};
|
|
|
+
|
|
|
+function runSpring(clock, value, dest) {
|
|
|
+ const state = {
|
|
|
+ finished: new Value(0),
|
|
|
+ velocity: new Value(0),
|
|
|
+ position: new Value(0),
|
|
|
+ time: new Value(0)
|
|
|
+ };
|
|
|
+
|
|
|
+ const config = {
|
|
|
+ damping: 20,
|
|
|
+ mass: 1,
|
|
|
+ stiffness: 100,
|
|
|
+ overshootClamping: false,
|
|
|
+ restSpeedThreshold: 1,
|
|
|
+ restDisplacementThreshold: 0.5,
|
|
|
+ toValue: new Value(0)
|
|
|
+ };
|
|
|
+
|
|
|
+ return [
|
|
|
+ cond(clockRunning(clock), 0, [
|
|
|
+ set(state.finished, 0),
|
|
|
+ set(state.velocity, 0),
|
|
|
+ set(state.position, value),
|
|
|
+ set(config.toValue, dest),
|
|
|
+ startClock(clock)
|
|
|
+ ]),
|
|
|
+ spring(clock, state, config),
|
|
|
+ cond(state.finished, stopClock(clock)),
|
|
|
+ state.position
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+const onShare = async () => {
|
|
|
+ try {
|
|
|
+ const result = await Share.share({
|
|
|
+ message: '페이스북 | https://facebook.com'
|
|
|
+ });
|
|
|
+
|
|
|
+ if (result.action === Share.sharedAction) {
|
|
|
+ if (result.activityType) {
|
|
|
+ // shared with activity type of result.activityType
|
|
|
+ } else {
|
|
|
+ // shared
|
|
|
+ }
|
|
|
+ } else if (result.action === Share.dismissedAction) {
|
|
|
+ // dismissed
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ alert(error.message);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+export default props => {
|
|
|
+ const [modalVisible, setModalVisible] = useState(null);
|
|
|
+ const { y: tabY } = props.Pages.tabPosition;
|
|
|
+ const destination = tabY - minHeight - getStatusBarHeight(true);
|
|
|
+ const [currentPlayerValue, setCurrentPlayerValue] = useState(0);
|
|
|
+ const [isPlay, setPlay] = useState(false);
|
|
|
+ const { onGestureEvent, translateY } = useMemo(() => {
|
|
|
+ const translationY = new Value(0);
|
|
|
+ const velocityY = new Value(0);
|
|
|
+ const offsetY = new Value(0);
|
|
|
+ const offsetY2 = new Value(0);
|
|
|
+ const gestureState = new Value(State.UNDETERMINED);
|
|
|
+
|
|
|
+ const clockY = new Clock();
|
|
|
+ const finalTranslateY = add(translationY, multiply(0.2, velocityY));
|
|
|
+ /**
|
|
|
+ * snapPoint 제스처가 끝날 때 위치 지정을 해주는 값
|
|
|
+ * TabBar 의 높이가 해상도와 스크린에서 그려지는 값이 틀려 TabBar 에서
|
|
|
+ * onLayout 이벤트를 이용하여 TabBar의 y 값을 가져와 처리
|
|
|
+ * destination 변수가 플레이어 축소 시 y 값 높이
|
|
|
+ */
|
|
|
+ const snapPoint = cond(
|
|
|
+ lessThan(finalTranslateY, sub(offsetY, height / 4)),
|
|
|
+ 0,
|
|
|
+ destination
|
|
|
+ );
|
|
|
+ const ty = cond(
|
|
|
+ and(eq(gestureState, State.END), neq(translationY, 0)),
|
|
|
+ [
|
|
|
+ set(
|
|
|
+ translationY,
|
|
|
+ runSpring(clockY, add(offsetY, translationY), snapPoint)
|
|
|
+ ),
|
|
|
+ set(offsetY, translationY),
|
|
|
+ translationY
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ cond(eq(gestureState, State.BEGAN), stopClock(clockY)),
|
|
|
+ add(offsetY, translationY)
|
|
|
+ ]
|
|
|
+ );
|
|
|
+
|
|
|
+ // const translateY = add(ty, offsetY2);
|
|
|
+ // const onGestureEvent = event(
|
|
|
+ // [
|
|
|
+ // {
|
|
|
+ // nativeEvent: {
|
|
|
+ // translationY,
|
|
|
+ // velocityY,
|
|
|
+ // state: gestureState
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // ],
|
|
|
+ // { useNativeDriver: true }
|
|
|
+ // );
|
|
|
+
|
|
|
+ return {
|
|
|
+ translateY: add(ty, offsetY2),
|
|
|
+ onGestureEvent: event(
|
|
|
+ [
|
|
|
+ {
|
|
|
+ nativeEvent: {
|
|
|
+ translationY,
|
|
|
+ velocityY,
|
|
|
+ state: gestureState
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ { useNativeDriver: true }
|
|
|
+ )
|
|
|
+ };
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const opacity = interpolate(translateY, {
|
|
|
+ inputRange: [0, midBound - 100],
|
|
|
+ outputRange: [1, 0],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const statusBarOpacity = interpolate(translateY, {
|
|
|
+ inputRange: [0, statusBarHeight],
|
|
|
+ outputRange: [1, 0],
|
|
|
+ extrapolateLeft: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const playContainerWidth = interpolate(translateY, {
|
|
|
+ inputRange: [0, midBound],
|
|
|
+ outputRange: [width, width - 16],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const playerContainerHeight = interpolate(translateY, {
|
|
|
+ inputRange: [0, midBound],
|
|
|
+ outputRange: [height, 0],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const videoWidth = interpolate(translateY, {
|
|
|
+ inputRange: [0, midBound, upperBound],
|
|
|
+ outputRange: [width, width - 16, width / 4],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const videoHeight = interpolate(translateY, {
|
|
|
+ inputRange: [0, midBound, upperBound],
|
|
|
+ outputRange: [width / 1.78, minHeight * 1.3, minHeight],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const playerControlOpaciy = interpolate(translateY, {
|
|
|
+ inputRange: [midBound, upperBound],
|
|
|
+ outputRange: [0, 1],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+
|
|
|
+ const playerBoderRadius = interpolate(translateY, {
|
|
|
+ inputRange: [midBound, upperBound],
|
|
|
+ outputRange: [0, 15],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const mainPlayerContainerHeight = interpolate(translateY, {
|
|
|
+ inputRange: [0, midBound, upperBound],
|
|
|
+ outputRange: [height, minHeight * 1.3, minHeight],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const PlayerHeadWidth = interpolate(translateY, {
|
|
|
+ inputRange: [0, width],
|
|
|
+ outputRange: [width, 0],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ const PlayerHeadHeight = interpolate(translateY, {
|
|
|
+ inputRange: [0, 50],
|
|
|
+ outputRange: [50, 0],
|
|
|
+ extrapolate: Extrapolate.CLAMP
|
|
|
+ });
|
|
|
+ useEffect(() => {
|
|
|
+ return () => {};
|
|
|
+ });
|
|
|
+
|
|
|
+ const slideUp = () => {};
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <PanGestureHandler
|
|
|
+ onHandlerStateChange={onGestureEvent}
|
|
|
+ activeOffsetY={[-10, 10]}
|
|
|
+ onGestureEvent={onGestureEvent}
|
|
|
+ >
|
|
|
+ <Animated.View
|
|
|
+ style={{
|
|
|
+ ...StyleSheet.absoluteFillObject,
|
|
|
+ zIndex: 10,
|
|
|
+ marginTop: statusBarHeight,
|
|
|
+ transform: [{ translateY }],
|
|
|
+ alignItems: 'center',
|
|
|
+ height: mainPlayerContainerHeight
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Animated.View
|
|
|
+ style={{
|
|
|
+ padding: 0,
|
|
|
+ backgroundColor: 'white',
|
|
|
+ width: playContainerWidth,
|
|
|
+ borderRadius: playerBoderRadius
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Animated.View
|
|
|
+ style={[
|
|
|
+ Platform.OS === 'android'
|
|
|
+ ? { borderColor: '#eee' }
|
|
|
+ : { borderColor: 'white' },
|
|
|
+ {
|
|
|
+ ...StyleSheet.absoluteFillObject,
|
|
|
+ borderWidth: 1,
|
|
|
+ borderRadius: 15,
|
|
|
+ opacity: playerControlOpaciy,
|
|
|
+ backgroundColor: 'white'
|
|
|
+ },
|
|
|
+ shadow2
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <PlayerControls
|
|
|
+ {...props}
|
|
|
+ currentPlayerValue={currentPlayerValue}
|
|
|
+ setCurrentPlayerValue={setCurrentPlayerValue}
|
|
|
+ />
|
|
|
+ </Animated.View>
|
|
|
+ <Animated.View
|
|
|
+ style={{
|
|
|
+ opacity,
|
|
|
+ backgroundColor: 'rgba(255,255,255,0)',
|
|
|
+ width: PlayerHeadWidth,
|
|
|
+ height: PlayerHeadHeight
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <PlayerHeader
|
|
|
+ {...props}
|
|
|
+ visibleMenu={setModalVisible}
|
|
|
+ />
|
|
|
+ </Animated.View>
|
|
|
+ <Animated.View style={{ width: videoWidth, height: videoHeight }}>
|
|
|
+ <Animated.Image
|
|
|
+ resizeMode="contain"
|
|
|
+ style={{ flex: 1, margin: 10, width: null, height: null }}
|
|
|
+ source={props.Pages.img}
|
|
|
+ />
|
|
|
+ </Animated.View>
|
|
|
+ </Animated.View>
|
|
|
+ <Animated.View
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'white',
|
|
|
+ width: playContainerWidth,
|
|
|
+ height: playerContainerHeight
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Animated.View style={{ opacity }}>
|
|
|
+ <PlayerContents
|
|
|
+ {...props}
|
|
|
+ currentPlayerValue={currentPlayerValue}
|
|
|
+ setCurrentPlayerValue={setCurrentPlayerValue}
|
|
|
+ />
|
|
|
+ </Animated.View>
|
|
|
+ </Animated.View>
|
|
|
+ </Animated.View>
|
|
|
+ </PanGestureHandler>
|
|
|
+ <Modal
|
|
|
+ isVisible={modalVisible === 'detail'}
|
|
|
+ onSwipeComplete={() => {
|
|
|
+ setModalVisible(null);
|
|
|
+ }}
|
|
|
+ swipeDirection="left"
|
|
|
+ //deviceHeight={height}
|
|
|
+ deviceWidth={width}
|
|
|
+ style={{ margin: 0 }}
|
|
|
+ backdropColor="white"
|
|
|
+ backdropOpacity={1}
|
|
|
+ animationIn="slideInLeft"
|
|
|
+ animationOut="slideOutLeft"
|
|
|
+ >
|
|
|
+ <C.View cls="w90 h100 bgc-rgba-black-0_5">
|
|
|
+ <ScrollView style={{ flex: 1, marginTop: '10%' }}>
|
|
|
+ <C.Text cls="flx1 ta-c">{props.Pages.subtitle}</C.Text>
|
|
|
+ <C.View cls="flx2 ph5 mt5">
|
|
|
+ <C.Text cls="fw-b">{props.Pages.subtitle}</C.Text>
|
|
|
+ <C.Text cls="mt2">{props.Pages.contens}</C.Text>
|
|
|
+ <C.Text cls="mt3" numberOfLines={1}>
|
|
|
+ by {props.Pages.subtitle.replace(/\r\n|\n|\r/gm, ' ')}
|
|
|
+ </C.Text>
|
|
|
+ <C.Text cls="mt5 as-e" numberOfLines={1}>
|
|
|
+ March 19
|
|
|
+ </C.Text>
|
|
|
+ </C.View>
|
|
|
+ </ScrollView>
|
|
|
+ </C.View>
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[
|
|
|
+ StyleSheet.absoluteFillObject,
|
|
|
+ {
|
|
|
+ width: '90%',
|
|
|
+ justifyContent: 'center',
|
|
|
+ paddingRight: '5%',
|
|
|
+ alignItems: 'flex-end'
|
|
|
+ }
|
|
|
+ ]}
|
|
|
+ onPress={() => {
|
|
|
+ setModalVisible(!modalVisible);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <C.EL.Icon
|
|
|
+ color="rgb(10,132,255)"
|
|
|
+ {...C.n2cls('size10')}
|
|
|
+ type="ionicon"
|
|
|
+ name="md-arrow-dropleft"
|
|
|
+ />
|
|
|
+ </TouchableOpacity>
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ <Modal isVisible={modalVisible === 'share'} style={{ margin: 0 }}>
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ flex: 1,
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ backgroundColor: 'rgb(242,242,247)'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ScrollView>
|
|
|
+ <View style={{ height: getStatusBarHeight(true) }} />
|
|
|
+ <View style={{ marginHorizontal: '5%', alignItems: 'flex-end' }}>
|
|
|
+ <C.EL.Icon
|
|
|
+ color="black"
|
|
|
+ {...C.n2cls('size10')}
|
|
|
+ type="ionicon"
|
|
|
+ name="md-close"
|
|
|
+ onPress={() => {
|
|
|
+ setModalVisible(null);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ //...shadowImage,
|
|
|
+ //backgroundColor: 'white',
|
|
|
+ marginHorizontal: '5%',
|
|
|
+ width: width * 0.8,
|
|
|
+ height: (width * 0.8) / 1.78
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Image
|
|
|
+ resizeMode="contain"
|
|
|
+ style={{
|
|
|
+ flex: 1,
|
|
|
+ marginVertical: 10,
|
|
|
+ width: null,
|
|
|
+ height: null
|
|
|
+ }}
|
|
|
+ source={props.Pages.img}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ <C.View cls="flx-row jc-sa ai-t mt2">
|
|
|
+ <C.Text cls="fw-b">{'Spring of Music'}</C.Text>
|
|
|
+ <C.Text cls="fw-b">{`18min`}</C.Text>
|
|
|
+ </C.View>
|
|
|
+
|
|
|
+ <View style={{ height: minHeight }} />
|
|
|
+
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={onShare}
|
|
|
+ style={{ marginHorizontal: '5%', alignItems: 'center' }}
|
|
|
+ >
|
|
|
+ <View style={[s.sharContainer]}>
|
|
|
+ <C.EL.Icon
|
|
|
+ color="black"
|
|
|
+ {...C.n2cls('size10')}
|
|
|
+ type="ionicon"
|
|
|
+ name="md-share"
|
|
|
+ />
|
|
|
+ <C.Text cls="f2.5">공유하기, 메세지 보내기</C.Text>
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+
|
|
|
+ <View style={{ height: minHeight }} />
|
|
|
+ </ScrollView>
|
|
|
+ </View>
|
|
|
+ </Modal>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const s = StyleSheet.create({
|
|
|
+ sharContainer: {
|
|
|
+ backgroundColor: 'white',
|
|
|
+ flexDirection: 'row',
|
|
|
+ justifyContent: 'space-around',
|
|
|
+ alignItems: 'center',
|
|
|
+ borderColor: '#ddd',
|
|
|
+ borderRadius: 15,
|
|
|
+ flex: 1,
|
|
|
+ width: width * 0.8,
|
|
|
+ height: minHeight,
|
|
|
+ ...shadowShare
|
|
|
+ }
|
|
|
+});
|