Refactor channel swiper (#933)

* Refactor channel swiper

* Feedback review

* clean up

* another feedback review

* Remove unnecessary props

* clean up initialState
This commit is contained in:
enahum 2017-09-25 14:40:30 -03:00 committed by GitHub
parent e31d1834f6
commit 271a757505
7 changed files with 344 additions and 93 deletions

View file

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

View file

@ -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 = (
<View style={style.swiperContent}>
<TeamsList
closeChannelDrawer={this.closeChannelDrawer}
navigator={navigator}
/>
</View>
);
let teams;
if (showTeams) {
teams = (
<View style={style.swiperContent}>
<TeamsList
closeChannelDrawer={this.closeChannelDrawer}
navigator={navigator}
/>
</View>
);
if (this.drawerSwiper) {
this.drawerSwiper.getWrappedInstance().runOnLayout();
}
} else if (this.drawerSwiper && !openDrawerOffset) {
this.drawerSwiper.getWrappedInstance().scrollToStart();
}
const channelsList = (
<View style={style.swiperContent}>
@ -344,7 +355,7 @@ export default class ChannelDrawer extends PureComponent {
{channelsList}
</DrawerSwiper>
);
}
};
render() {
const {children} = this.props;

View file

@ -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 (
<Swiper
ref='swiper'
horizontal={true}
loop={false}
index={this.state.index}
onMomentumScrollEnd={this.swiperPageSelected}
paginationStyle={[{position: 'absolute'}, pagination]}
initialPage={1}
onIndexChanged={this.swiperPageSelected}
paginationStyle={style.pagination}
width={deviceWidth - openDrawerOffset}
height={deviceHeight}
style={{backgroundColor: theme.sidebarBg}}
activeDotColor={theme.sidebarText}
dotColor={changeOpacity(theme.sidebarText, 0.5)}
removeClippedSubviews={true}
automaticallyAdjustContentInsets={true}
scrollEnabled={showTeams}
showsPagination={showTeams}
keyboardShouldPersistTaps={'always'}
@ -91,3 +72,10 @@ export default class DrawerSwiper extends PureComponent {
);
}
}
const style = StyleSheet.create({
pagination: {
bottom: 0,
position: 'absolute'
}
});

View file

@ -208,6 +208,7 @@ export default class SearchBarAndroid extends PureComponent {
placeholderTextColor={placeholderTextColor}
selectionColor={selectionColor}
underlineColorAndroid='transparent'
disableFullscreenUI={true}
style={[
styles.searchBarInput,
inputNoBackground,

291
app/components/swiper.js Normal file
View file

@ -0,0 +1,291 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
InteractionManager,
View,
ScrollView,
ViewPagerAndroid,
Platform,
StyleSheet
} from 'react-native';
export default class Swiper extends PureComponent {
static propTypes = {
activeDotColor: PropTypes.string,
children: PropTypes.node.isRequired,
dotColor: PropTypes.string,
initialPage: PropTypes.number,
keyboardShouldPersistTaps: PropTypes.string,
onIndexChanged: PropTypes.func,
paginationStyle: PropTypes.oneOfType([
PropTypes.object,
PropTypes.number
]),
scrollEnabled: PropTypes.bool,
showsPagination: PropTypes.bool,
style: PropTypes.oneOfType([
PropTypes.object,
PropTypes.number
]),
width: PropTypes.number
};
static defaultProps = {
initialPage: 0,
keyboardShouldPersistTaps: 'handled',
onIndexChanged: () => 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 (
<ScrollView
ref={this.refScrollView}
horizontal={true}
removeClippedSubviews={true}
automaticallyAdjustContentInsets={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
contentContainerStyle={[styles.wrapperIOS, this.props.style]}
onScrollBeginDrag={this.onScrollBegin}
onMomentumScrollEnd={this.onScrollEnd}
pagingEnabled={true}
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
scrollEnabled={scrollEnabled}
>
{pages}
</ScrollView>
);
}
return (
<ViewPagerAndroid
ref={this.refScrollView}
initialPage={this.props.initialPage}
onScrollBeginDrag={this.onScrollBegin}
onPageSelected={this.onScrollEnd}
scrollEnabled={scrollEnabled}
key={pages.length}
style={[styles.wrapperAndroid, this.props.style]}
>
{pages}
</ViewPagerAndroid>
);
};
renderPagination = () => {
if (this.state.total <= 1 || !this.props.showsPagination) {
return null;
}
const dots = [];
const activeDot = (
<View
style={[
styles.dotStyle,
{backgroundColor: this.props.activeDotColor || '#007aff'}
]}
/>
);
const dot = (
<View
style={[
styles.dotStyle,
{backgroundColor: this.props.dotColor || 'rgba(0,0,0,.2)'}
]}
/>
);
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 (
<View
pointerEvents='none'
style={[styles.pagination, this.props.paginationStyle]}
>
{dots}
</View>
);
};
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 (
<View
style={[styles.slide, pageStyle]}
key={i}
>
{page}
</View>
);
});
return (
<View
style={[styles.container]}
onLayout={this.onLayout}
>
{this.renderScrollView(pages)}
{this.renderPagination()}
</View>
);
}
}
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
}
});

View file

@ -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",

View file

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