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:
parent
e31d1834f6
commit
271a757505
7 changed files with 344 additions and 93 deletions
33
NOTICE.txt
33
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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
291
app/components/swiper.js
Normal 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
|
||||
}
|
||||
});
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue