From 271a7575059c9eb86b5e7385bba245b1bd39f14b Mon Sep 17 00:00:00 2001 From: enahum Date: Mon, 25 Sep 2017 14:40:30 -0300 Subject: [PATCH] Refactor channel swiper (#933) * Refactor channel swiper * Feedback review * clean up * another feedback review * Remove unnecessary props * clean up initialState --- NOTICE.txt | 33 -- .../channel_drawer/channel_drawer.js | 37 ++- .../drawer_swipper/drawer_swiper.js | 68 ++-- .../search_bar/search_bar.android.js | 1 + app/components/swiper.js | 291 ++++++++++++++++++ package.json | 1 - yarn.lock | 6 - 7 files changed, 344 insertions(+), 93 deletions(-) create mode 100644 app/components/swiper.js diff --git a/NOTICE.txt b/NOTICE.txt index f6c4b843d..fdf0a6093 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -8,39 +8,6 @@ This document includes a list of open source components used in Mattermost Mobil -------- -## react-native-swiper - -This product contains 'react-native-swiper', A Swiper component for React Native. - -* HOMEPAGE: - * https://github.com/leecade/react-native-swiper - -* LICENSE: - -The MIT License (MIT) - -Copyright (c) 2015 斯人 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ---- - ## react-native-tooltip This product contains a modified portion of 'react-native-tooltip', A react-native component from displaying tooltip. Uses UIMenuController. diff --git a/app/components/channel_drawer/channel_drawer.js b/app/components/channel_drawer/channel_drawer.js index d265c8a54..527790664 100644 --- a/app/components/channel_drawer/channel_drawer.js +++ b/app/components/channel_drawer/channel_drawer.js @@ -79,11 +79,13 @@ export default class ChannelDrawer extends PureComponent { componentWillReceiveProps(nextProps) { const {isLandscape, isTablet} = this.props; if (nextProps.isLandscape !== isLandscape || nextProps.isTablet || isTablet) { - let openDrawerOffset = DRAWER_INITIAL_OFFSET; - if (nextProps.isLandscape || nextProps.isTablet) { - openDrawerOffset = DRAWER_LANDSCAPE_OFFSET; + if (this.state.openDrawerOffset !== 0) { + let openDrawerOffset = DRAWER_INITIAL_OFFSET; + if (nextProps.isLandscape || nextProps.isTablet) { + openDrawerOffset = DRAWER_LANDSCAPE_OFFSET; + } + this.setState({openDrawerOffset}); } - this.setState({openDrawerOffset}); } } @@ -310,14 +312,23 @@ export default class ChannelDrawer extends PureComponent { const showTeams = openDrawerOffset !== 0 && teamsCount > 1; - const teams = ( - - - - ); + let teams; + if (showTeams) { + teams = ( + + + + ); + + if (this.drawerSwiper) { + this.drawerSwiper.getWrappedInstance().runOnLayout(); + } + } else if (this.drawerSwiper && !openDrawerOffset) { + this.drawerSwiper.getWrappedInstance().scrollToStart(); + } const channelsList = ( @@ -344,7 +355,7 @@ export default class ChannelDrawer extends PureComponent { {channelsList} ); - } + }; render() { const {children} = this.props; diff --git a/app/components/channel_drawer/drawer_swipper/drawer_swiper.js b/app/components/channel_drawer/drawer_swipper/drawer_swiper.js index e86a37b03..b6f26384d 100644 --- a/app/components/channel_drawer/drawer_swipper/drawer_swiper.js +++ b/app/components/channel_drawer/drawer_swipper/drawer_swiper.js @@ -3,16 +3,16 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import Swiper from 'react-native-swiper'; +import {StyleSheet} from 'react-native'; import {changeOpacity} from 'app/utils/theme'; +import Swiper from 'app/components/swiper'; + export default class DrawerSwiper extends PureComponent { static propTypes = { children: PropTypes.node.isRequired, - deviceHeight: PropTypes.number.isRequired, deviceWidth: PropTypes.number.isRequired, - isLandscape: PropTypes.bool.isRequired, onPageSelected: PropTypes.func, openDrawerOffset: PropTypes.number, showTeams: PropTypes.bool.isRequired, @@ -24,64 +24,45 @@ export default class DrawerSwiper extends PureComponent { openDrawerOffset: 0 }; - state = { - index: 1 - }; - - componentWillReceiveProps(nextProps) { - if (this.refs.swiper) { - if (nextProps.openDrawerOffset !== this.props.openDrawerOffset || nextProps.isLandscape !== this.props.isLandscape) { - this.refs.swiper.initialRender = true; - if (this.state.index === 1) { - this.resetPage(); - } - } - } - } - - swiperPageSelected = (e, state, context) => { - this.props.onPageSelected(context.state.index); - this.setState({index: context.state.index}); - }; - - showTeamsPage = () => { - this.refs.swiper.scrollBy(-1, true); + runOnLayout = (shouldRun = true) => { + this.refs.swiper.runOnLayout = shouldRun; }; resetPage = () => { - this.refs.swiper.scrollBy(1, false); + this.refs.swiper.scrollToIndex(1, false); + }; + + scrollToStart = () => { + this.refs.swiper.scrollToStart(); + }; + + swiperPageSelected = (index) => { + this.props.onPageSelected(index); + }; + + showTeamsPage = () => { + this.refs.swiper.scrollToIndex(0, true); }; render() { const { children, - deviceHeight, deviceWidth, openDrawerOffset, showTeams, theme } = this.props; - const pagination = {bottom: 0}; - if (showTeams) { - pagination.bottom = 0; - } - return ( null, + scrollEnabled: true, + showsPagination: true + }; + + constructor(props) { + super(props); + + this.runOnLayout = true; + this.offset = props.width * props.initialPage; + this.isScrolling = false; + + this.state = this.initialState(props); + } + + componentDidUpdate(prevProps, prevState) { + // If the index has changed, we notify the parent via the onIndexChanged callback + if (this.state.index !== prevState.index) { + this.props.onIndexChanged(this.state.index); + } + } + + initialState = (props) => { + const index = props.initialPage; + + return { + index, + total: React.Children.count(props.children) + }; + }; + + onLayout = () => { + if (this.runOnLayout) { + if (Platform.OS === 'ios') { + this.scrollView.scrollTo({x: this.offset, animated: false}); + } else { + this.scrollView.setPageWithoutAnimation(this.state.index); + } + this.runOnLayout = false; + } + }; + + onScrollBegin = () => { + this.isScrolling = true; + }; + + onScrollEnd = (e) => { + // making our events coming from android compatible to updateIndex logic + if (!e.nativeEvent.contentOffset) { + e.nativeEvent.contentOffset = {x: e.nativeEvent.position * this.props.width}; + } + + this.isScrolling = false; + + // get the index + this.updateIndex(e.nativeEvent.contentOffset.x); + }; + + scrollToStart = () => { + if (Platform.OS === 'ios') { + InteractionManager.runAfterInteractions(() => { + this.scrollView.scrollTo({x: 0, animated: false}); + }); + } + }; + + refScrollView = (view) => { + this.scrollView = view; + }; + + renderScrollView = (pages) => { + const { + keyboardShouldPersistTaps, + scrollEnabled + } = this.props; + + if (Platform.OS === 'ios') { + return ( + + {pages} + + ); + } + return ( + + {pages} + + ); + }; + + renderPagination = () => { + if (this.state.total <= 1 || !this.props.showsPagination) { + return null; + } + + const dots = []; + const activeDot = ( + + ); + const dot = ( + + ); + for (let i = 0; i < this.state.total; i++) { + if (i === this.state.index) { + dots.push(React.cloneElement(activeDot, {key: i})); + } else { + dots.push(React.cloneElement(dot, {key: i})); + } + } + + return ( + + {dots} + + ); + }; + + scrollToIndex = (index, animated) => { + if (this.isScrolling || this.state.total < 2) { + return; + } + + if (Platform.OS === 'ios') { + this.scrollView.scrollTo({x: (index * this.props.width), animated}); + } else { + this.scrollView[animated ? 'setPage' : 'setPageWithoutAnimation'](index); + } + + // trigger onScrollEnd manually in android or if not animated + if (!animated || Platform.OS === 'android') { + setImmediate(() => { + this.onScrollEnd({ + nativeEvent: { + position: index + } + }); + }); + } + }; + + updateIndex = (offset) => { + let index = this.state.index; + const diff = offset - this.offset; + if (!diff) { + return; + } + + index = parseInt(index + Math.round(diff / this.props.width), 10); + this.offset = offset; + this.setState({index}); + }; + + render() { + const { + children, + width + } = this.props; + + const pages = React.Children.map(children, (page, i) => { + const pageStyle = page ? {width} : {width: 0}; + return ( + + {page} + + ); + }); + + return ( + + {this.renderScrollView(pages)} + {this.renderPagination()} + + ); + } +} + +const styles = StyleSheet.create({ + container: { + backgroundColor: 'transparent', + position: 'relative', + flex: 1 + }, + wrapperIOS: { + backgroundColor: 'transparent' + }, + wrapperAndroid: { + backgroundColor: 'transparent', + flex: 1 + }, + slide: { + backgroundColor: 'transparent' + }, + pagination: { + position: 'absolute', + bottom: 25, + left: 0, + right: 0, + flexDirection: 'row', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'transparent' + }, + dotStyle: { + width: 8, + height: 8, + borderRadius: 4, + marginLeft: 3, + marginRight: 3, + marginTop: 3, + marginBottom: 3 + } +}); diff --git a/package.json b/package.json index 5559bc3cb..3365c5fad 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "react-native-sentry": "0.15.1", "react-native-status-bar-size": "0.3.2", "react-native-svg": "5.4.1", - "react-native-swiper": "nixoz/react-native-swiper", "react-native-tooltip": "enahum/react-native-tooltip", "react-native-vector-icons": "4.3.0", "react-native-youtube": "1.0.0-beta.3", diff --git a/yarn.lock b/yarn.lock index 55ca80ee0..33a7d1cf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5011,12 +5011,6 @@ react-native-svg@5.4.1: color "^0.11.1" lodash "^4.16.6" -react-native-swiper@nixoz/react-native-swiper: - version "1.5.12" - resolved "https://codeload.github.com/nixoz/react-native-swiper/tar.gz/e6978073f082eb0aaa72fb1d85265f19b77c1c98" - dependencies: - prop-types "^15.5.10" - react-native-tooltip@enahum/react-native-tooltip: version "5.0.0" resolved "https://codeload.github.com/enahum/react-native-tooltip/tar.gz/97d58d19636df8d8df66d6b5737154c9fab727c8"