Add the paginator
This commit is contained in:
parent
1d134d705b
commit
19b2c3c41f
4 changed files with 76 additions and 248 deletions
|
|
@ -1,237 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {MutableRefObject, useCallback, useEffect, useRef} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, Platform, useWindowDimensions, View} from 'react-native';
|
||||
import Button from 'react-native-button';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
|
||||
import FloatingTextInput, {FloatingTextInputRef} from '@components/floating_text_input_label';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import Loading from '@components/loading';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
autoFocus?: boolean;
|
||||
buttonDisabled: boolean;
|
||||
connecting: boolean;
|
||||
displayName?: string;
|
||||
displayNameError?: string;
|
||||
handleConnect: () => void;
|
||||
handleDisplayNameTextChanged: (text: string) => void;
|
||||
handleUrlTextChanged: (text: string) => void;
|
||||
keyboardAwareRef: MutableRefObject<KeyboardAwareScrollView | null>;
|
||||
theme: Theme;
|
||||
url?: string;
|
||||
urlError?: string;
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
formContainer: {
|
||||
alignItems: 'center',
|
||||
maxWidth: 600,
|
||||
width: '100%',
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
enterServer: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
fullWidth: {
|
||||
width: '100%',
|
||||
},
|
||||
error: {
|
||||
marginBottom: 18,
|
||||
},
|
||||
chooseText: {
|
||||
alignSelf: 'flex-start',
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
marginTop: 8,
|
||||
...typography('Body', 75, 'Regular'),
|
||||
},
|
||||
connectButton: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
width: '100%',
|
||||
marginTop: 32,
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
padding: 15,
|
||||
},
|
||||
connectingIndicator: {
|
||||
marginRight: 10,
|
||||
},
|
||||
loadingContainerStyle: {
|
||||
marginRight: 10,
|
||||
padding: 0,
|
||||
top: -2,
|
||||
},
|
||||
}));
|
||||
|
||||
const ServerForm = ({
|
||||
autoFocus = false,
|
||||
buttonDisabled,
|
||||
connecting,
|
||||
displayName = '',
|
||||
displayNameError,
|
||||
handleConnect,
|
||||
handleDisplayNameTextChanged,
|
||||
handleUrlTextChanged,
|
||||
keyboardAwareRef,
|
||||
theme,
|
||||
url = '',
|
||||
urlError,
|
||||
}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const dimensions = useWindowDimensions();
|
||||
const displayNameRef = useRef<FloatingTextInputRef>(null);
|
||||
const urlRef = useRef<FloatingTextInputRef>(null);
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const focus = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
let offsetY = 160;
|
||||
if (isTablet) {
|
||||
const {width, height} = dimensions;
|
||||
const isLandscape = width > height;
|
||||
offsetY = isLandscape ? 230 : 100;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
keyboardAwareRef.current?.scrollToPosition(0, offsetY);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onBlur = useCallback(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
const reset = !displayNameRef.current?.isFocused() && !urlRef.current?.isFocused();
|
||||
if (reset) {
|
||||
keyboardAwareRef.current?.scrollToPosition(0, 0);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onConnect = useCallback(() => {
|
||||
Keyboard.dismiss();
|
||||
handleConnect();
|
||||
}, [buttonDisabled, connecting, displayName, theme, url]);
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
focus();
|
||||
}, [dimensions]);
|
||||
|
||||
const onUrlSubmit = useCallback(() => {
|
||||
displayNameRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'ios' && isTablet) {
|
||||
if (urlRef.current?.isFocused() || displayNameRef.current?.isFocused()) {
|
||||
focus();
|
||||
} else {
|
||||
keyboardAwareRef.current?.scrollToPosition(0, 0);
|
||||
}
|
||||
}
|
||||
}, [dimensions, isTablet]);
|
||||
|
||||
const buttonType = buttonDisabled ? 'disabled' : 'default';
|
||||
const styleButtonText = buttonTextStyle(theme, 'lg', 'primary', buttonType);
|
||||
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary', buttonType);
|
||||
|
||||
let buttonID = t('mobile.components.select_server_view.connect');
|
||||
let buttonText = 'Connect';
|
||||
let buttonIcon;
|
||||
|
||||
if (connecting) {
|
||||
buttonID = t('mobile.components.select_server_view.connecting');
|
||||
buttonText = 'Connecting';
|
||||
buttonIcon = (
|
||||
<Loading
|
||||
containerStyle={styles.loadingContainerStyle}
|
||||
color={theme.buttonColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const connectButtonTestId = buttonDisabled ? 'server_form.connect.button.disabled' : 'server_form.connect.button';
|
||||
|
||||
return (
|
||||
<View style={styles.formContainer}>
|
||||
<View style={[styles.fullWidth, urlError?.length ? styles.error : undefined]}>
|
||||
<FloatingTextInput
|
||||
autoCorrect={false}
|
||||
autoCapitalize={'none'}
|
||||
autoFocus={autoFocus}
|
||||
blurOnSubmit={false}
|
||||
containerStyle={styles.enterServer}
|
||||
enablesReturnKeyAutomatically={true}
|
||||
error={urlError}
|
||||
keyboardType='url'
|
||||
label={formatMessage({
|
||||
id: 'mobile.components.select_server_view.enterServerUrl',
|
||||
defaultMessage: 'Enter Server URL',
|
||||
})}
|
||||
onBlur={onBlur}
|
||||
onChangeText={handleUrlTextChanged}
|
||||
onFocus={onFocus}
|
||||
onSubmitEditing={onUrlSubmit}
|
||||
ref={urlRef}
|
||||
returnKeyType='next'
|
||||
spellCheck={false}
|
||||
testID='server_form.server_url.input'
|
||||
theme={theme}
|
||||
value={url}
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.fullWidth, displayNameError?.length ? styles.error : undefined]}>
|
||||
<FloatingTextInput
|
||||
autoCorrect={false}
|
||||
autoCapitalize={'none'}
|
||||
enablesReturnKeyAutomatically={true}
|
||||
error={displayNameError}
|
||||
label={formatMessage({
|
||||
id: 'mobile.components.select_server_view.displayName',
|
||||
defaultMessage: 'Display Name',
|
||||
})}
|
||||
onBlur={onBlur}
|
||||
onChangeText={handleDisplayNameTextChanged}
|
||||
onFocus={onFocus}
|
||||
onSubmitEditing={onConnect}
|
||||
ref={displayNameRef}
|
||||
returnKeyType='done'
|
||||
spellCheck={false}
|
||||
testID='server_form.server_display_name.input'
|
||||
theme={theme}
|
||||
value={displayName}
|
||||
/>
|
||||
</View>
|
||||
{!displayNameError &&
|
||||
<FormattedText
|
||||
defaultMessage={'Choose a display name for your server'}
|
||||
id={'mobile.components.select_server_view.displayHelp'}
|
||||
style={styles.chooseText}
|
||||
testID={'server_form.display_help'}
|
||||
/>
|
||||
}
|
||||
<Button
|
||||
containerStyle={[styles.connectButton, styleButtonBackground]}
|
||||
disabled={buttonDisabled}
|
||||
onPress={onConnect}
|
||||
testID={connectButtonTestId}
|
||||
>
|
||||
{buttonIcon}
|
||||
<FormattedText
|
||||
defaultMessage={buttonText}
|
||||
id={buttonID}
|
||||
style={styleButtonText}
|
||||
/>
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerForm;
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useRef} from 'react';
|
||||
import {Platform, Text, View, FlatList, ScrollView, ListRenderItemInfo} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
import React, {useCallback, useRef, useState} from 'react';
|
||||
import {Platform, Text, View, FlatList, Animated, ListRenderItemInfo, ViewToken} from 'react-native';
|
||||
import {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {generateId} from '@app/utils/general';
|
||||
import Background from '@screens/background';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import Paginator from './paginator';
|
||||
import SlideItem from './slide';
|
||||
import slidesData from './slides_data';
|
||||
|
||||
|
|
@ -25,6 +26,8 @@ const Onboarding = ({
|
|||
}: OnboardingProps) => {
|
||||
const translateX = useSharedValue(0);
|
||||
const styles = getStyleSheet(theme);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const slidesRef = useRef(null);
|
||||
|
||||
const transform = useAnimatedStyle(() => {
|
||||
const duration = Platform.OS === 'android' ? 250 : 350;
|
||||
|
|
@ -33,24 +36,32 @@ const Onboarding = ({
|
|||
};
|
||||
}, []);
|
||||
|
||||
const renderSlide = useCallback(({item: t}: ListRenderItemInfo<any>) => {
|
||||
const renderSlide = useCallback(({item: i}: ListRenderItemInfo<any>) => {
|
||||
return (
|
||||
<SlideItem
|
||||
item={t}
|
||||
item={i}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const scrollX = useRef(new Animated.Value(0)).current;
|
||||
|
||||
const viewableItemsChanged = useRef(({viewableItems}: any) => {
|
||||
setCurrentIndex(viewableItems[0].index);
|
||||
}).current;
|
||||
|
||||
const viewConfig = useRef({viewAreaCoveragePercentThreshold: 50}).current;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.flex}
|
||||
style={styles.onBoardingContainer}
|
||||
testID='onboarding.screen'
|
||||
>
|
||||
<Background theme={theme}/>
|
||||
<AnimatedSafeArea
|
||||
key={'onboarding_content'}
|
||||
style={[styles.flex, transform]}
|
||||
style={[styles.scrollContainer, transform]}
|
||||
>
|
||||
<FlatList
|
||||
keyExtractor={(item) => item.id}
|
||||
|
|
@ -61,20 +72,31 @@ const Onboarding = ({
|
|||
showsHorizontalScrollIndicator={true}
|
||||
pagingEnabled={true}
|
||||
bounces={false}
|
||||
onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}], {
|
||||
useNativeDriver: false,
|
||||
})}
|
||||
onViewableItemsChanged={viewableItemsChanged}
|
||||
viewabilityConfig={viewConfig}
|
||||
scrollEventThrottle={32}
|
||||
ref={slidesRef}
|
||||
/>
|
||||
</AnimatedSafeArea>
|
||||
<Paginator
|
||||
data={slidesData}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
appInfo: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
},
|
||||
flex: {
|
||||
onBoardingContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
scrollContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
|
|
|
|||
41
app/screens/onboarding/paginator.tsx
Normal file
41
app/screens/onboarding/paginator.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
data: any;
|
||||
theme: Theme;
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
dot: {
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
backgroundColor: theme.buttonBg,
|
||||
marginHorizontal: 8,
|
||||
width: 10,
|
||||
},
|
||||
}));
|
||||
|
||||
const Paginator = ({theme, data}: Props) => {
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={{flexDirection: 'row', height: 64}}>
|
||||
{data.map((item: any) => {
|
||||
return (
|
||||
<View
|
||||
style={[styles.dot]}
|
||||
key={item.id}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Paginator;
|
||||
|
|
@ -33,6 +33,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
},
|
||||
itemContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue