From b7e8c3b739ab1ff17f1faeb9d4e305bce76e99da Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 25 Oct 2022 08:19:44 +0200 Subject: [PATCH 01/50] MM-35065 - add addonboarding screens; first steps --- app/constants/screens.ts | 2 + app/init/launch.ts | 9 +- app/screens/index.tsx | 2 + app/screens/navigation.ts | 48 ++++++ app/screens/onboarding/form.tsx | 237 ++++++++++++++++++++++++++++++ app/screens/onboarding/header.tsx | 91 ++++++++++++ app/screens/onboarding/index.tsx | 80 ++++++++++ index.ts | 5 + package.json | 2 +- 9 files changed, 473 insertions(+), 3 deletions(-) create mode 100644 app/screens/onboarding/form.tsx create mode 100644 app/screens/onboarding/header.tsx create mode 100644 app/screens/onboarding/index.tsx diff --git a/app/constants/screens.ts b/app/constants/screens.ts index ddef79467..a5aab658c 100644 --- a/app/constants/screens.ts +++ b/app/constants/screens.ts @@ -33,6 +33,7 @@ export const LATEX = 'Latex'; export const LOGIN = 'Login'; export const MENTIONS = 'Mentions'; export const MFA = 'MFA'; +export const ONBOARDING = 'Onboarding'; export const PERMALINK = 'Permalink'; export const PINNED_MESSAGES = 'PinnedMessages'; export const POST_OPTIONS = 'PostOptions'; @@ -94,6 +95,7 @@ export default { LOGIN, MENTIONS, MFA, + ONBOARDING, PERMALINK, PINNED_MESSAGES, POST_OPTIONS, diff --git a/app/init/launch.ts b/app/init/launch.ts index 04aa737a0..6a372a61a 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -12,7 +12,7 @@ import {getActiveServerUrl, getServerCredentials, removeServerCredentials} from import {getThemeForCurrentTeam} from '@queries/servers/preference'; import {getCurrentUserId} from '@queries/servers/system'; import {queryMyTeams} from '@queries/servers/team'; -import {goToScreen, resetToHome, resetToSelectServer, resetToTeams} from '@screens/navigation'; +import {goToScreen, resetToHome, resetToSelectServer, resetToTeams, resetToOnboarding} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import {logInfo} from '@utils/log'; import {convertToNotificationData} from '@utils/notification'; @@ -58,6 +58,7 @@ const launchAppFromNotification = async (notification: NotificationWithData) => launchApp(props); }; +// pablo - here I need to validate the logic for launching the onboarding screen const launchApp = async (props: LaunchProps, resetNavigation = true) => { let serverUrl: string | undefined; switch (props?.launchType) { @@ -126,7 +127,7 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } - return launchToServer(props, resetNavigation); + return launchToOnboarding(props); }; const launchToHome = async (props: LaunchProps) => { @@ -180,6 +181,10 @@ const launchToServer = (props: LaunchProps, resetNavigation: Boolean) => { return goToScreen(Screens.SERVER, title, {...props}); }; +const launchToOnboarding = (props: LaunchProps) => { + return resetToOnboarding(props); +}; + export const relaunchApp = (props: LaunchProps, resetNavigation = false) => { return launchApp(props, resetNavigation); }; diff --git a/app/screens/index.tsx b/app/screens/index.tsx index baa707d88..68b7f3798 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -227,6 +227,8 @@ Navigation.setLazyComponentRegistrator((screenName) => { export function registerScreens() { const homeScreen = require('@screens/home').default; const serverScreen = require('@screens/server').default; + const onboardingScreen = require('@screens/onboarding').default; + Navigation.registerComponent(Screens.ONBOARDING, () => withGestures(withIntl(withManagedConfig(onboardingScreen)), undefined)); Navigation.registerComponent(Screens.SERVER, () => withGestures(withIntl(withManagedConfig(serverScreen)), undefined)); Navigation.registerComponent(Screens.HOME, () => withGestures(withSafeAreaInsets(withServerDatabase(withManagedConfig(homeScreen))), undefined)); } diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 7eb8fda44..b98575420 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -281,6 +281,54 @@ export function resetToSelectServer(passProps: LaunchProps) { }); } +export function resetToOnboarding(passProps: LaunchProps) { + const theme = getDefaultThemeByAppearance(); + const isDark = tinyColor(theme.sidebarBg).isDark(); + StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); + + NavigationStore.clearNavigationComponents(); + + const children = [{ + component: { + id: Screens.ONBOARDING, + name: Screens.ONBOARDING, + passProps: { + ...passProps, + theme, + }, + options: { + layout: { + backgroundColor: theme.centerChannelBg, + componentBackgroundColor: theme.centerChannelBg, + }, + statusBar: { + visible: true, + backgroundColor: theme.sidebarBg, + }, + topBar: { + backButton: { + color: theme.sidebarHeaderTextColor, + title: '', + }, + background: { + color: theme.sidebarBg, + }, + visible: false, + height: 0, + }, + }, + }, + }]; + + return Navigation.setRoot({ + root: { + stack: { + children, + }, + }, + }); +} + export function resetToTeams() { const theme = getThemeFromState(); const isDark = tinyColor(theme.sidebarBg).isDark(); diff --git a/app/screens/onboarding/form.tsx b/app/screens/onboarding/form.tsx new file mode 100644 index 000000000..9285a8c65 --- /dev/null +++ b/app/screens/onboarding/form.tsx @@ -0,0 +1,237 @@ +// 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; + 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(null); + const urlRef = useRef(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 = ( + + ); + } + + const connectButtonTestId = buttonDisabled ? 'server_form.connect.button.disabled' : 'server_form.connect.button'; + + return ( + + + + + + + + {!displayNameError && + + } + + + ); +}; + +export default ServerForm; diff --git a/app/screens/onboarding/header.tsx b/app/screens/onboarding/header.tsx new file mode 100644 index 000000000..d7fd475cb --- /dev/null +++ b/app/screens/onboarding/header.tsx @@ -0,0 +1,91 @@ +// 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 FormattedText from '@components/formatted_text'; +import {useIsTablet} from '@hooks/device'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +type Props = { + additionalServer: boolean; + theme: Theme; +}; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + textContainer: { + marginBottom: 32, + maxWidth: 600, + width: '100%', + paddingHorizontal: 20, + }, + welcome: { + marginTop: 12, + color: changeOpacity(theme.centerChannelColor, 0.64), + ...typography('Heading', 400, 'SemiBold'), + }, + connect: { + width: 270, + letterSpacing: -1, + color: theme.centerChannelColor, + marginVertical: 12, + ...typography('Heading', 1000, 'SemiBold'), + }, + connectTablet: { + width: undefined, + }, + description: { + color: changeOpacity(theme.centerChannelColor, 0.64), + ...typography('Body', 200, 'Regular'), + }, +})); + +const ServerHeader = ({additionalServer, theme}: Props) => { + const isTablet = useIsTablet(); + const styles = getStyleSheet(theme); + + let title; + if (additionalServer) { + title = ( + + ); + } else { + title = ( + + ); + } + + return ( + + {!additionalServer && + + } + {title} + + + ); +}; + +export default ServerHeader; diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx new file mode 100644 index 000000000..2b13f582d --- /dev/null +++ b/app/screens/onboarding/index.tsx @@ -0,0 +1,80 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useRef} from 'react'; +import {Platform, Text, View} from 'react-native'; +import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {SafeAreaView} from 'react-native-safe-area-context'; + +import AppVersion from '@components/app_version'; +import Background from '@screens/background'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; + +import type {LaunchProps} from '@typings/launch'; + +interface OnboardingProps extends LaunchProps { + theme: Theme; +} +const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView); + +const Onboarding = ({ + theme, +}: OnboardingProps) => { + const translateX = useSharedValue(0); + const keyboardAwareRef = useRef(null); + const styles = getStyleSheet(theme); + + const transform = useAnimatedStyle(() => { + const duration = Platform.OS === 'android' ? 250 : 350; + return { + transform: [{translateX: withTiming(translateX.value, {duration})}], + }; + }, []); + + return ( + + + + + {'Hola mundo'} + + + + + ); +}; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + appInfo: { + color: changeOpacity(theme.centerChannelColor, 0.56), + }, + flex: { + flex: 1, + }, + scrollContainer: { + alignItems: 'center', + height: '100%', + justifyContent: 'center', + }, +})); + +export default Onboarding; diff --git a/index.ts b/index.ts index ccceb3ec9..9f8d9199c 100644 --- a/index.ts +++ b/index.ts @@ -67,6 +67,7 @@ if (global.HermesInternal) { let alreadyInitialized = false; Navigation.events().registerAppLaunchedListener(async () => { // See caution in the library doc https://wix.github.io/react-native-navigation/docs/app-launch#android + console.log('*** already init', alreadyInitialized); if (!alreadyInitialized) { alreadyInitialized = true; GlobalEventHandler.init(); @@ -82,8 +83,12 @@ Navigation.events().registerAppLaunchedListener(async () => { await WebsocketManager.init(serverCredentials); PushNotifications.init(); SessionManager.init(); + console.log('*** already init 2', alreadyInitialized); } + console.log('*** already init 3', alreadyInitialized); + + initialLaunch(); }); diff --git a/package.json b/package.json index be35b8c9a..b5636dd4e 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "mmjstool": "mmjstool", "pod-install": "cd ios && bundle exec pod install", "postinstall": "patch-package && ./scripts/postinstall.sh", - "preinstall": "npx solidarity", + "preinstall": "", "prestorybook": "rnstl", "start": "react-native start", "storybook": "start-storybook -p 7007", From 1d134d705b27279df5a484b09ea1baa95fc0b2b2 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 25 Oct 2022 13:18:17 +0200 Subject: [PATCH 02/50] add images, add initial flat list with horizontal scroll --- app/screens/onboarding/header.tsx | 91 -------- .../onboarding/illustrations/calls.svg | 64 ++++++ app/screens/onboarding/illustrations/chat.svg | 210 ++++++++++++++++++ .../onboarding/illustrations/integrations.svg | 73 ++++++ .../illustrations/team_communication.svg | 72 ++++++ app/screens/onboarding/index.tsx | 48 ++-- app/screens/onboarding/slide.tsx | 57 +++++ app/screens/onboarding/slides_data.ts | 30 +++ index.ts | 5 - 9 files changed, 532 insertions(+), 118 deletions(-) delete mode 100644 app/screens/onboarding/header.tsx create mode 100644 app/screens/onboarding/illustrations/calls.svg create mode 100644 app/screens/onboarding/illustrations/chat.svg create mode 100644 app/screens/onboarding/illustrations/integrations.svg create mode 100644 app/screens/onboarding/illustrations/team_communication.svg create mode 100644 app/screens/onboarding/slide.tsx create mode 100644 app/screens/onboarding/slides_data.ts diff --git a/app/screens/onboarding/header.tsx b/app/screens/onboarding/header.tsx deleted file mode 100644 index d7fd475cb..000000000 --- a/app/screens/onboarding/header.tsx +++ /dev/null @@ -1,91 +0,0 @@ -// 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 FormattedText from '@components/formatted_text'; -import {useIsTablet} from '@hooks/device'; -import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; -import {typography} from '@utils/typography'; - -type Props = { - additionalServer: boolean; - theme: Theme; -}; - -const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ - textContainer: { - marginBottom: 32, - maxWidth: 600, - width: '100%', - paddingHorizontal: 20, - }, - welcome: { - marginTop: 12, - color: changeOpacity(theme.centerChannelColor, 0.64), - ...typography('Heading', 400, 'SemiBold'), - }, - connect: { - width: 270, - letterSpacing: -1, - color: theme.centerChannelColor, - marginVertical: 12, - ...typography('Heading', 1000, 'SemiBold'), - }, - connectTablet: { - width: undefined, - }, - description: { - color: changeOpacity(theme.centerChannelColor, 0.64), - ...typography('Body', 200, 'Regular'), - }, -})); - -const ServerHeader = ({additionalServer, theme}: Props) => { - const isTablet = useIsTablet(); - const styles = getStyleSheet(theme); - - let title; - if (additionalServer) { - title = ( - - ); - } else { - title = ( - - ); - } - - return ( - - {!additionalServer && - - } - {title} - - - ); -}; - -export default ServerHeader; diff --git a/app/screens/onboarding/illustrations/calls.svg b/app/screens/onboarding/illustrations/calls.svg new file mode 100644 index 000000000..7015eeb94 --- /dev/null +++ b/app/screens/onboarding/illustrations/calls.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/screens/onboarding/illustrations/chat.svg b/app/screens/onboarding/illustrations/chat.svg new file mode 100644 index 000000000..8441ba906 --- /dev/null +++ b/app/screens/onboarding/illustrations/chat.svg @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/screens/onboarding/illustrations/integrations.svg b/app/screens/onboarding/illustrations/integrations.svg new file mode 100644 index 000000000..7153d8fec --- /dev/null +++ b/app/screens/onboarding/illustrations/integrations.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/screens/onboarding/illustrations/team_communication.svg b/app/screens/onboarding/illustrations/team_communication.svg new file mode 100644 index 000000000..b93259269 --- /dev/null +++ b/app/screens/onboarding/illustrations/team_communication.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 2b13f582d..08f8ea1c5 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -1,16 +1,18 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useRef} from 'react'; -import {Platform, Text, View} from 'react-native'; -import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; +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 {SafeAreaView} from 'react-native-safe-area-context'; -import AppVersion from '@components/app_version'; +import {generateId} from '@app/utils/general'; import Background from '@screens/background'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import SlideItem from './slide'; +import slidesData from './slides_data'; + import type {LaunchProps} from '@typings/launch'; interface OnboardingProps extends LaunchProps { @@ -22,7 +24,6 @@ const Onboarding = ({ theme, }: OnboardingProps) => { const translateX = useSharedValue(0); - const keyboardAwareRef = useRef(null); const styles = getStyleSheet(theme); const transform = useAnimatedStyle(() => { @@ -32,32 +33,35 @@ const Onboarding = ({ }; }, []); + const renderSlide = useCallback(({item: t}: ListRenderItemInfo) => { + return ( + + ); + }, []); + return ( - item.id} + data={slidesData} + renderItem={renderSlide} + listKey={generateId()} + horizontal={true} + showsHorizontalScrollIndicator={true} + pagingEnabled={true} bounces={false} - contentContainerStyle={styles.scrollContainer} - enableAutomaticScroll={Platform.OS === 'android'} - enableOnAndroid={false} - enableResetScrollToCoords={true} - extraScrollHeight={20} - keyboardDismissMode='on-drag' - keyboardShouldPersistTaps='handled' - ref={keyboardAwareRef} - scrollToOverflowEnabled={true} - style={styles.flex} - > - {'Hola mundo'} - - + /> ); diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx new file mode 100644 index 000000000..039baae95 --- /dev/null +++ b/app/screens/onboarding/slide.tsx @@ -0,0 +1,57 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Text, View, useWindowDimensions} from 'react-native'; + +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +type Props = { + item: any; + theme: Theme; +}; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + title: { + fontWeight: '800', + fontSize: 28, + marginBottom: 10, + color: theme.centerChannelColor, + textAlign: 'center', + }, + description: { + fontWeight: '300', + textAlign: 'center', + paddingHorizontal: 64, + color: changeOpacity(theme.centerChannelColor, 0.64), + ...typography('Body', 200, 'Regular'), + }, + image: { + flex: 0.7, + justifyContent: 'center', + }, + itemContainer: { + flex: 1, + }, +})); + +const SlideItem = ({theme, item}: Props) => { + const {width} = useWindowDimensions(); + const styles = getStyleSheet(theme); + const SvgImg = item.image; + + return ( + + + + {item.title} + {item.description} + + + ); +}; + +export default SlideItem; diff --git a/app/screens/onboarding/slides_data.ts b/app/screens/onboarding/slides_data.ts new file mode 100644 index 000000000..331dbc858 --- /dev/null +++ b/app/screens/onboarding/slides_data.ts @@ -0,0 +1,30 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// pablo - translate each text. turn svgs into react components? +export default [ + { + id: '1', + title: 'Welcome', + description: 'Mattermost is an open source platform for developer collaboration. Secure, flexible, and integrated with your tools.', + image: require('./illustrations/chat.svg').default, + }, + { + id: '2', + title: 'Collaborate in real-time', + description: 'Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.', + image: require('./illustrations/team_communication.svg').default, + }, + { + id: '3', + title: 'Start secure audio calls instantly', + description: 'When typing isn’t fast enough, switch from channel-based chat to secure audio calls with a single tap.', + image: require('./illustrations/calls.svg').default, + }, + { + id: '4', + title: 'Integrate with tools you love', + description: 'Go beyond chat with tightly-integratedproduct solutions matched to common development processes.', + image: require('./illustrations/integrations.svg').default, + }, +]; diff --git a/index.ts b/index.ts index 9f8d9199c..ccceb3ec9 100644 --- a/index.ts +++ b/index.ts @@ -67,7 +67,6 @@ if (global.HermesInternal) { let alreadyInitialized = false; Navigation.events().registerAppLaunchedListener(async () => { // See caution in the library doc https://wix.github.io/react-native-navigation/docs/app-launch#android - console.log('*** already init', alreadyInitialized); if (!alreadyInitialized) { alreadyInitialized = true; GlobalEventHandler.init(); @@ -83,12 +82,8 @@ Navigation.events().registerAppLaunchedListener(async () => { await WebsocketManager.init(serverCredentials); PushNotifications.init(); SessionManager.init(); - console.log('*** already init 2', alreadyInitialized); } - console.log('*** already init 3', alreadyInitialized); - - initialLaunch(); }); From 19b2c3c41fa4a2edd8b757289f96f6e072ee8617 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 25 Oct 2022 14:06:52 +0200 Subject: [PATCH 03/50] Add the paginator --- app/screens/onboarding/form.tsx | 237 --------------------------- app/screens/onboarding/index.tsx | 44 +++-- app/screens/onboarding/paginator.tsx | 41 +++++ app/screens/onboarding/slide.tsx | 2 + 4 files changed, 76 insertions(+), 248 deletions(-) delete mode 100644 app/screens/onboarding/form.tsx create mode 100644 app/screens/onboarding/paginator.tsx diff --git a/app/screens/onboarding/form.tsx b/app/screens/onboarding/form.tsx deleted file mode 100644 index 9285a8c65..000000000 --- a/app/screens/onboarding/form.tsx +++ /dev/null @@ -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; - 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(null); - const urlRef = useRef(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 = ( - - ); - } - - const connectButtonTestId = buttonDisabled ? 'server_form.connect.button.disabled' : 'server_form.connect.button'; - - return ( - - - - - - - - {!displayNameError && - - } - - - ); -}; - -export default ServerForm; diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 08f8ea1c5..55a7b44ca 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -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) => { + const renderSlide = useCallback(({item: i}: ListRenderItemInfo) => { return ( ); }, []); + 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 ( 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} /> + ); }; 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', diff --git a/app/screens/onboarding/paginator.tsx b/app/screens/onboarding/paginator.tsx new file mode 100644 index 000000000..31ca744d0 --- /dev/null +++ b/app/screens/onboarding/paginator.tsx @@ -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 ( + + {data.map((item: any) => { + return ( + + ); + })} + + ); +}; + +export default Paginator; diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 039baae95..085a2b0fe 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -33,6 +33,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, itemContainer: { flex: 1, + alignItems: 'center', + justifyContent: 'center', }, })); From 519fcd2181ea6996aeb80022c55e4cc510547787 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 25 Oct 2022 17:43:28 +0200 Subject: [PATCH 04/50] add the paginator animations shadows --- app/screens/onboarding/index.tsx | 19 ++++++- app/screens/onboarding/paginator.tsx | 82 +++++++++++++++++++++++----- app/screens/onboarding/slide.tsx | 17 ++++-- 3 files changed, 97 insertions(+), 21 deletions(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 55a7b44ca..e076fa92e 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -2,13 +2,13 @@ // See LICENSE.txt for license information. import React, {useCallback, useRef, useState} from 'react'; -import {Platform, Text, View, FlatList, Animated, ListRenderItemInfo, ViewToken} from 'react-native'; +import {Platform, View, FlatList, Animated, ListRenderItemInfo} 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 {makeStyleSheetFromTheme} from '@utils/theme'; import Paginator from './paginator'; import SlideItem from './slide'; @@ -29,6 +29,18 @@ const Onboarding = ({ const [currentIndex, setCurrentIndex] = useState(0); const slidesRef = useRef(null); + const nextSlide = () => { + console.log('\n*** slidesRef 1\n'); + if (slidesRef.current && currentIndex < slidesData.length - 1) { + console.log('\n*** slidesRef 2\n'); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + slidesRef.current.scrollToIndex({index: currentIndex + 1}); + } else { + console.log('*** end of slide'); + } + }; + const transform = useAnimatedStyle(() => { const duration = Platform.OS === 'android' ? 250 : 350; return { @@ -84,6 +96,8 @@ const Onboarding = ({ ); @@ -94,6 +108,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ flex: 1, alignItems: 'center', justifyContent: 'center', + verticalAlign: 'top', }, scrollContainer: { flex: 1, diff --git a/app/screens/onboarding/paginator.tsx b/app/screens/onboarding/paginator.tsx index 31ca744d0..83b9de914 100644 --- a/app/screens/onboarding/paginator.tsx +++ b/app/screens/onboarding/paginator.tsx @@ -2,38 +2,94 @@ // See LICENSE.txt for license information. import React from 'react'; -import {View} from 'react-native'; +import {View, Animated, useWindowDimensions} from 'react-native'; +import Button from 'react-native-button'; -import {makeStyleSheetFromTheme} from '@utils/theme'; +import CompassIcon from '@app/components/compass_icon'; +import FormattedText from '@app/components/formatted_text'; +import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; type Props = { data: any; theme: Theme; + scrollX: any; + nextSlideHandler: any; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ dot: { - height: 10, + height: 8, borderRadius: 5, backgroundColor: theme.buttonBg, marginHorizontal: 8, - width: 10, + width: 8, + + // shadow + shadowOffset: {width: 0, height: 13}, + shadowOpacity: 0.7, + shadowColor: theme.buttonBg, + shadowRadius: 6, + elevation: 3, + }, + button: { + marginTop: 5, + }, + rowIcon: { + color: theme.buttonColor, + fontSize: 12, + marginLeft: 5, }, })); -const Paginator = ({theme, data}: Props) => { +const Paginator = ({theme, data, scrollX, nextSlideHandler}: Props) => { const styles = getStyleSheet(theme); + const {width} = useWindowDimensions(); return ( - - {data.map((item: any) => { - return ( - + + {data.map((item: any, i: number) => { + const inputRange = [(i - 1) * width, i * width, (i + 1) * width]; + + const opacity = scrollX.interpolate({ + inputRange, + outputRange: [0.3, 1, 0.3], + extrapolate: 'clamp', + }); + + return ( + + ); + })} + + + + ); }; diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 085a2b0fe..fae28d79b 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -14,22 +14,27 @@ type Props = { const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ title: { - fontWeight: '800', - fontSize: 28, - marginBottom: 10, + fontWeight: '600', + fontSize: 40, + marginBottom: 5, + height: 100, color: theme.centerChannelColor, textAlign: 'center', }, description: { - fontWeight: '300', + fontWeight: '400', + fontSize: 16, textAlign: 'center', - paddingHorizontal: 64, + paddingHorizontal: 20, + height: 80, color: changeOpacity(theme.centerChannelColor, 0.64), ...typography('Body', 200, 'Regular'), }, image: { - flex: 0.7, justifyContent: 'center', + height: 60, + maxHeight: 120, + width: 50, }, itemContainer: { flex: 1, From 92087b8c3f9716501fbc2163da9bfdfc49a796c6 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Thu, 27 Oct 2022 13:22:55 +0200 Subject: [PATCH 05/50] add the sign in handler --- app/screens/onboarding/index.tsx | 6 +++++- app/screens/onboarding/paginator.tsx | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index e076fa92e..1ffee91a1 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -40,6 +40,9 @@ const Onboarding = ({ console.log('*** end of slide'); } }; + const signInHandler = () => { + console.log('sign in handler'); + }; const transform = useAnimatedStyle(() => { const duration = Platform.OS === 'android' ? 250 : 350; @@ -81,7 +84,7 @@ const Onboarding = ({ renderItem={renderSlide} listKey={generateId()} horizontal={true} - showsHorizontalScrollIndicator={true} + showsHorizontalScrollIndicator={false} pagingEnabled={true} bounces={false} onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}], { @@ -98,6 +101,7 @@ const Onboarding = ({ theme={theme} scrollX={scrollX} nextSlideHandler={nextSlide} + signInHandler={signInHandler} /> ); diff --git a/app/screens/onboarding/paginator.tsx b/app/screens/onboarding/paginator.tsx index 83b9de914..3bca52537 100644 --- a/app/screens/onboarding/paginator.tsx +++ b/app/screens/onboarding/paginator.tsx @@ -15,6 +15,7 @@ type Props = { theme: Theme; scrollX: any; nextSlideHandler: any; + signInHandler: any; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -42,7 +43,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, })); -const Paginator = ({theme, data, scrollX, nextSlideHandler}: Props) => { +const Paginator = ({ + theme, + data, + scrollX, + nextSlideHandler, + signInHandler, +}: Props) => { const styles = getStyleSheet(theme); const {width} = useWindowDimensions(); @@ -89,6 +96,17 @@ const Paginator = ({theme, data, scrollX, nextSlideHandler}: Props) => { style={styles.rowIcon} /> + ); From 50b832fbad32f1da0e33196a760ef15162e4fd69 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Thu, 27 Oct 2022 14:23:14 +0200 Subject: [PATCH 06/50] use last index to modify buttons texts --- app/screens/onboarding/index.tsx | 27 ++++++++--- app/screens/onboarding/paginator.tsx | 72 +++++++++++++++++++--------- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 1ffee91a1..1f5c917db 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -27,19 +27,30 @@ const Onboarding = ({ const translateX = useSharedValue(0); const styles = getStyleSheet(theme); const [currentIndex, setCurrentIndex] = useState(0); + const [isLastSlide, setIsLastSlide] = useState(false); + const lastSlideIndex = slidesData.length - 1; const slidesRef = useRef(null); const nextSlide = () => { - console.log('\n*** slidesRef 1\n'); - if (slidesRef.current && currentIndex < slidesData.length - 1) { - console.log('\n*** slidesRef 2\n'); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - slidesRef.current.scrollToIndex({index: currentIndex + 1}); + const nextSlideIndex = currentIndex + 1; + if (slidesRef.current && currentIndex < lastSlideIndex) { + console.log('*** current slide', currentIndex); + console.log('*** next slide', nextSlideIndex); + moveToSlide(nextSlideIndex); } else { - console.log('*** end of slide'); + console.log('*** end of slide', lastSlideIndex); } }; + + const moveToSlide = (slideIndexToMove: number) => { + if (slideIndexToMove === lastSlideIndex) { + setIsLastSlide(true); + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + slidesRef?.current?.scrollToIndex({index: slideIndexToMove}); + }; + const signInHandler = () => { console.log('sign in handler'); }; @@ -100,8 +111,10 @@ const Onboarding = ({ data={slidesData} theme={theme} scrollX={scrollX} + isLastSlide={isLastSlide} nextSlideHandler={nextSlide} signInHandler={signInHandler} + moveToSlide={moveToSlide} /> ); diff --git a/app/screens/onboarding/paginator.tsx b/app/screens/onboarding/paginator.tsx index 3bca52537..91b6f2c8f 100644 --- a/app/screens/onboarding/paginator.tsx +++ b/app/screens/onboarding/paginator.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React from 'react'; -import {View, Animated, useWindowDimensions} from 'react-native'; +import {View, Animated, useWindowDimensions, TouchableOpacity} from 'react-native'; import Button from 'react-native-button'; import CompassIcon from '@app/components/compass_icon'; @@ -14,8 +14,10 @@ type Props = { data: any; theme: Theme; scrollX: any; + isLastSlide: boolean; nextSlideHandler: any; signInHandler: any; + moveToSlide: any; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -49,10 +51,39 @@ const Paginator = ({ scrollX, nextSlideHandler, signInHandler, + moveToSlide, + isLastSlide, }: Props) => { const styles = getStyleSheet(theme); const {width} = useWindowDimensions(); + let mainButtonText = ( + <> + + + + ); + + let mainButtonAction = nextSlideHandler; + + if (isLastSlide) { + mainButtonText = ( + + ); + mainButtonAction = signInHandler; + } + return ( @@ -66,35 +97,32 @@ const Paginator = ({ }); return ( - moveToSlide(i)} key={item.id} - /> + > + + ); })} + + + ); +}; + +export default FooterButtons; diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 1f5c917db..6e2b07bd0 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -15,6 +15,7 @@ import SlideItem from './slide'; import slidesData from './slides_data'; import type {LaunchProps} from '@typings/launch'; +import FooterButtons from './footer_buttons'; interface OnboardingProps extends LaunchProps { theme: Theme; @@ -34,18 +35,13 @@ const Onboarding = ({ const nextSlide = () => { const nextSlideIndex = currentIndex + 1; if (slidesRef.current && currentIndex < lastSlideIndex) { - console.log('*** current slide', currentIndex); - console.log('*** next slide', nextSlideIndex); moveToSlide(nextSlideIndex); - } else { - console.log('*** end of slide', lastSlideIndex); } }; const moveToSlide = (slideIndexToMove: number) => { - if (slideIndexToMove === lastSlideIndex) { - setIsLastSlide(true); - } + setIsLastSlide(slideIndexToMove === lastSlideIndex); + setCurrentIndex(slideIndexToMove); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore slidesRef?.current?.scrollToIndex({index: slideIndexToMove}); @@ -67,6 +63,7 @@ const Onboarding = ({ ); }, []); @@ -75,6 +72,7 @@ const Onboarding = ({ const viewableItemsChanged = useRef(({viewableItems}: any) => { setCurrentIndex(viewableItems[0].index); + setIsLastSlide(viewableItems[0].index === lastSlideIndex); }).current; const viewConfig = useRef({viewAreaCoveragePercentThreshold: 50}).current; @@ -89,7 +87,7 @@ const Onboarding = ({ key={'onboarding_content'} style={[styles.scrollContainer, transform]} > - item.id} data={slidesData} renderItem={renderSlide} @@ -99,7 +97,7 @@ const Onboarding = ({ pagingEnabled={true} bounces={false} onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}], { - useNativeDriver: false, + useNativeDriver: true, })} onViewableItemsChanged={viewableItemsChanged} viewabilityConfig={viewConfig} @@ -111,10 +109,13 @@ const Onboarding = ({ data={slidesData} theme={theme} scrollX={scrollX} + moveToSlide={moveToSlide} + /> + ); diff --git a/app/screens/onboarding/paginator.tsx b/app/screens/onboarding/paginator.tsx index 91b6f2c8f..b111f07d0 100644 --- a/app/screens/onboarding/paginator.tsx +++ b/app/screens/onboarding/paginator.tsx @@ -3,20 +3,13 @@ import React from 'react'; import {View, Animated, useWindowDimensions, TouchableOpacity} from 'react-native'; -import Button from 'react-native-button'; -import CompassIcon from '@app/components/compass_icon'; -import FormattedText from '@app/components/formatted_text'; -import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; type Props = { data: any; theme: Theme; scrollX: any; - isLastSlide: boolean; - nextSlideHandler: any; - signInHandler: any; moveToSlide: any; }; @@ -49,43 +42,13 @@ const Paginator = ({ theme, data, scrollX, - nextSlideHandler, - signInHandler, moveToSlide, - isLastSlide, }: Props) => { const styles = getStyleSheet(theme); const {width} = useWindowDimensions(); - let mainButtonText = ( - <> - - - - ); - - let mainButtonAction = nextSlideHandler; - - if (isLastSlide) { - mainButtonText = ( - - ); - mainButtonAction = signInHandler; - } - return ( - + {data.map((item: any, i: number) => { const inputRange = [(i - 1) * width, i * width, (i + 1) * width]; @@ -116,26 +79,6 @@ const Paginator = ({ ); })} - - - - ); }; diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index fae28d79b..03d6ee3b4 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -10,6 +10,7 @@ import {typography} from '@utils/typography'; type Props = { item: any; theme: Theme; + scrollX: any; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -43,7 +44,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, })); -const SlideItem = ({theme, item}: Props) => { +const SlideItem = ({theme, item, scrollX}: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); const SvgImg = item.image; From 9f4207ddcf2e580bd93983a72948f8eed15e7112 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Fri, 28 Oct 2022 23:13:47 +0200 Subject: [PATCH 08/50] add initial animations to ttitle and text --- app/screens/onboarding/index.tsx | 5 ++- app/screens/onboarding/slide.tsx | 70 +++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 6e2b07bd0..6c8a7e27d 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -58,12 +58,13 @@ const Onboarding = ({ }; }, []); - const renderSlide = useCallback(({item: i}: ListRenderItemInfo) => { + const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { return ( ); }, []); diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 03d6ee3b4..6851b5074 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React from 'react'; -import {Text, View, useWindowDimensions} from 'react-native'; +import {Animated, View, useWindowDimensions} from 'react-native'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -11,17 +11,23 @@ type Props = { item: any; theme: Theme; scrollX: any; + index: number; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ title: { fontWeight: '600', - fontSize: 40, marginBottom: 5, height: 100, color: theme.centerChannelColor, textAlign: 'center', }, + fontTitle: { + fontSize: 40, + }, + fontFirstTitle: { + fontSize: 66, + }, description: { fontWeight: '400', fontSize: 16, @@ -34,8 +40,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ image: { justifyContent: 'center', height: 60, - maxHeight: 120, - width: 50, + maxHeight: 180, + width: 60, }, itemContainer: { flex: 1, @@ -44,19 +50,63 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, })); -const SlideItem = ({theme, item, scrollX}: Props) => { +const SlideItem = ({theme, item, scrollX, index}: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); const SvgImg = item.image; + const inputRange = [(index - 1) * width, index * width, (index + 1) * width]; + + const translateImage = scrollX.interpolate({ + inputRange, + outputRange: [width * 0.7, 1, -width * 0.7], + }); + + const translateTitle = scrollX.interpolate({ + inputRange, + outputRange: [width * 0.5, 1, -width * 0.5], + }); + + const translateDescription = scrollX.interpolate({ + inputRange, + outputRange: [width * 0.3, 1, -width * 0.3], + }); + return ( - + + + - {item.title} - {item.description} + + {item.title} + + + {item.description} + ); From 90cac692b4e0a629d1d8be9ea973736aa1a71939 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Sat, 29 Oct 2022 00:12:41 +0200 Subject: [PATCH 09/50] improve the animations for image and text --- app/screens/onboarding/slide.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 6851b5074..78c3b69d6 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -59,17 +59,17 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { const translateImage = scrollX.interpolate({ inputRange, - outputRange: [width * 0.7, 1, -width * 0.7], + outputRange: [width * 2, 0, -width * 2], }); const translateTitle = scrollX.interpolate({ inputRange, - outputRange: [width * 0.5, 1, -width * 0.5], + outputRange: [width * 0.6, 0, -width * 0.6], }); const translateDescription = scrollX.interpolate({ inputRange, - outputRange: [width * 0.3, 1, -width * 0.3], + outputRange: [width * 0.2, 0, -width * 0.2], }); return ( From 2a5237c0246298ab3d737d85ed39764e51f085cf Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 1 Nov 2022 11:51:08 +0100 Subject: [PATCH 10/50] add the outer dot style --- app/screens/onboarding/footer_buttons.tsx | 12 +++++- app/screens/onboarding/index.tsx | 7 +-- app/screens/onboarding/paginator.tsx | 52 +++++++++++++---------- app/screens/onboarding/slide.tsx | 7 +++ 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index fdde99f0a..8cc8dc982 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -1,8 +1,8 @@ // 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 React, {useEffect} from 'react'; +import {useWindowDimensions, View} from 'react-native'; import Button from 'react-native-button'; import CompassIcon from '@app/components/compass_icon'; @@ -15,6 +15,8 @@ type Props = { isLastSlide: boolean; nextSlideHandler: any; signInHandler: any; + scrollX: any; + }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -34,9 +36,15 @@ const FooterButtons = ({ nextSlideHandler, signInHandler, isLastSlide, + scrollX, }: Props) => { + const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); + useEffect(() => { + console.log('is last slide'); + }, [isLastSlide]); + let mainButtonText = ( ); }; -const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ +const getStyleSheet = makeStyleSheetFromTheme(() => ({ onBoardingContainer: { flex: 1, alignItems: 'center', diff --git a/app/screens/onboarding/paginator.tsx b/app/screens/onboarding/paginator.tsx index b111f07d0..6dcb77644 100644 --- a/app/screens/onboarding/paginator.tsx +++ b/app/screens/onboarding/paginator.tsx @@ -4,7 +4,7 @@ import React from 'react'; import {View, Animated, useWindowDimensions, TouchableOpacity} from 'react-native'; -import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {makeStyleSheetFromTheme} from '@utils/theme'; type Props = { data: any; @@ -13,29 +13,28 @@ type Props = { moveToSlide: any; }; +const DOT_SIZE = 16; + const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ dot: { - height: 8, + height: DOT_SIZE / 2, borderRadius: 5, backgroundColor: theme.buttonBg, - marginHorizontal: 8, - width: 8, - - // shadow - shadowOffset: {width: 0, height: 13}, - shadowOpacity: 0.7, - shadowColor: theme.buttonBg, - shadowRadius: 6, - elevation: 3, + marginHorizontal: DOT_SIZE / 2, + width: DOT_SIZE / 2, + }, + outerDot: { + height: DOT_SIZE, + borderRadius: DOT_SIZE / 2, + backgroundColor: theme.buttonBg, + marginHorizontal: 4, + marginTop: -4, + position: 'absolute', + width: DOT_SIZE, }, button: { marginTop: 5, }, - rowIcon: { - color: theme.buttonColor, - fontSize: 12, - marginLeft: 5, - }, })); const Paginator = ({ @@ -55,7 +54,13 @@ const Paginator = ({ const opacity = scrollX.interpolate({ inputRange, - outputRange: [0.3, 1, 0.3], + outputRange: [0.25, 1, 0.25], + extrapolate: 'clamp', + }); + + const opacityOuterDot = scrollX.interpolate({ + inputRange, + outputRange: [0, 0.15, 0], extrapolate: 'clamp', }); @@ -64,16 +69,17 @@ const Paginator = ({ onPress={() => moveToSlide(i)} key={item.id} > + ); diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 78c3b69d6..ceab4ed3f 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -72,6 +72,11 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { outputRange: [width * 0.2, 0, -width * 0.2], }); + const opacity = scrollX.interpolate({ + inputRange, + outputRange: [0.2, 1, 0.2], + }); + return ( { transform: [{ translateX: translateTitle, }], + opacity, }]} > {item.title} @@ -103,6 +109,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { transform: [{ translateX: translateDescription, }], + opacity, }]} > {item.description} From 88ef4fa9eda3b680a7ac75280fc611feb18ced9d Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 1 Nov 2022 12:58:59 +0100 Subject: [PATCH 11/50] start the buttons animation --- app/screens/onboarding/footer_buttons.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index 8cc8dc982..ed8b1c8c0 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -40,9 +40,12 @@ const FooterButtons = ({ }: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); + const inputRange = [-width, 0, width]; useEffect(() => { - console.log('is last slide'); + if (isLastSlide) { + console.log('is last slide'); + } }, [isLastSlide]); let mainButtonText = ( From 12ce5544918a9fbadb9ac3c73cad9478d2aa5f4d Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Wed, 2 Nov 2022 17:57:57 +0100 Subject: [PATCH 12/50] initial migration of everything using reanimated --- app/screens/onboarding/footer_buttons.tsx | 31 +++++--- app/screens/onboarding/index.tsx | 90 +++++++++++------------ app/screens/onboarding/paginator.tsx | 41 ++++++----- app/screens/onboarding/slide.tsx | 84 +++++++++++---------- 4 files changed, 132 insertions(+), 114 deletions(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index ed8b1c8c0..47e41ad58 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -2,8 +2,9 @@ // See LICENSE.txt for license information. import React, {useEffect} from 'react'; -import {useWindowDimensions, View} from 'react-native'; +import {Pressable, useWindowDimensions, View} from 'react-native'; import Button from 'react-native-button'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import CompassIcon from '@app/components/compass_icon'; import FormattedText from '@app/components/formatted_text'; @@ -15,7 +16,6 @@ type Props = { isLastSlide: boolean; nextSlideHandler: any; signInHandler: any; - scrollX: any; }; @@ -31,21 +31,32 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, })); +const AnimatedButton = Animated.createAnimatedComponent(Pressable); + const FooterButtons = ({ theme, nextSlideHandler, signInHandler, isLastSlide, - scrollX, }: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); - const inputRange = [-width, 0, width]; + + const scaledWidth = useSharedValue(80); + + const transform = useAnimatedStyle(() => { + return { + width: scaledWidth.value, + }; + }); useEffect(() => { - if (isLastSlide) { - console.log('is last slide'); - } + console.log('** footer buttons rerender', isLastSlide); + }, []); + + useEffect(() => { + console.log('** footer buttons rerender', isLastSlide); + scaledWidth.value = withTiming(isLastSlide ? ((width * 80) / 100) : 80, {duration: 100}); }, [isLastSlide]); let mainButtonText = ( @@ -77,13 +88,13 @@ const FooterButtons = ({ return ( - + + ); }; diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 22968573a..7f7019101 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -2,8 +2,8 @@ // See LICENSE.txt for license information. import React, {useCallback, useState} from 'react'; -import {View, ListRenderItemInfo, useWindowDimensions, Platform, SafeAreaView} from 'react-native'; -import Animated, {runOnJS, useAnimatedRef, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView} from 'react-native'; +import Animated, {runOnJS, useAnimatedRef, useAnimatedScrollHandler, useSharedValue} from 'react-native-reanimated'; import Background from '@screens/background'; import {makeStyleSheetFromTheme} from '@utils/theme'; @@ -11,7 +11,7 @@ import {makeStyleSheetFromTheme} from '@utils/theme'; import FooterButtons from './footer_buttons'; import Paginator from './paginator'; import SlideItem from './slide'; -import slidesData from './slides_data'; +import useSlidesData, {OnboardingItem} from './slides_data'; import type {LaunchProps} from '@typings/launch'; @@ -27,6 +27,7 @@ const Onboarding = ({ const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); const [isLastSlide, setIsLastSlide] = useState(false); + const slidesData = useSlidesData().slidesData; const lastSlideIndex = slidesData.length - 1; const slidesRef = useAnimatedRef(); const currentIndex = useSharedValue(0); @@ -51,7 +52,7 @@ const Onboarding = ({ setIsLastSlide(!isLastSlide); }; - const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { + const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { return ( {slidesData.map((item, index) => { - return renderSlide({item, index} as ListRenderItemInfo); + return renderSlide({item, index} as ListRenderItemInfo); })} ; - moveToSlide: any; + moveToSlide: (slideIndexToMove: number) => void; }; const DOT_SIZE = 16; @@ -44,9 +46,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ width: DOT_SIZE, opacity: 0.15, }, - button: { - marginTop: 5, - }, })); const Paginator = ({ @@ -56,34 +55,31 @@ const Paginator = ({ moveToSlide, }: Props) => { return ( - - - {data.map((item: any, i: number) => { - return ( - - ); - })} - + + {data.map((item: OnboardingItem, index: number) => { + return ( + + ); + })} ); }; type DotProps = { - item: any; index: number; scrollX: Animated.SharedValue; theme: Theme; - moveToSlide: any; + moveToSlide: (slideIndexToMove: number) => void; }; -const Dot = ({item, index, scrollX, theme, moveToSlide}: DotProps) => { +// this has to be extracted as a component since the useAnimatedStyle hook cant be used inside a loop +const Dot = ({index, scrollX, theme, moveToSlide}: DotProps) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); const inputRange = [(index - 1) * width, index * width, (index + 1) * width]; @@ -111,19 +107,15 @@ const Dot = ({item, index, scrollX, theme, moveToSlide}: DotProps) => { return ( moveToSlide(index)} - key={item.id} > ); diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 8ace39a5b..49f330bea 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -3,13 +3,15 @@ import React from 'react'; import {View, useWindowDimensions} from 'react-native'; -import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated'; +import Animated, {Extrapolate, interpolate, useAnimatedStyle} from 'react-native-reanimated'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; +import {OnboardingItem} from './slides_data'; + type Props = { - item: any; + item: OnboardingItem; theme: Theme; scrollX: Animated.SharedValue; index: number; @@ -63,6 +65,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { scrollX.value, inputRange, [width * 2, 0, -width * 2], + Extrapolate.CLAMP, ); return { @@ -77,6 +80,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { scrollX.value, inputRange, [width * 0.6, 0, -width * 0.6], + Extrapolate.CLAMP, ); return { @@ -91,6 +95,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { scrollX.value, inputRange, [width * 0.2, 0, -width * 0.2], + Extrapolate.CLAMP, ); return { @@ -105,6 +110,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { scrollX.value, inputRange, [0.2, 1, 0.2], + Extrapolate.CLAMP, ); return {opacity: opacityInterpolate}; diff --git a/app/screens/onboarding/slides_data.ts b/app/screens/onboarding/slides_data.ts index 331dbc858..52f55916b 100644 --- a/app/screens/onboarding/slides_data.ts +++ b/app/screens/onboarding/slides_data.ts @@ -1,30 +1,45 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -// pablo - translate each text. turn svgs into react components? -export default [ - { - id: '1', - title: 'Welcome', - description: 'Mattermost is an open source platform for developer collaboration. Secure, flexible, and integrated with your tools.', - image: require('./illustrations/chat.svg').default, - }, - { - id: '2', - title: 'Collaborate in real-time', - description: 'Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.', - image: require('./illustrations/team_communication.svg').default, - }, - { - id: '3', - title: 'Start secure audio calls instantly', - description: 'When typing isn’t fast enough, switch from channel-based chat to secure audio calls with a single tap.', - image: require('./illustrations/calls.svg').default, - }, - { - id: '4', - title: 'Integrate with tools you love', - description: 'Go beyond chat with tightly-integratedproduct solutions matched to common development processes.', - image: require('./illustrations/integrations.svg').default, - }, -]; +import {useIntl} from 'react-intl'; + +export type OnboardingItem = { + id: string; + title: string; + description: string; + image: string; +} +const useSalidesData = () => { + const intl = useIntl(); + + const slidesData: OnboardingItem[] = [ + { + id: '1', + title: intl.formatMessage({id: 'onboarding_screen.welcome', defaultMessage: 'Welcome'}), + description: intl.formatMessage({id: 'onboaring.welcome_description', defaultMessage: 'Mattermost is an open source platform for developer collaboration. Secure, flexible, and integrated with your tools.'}), + image: require('./illustrations/chat.svg').default, + }, + { + id: '2', + title: intl.formatMessage({id: 'onboarding.realtime_collaboration', defaultMessage: 'Collaborate in real-time'}), + description: intl.formatMessage({id: 'onboarding.realtime_collaboration_description', defaultMessage: 'Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.'}), + image: require('./illustrations/team_communication.svg').default, + }, + { + id: '3', + title: intl.formatMessage({id: 'onboarding.calls', defaultMessage: 'Start secure audio calls instantly'}), + description: intl.formatMessage({id: 'onboarding.calls_description', defaultMessage: 'When typing isn’t fast enough, switch from channel-based chat to secure audio calls with a single tap.'}), + image: require('./illustrations/calls.svg').default, + }, + { + id: '4', + title: intl.formatMessage({id: 'onboarding.integrations', defaultMessage: 'Integrate with tools you love'}), + description: intl.formatMessage({id: 'onboarding.integrations_description', defaultMessage: 'Go beyond chat with tightly-integratedproduct solutions matched to common development processes.'}), + image: require('./illustrations/integrations.svg').default, + }, + ]; + + return {slidesData}; +}; + +export default useSalidesData; From 63cc3e7413c95bf2da7fe4e866075dbb62437129 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Fri, 4 Nov 2022 14:21:50 +0100 Subject: [PATCH 17/50] add initial animation on load for first slide --- app/screens/onboarding/footer_buttons.tsx | 6 +- app/screens/onboarding/index.tsx | 2 +- app/screens/onboarding/slide.tsx | 82 +++++++++++++++++++++-- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index a9160bf9b..726df2637 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -3,8 +3,7 @@ import React from 'react'; import {Pressable, useWindowDimensions, View} from 'react-native'; -import Button from 'react-native-button'; -import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated'; +import Animated, {Extrapolate, interpolate, useAnimatedStyle} from 'react-native-reanimated'; import CompassIcon from '@app/components/compass_icon'; import FormattedText from '@app/components/formatted_text'; @@ -58,6 +57,7 @@ const FooterButtons = ({ scrollX.value, inputRange, [BUTTON_SIZE, isLastSlide ? width * 0.8 : BUTTON_SIZE, width * 0.8], + Extrapolate.CLAMP, ); return {width: needToAnimate ? interpolatedWidth : BUTTON_SIZE}; @@ -68,6 +68,7 @@ const FooterButtons = ({ scrollX.value, inputRange, [isPenultimateSlide ? 1 : 0, 1, 0], + Extrapolate.CLAMP, ); return {opacity: needToAnimate ? interpolatedScale : 1}; @@ -78,6 +79,7 @@ const FooterButtons = ({ scrollX.value, inputRange, [1, (isLastSlide ? 0 : 1), 0], + Extrapolate.CLAMP, ); return {opacity: needToAnimate ? interpolatedScale : 1}; diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 7f7019101..8591d65f6 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -93,7 +93,7 @@ const Onboarding = ({ showsHorizontalScrollIndicator={false} pagingEnabled={true} bounces={false} - onMomentumScrollEnd={handleScroll} + onScroll={handleScroll} ref={slidesRef} > {slidesData.map((item, index) => { diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 49f330bea..09bbc206e 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -1,9 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; +import React, { useEffect, useState } from 'react'; import {View, useWindowDimensions} from 'react-native'; -import Animated, {Extrapolate, interpolate, useAnimatedStyle} from 'react-native-reanimated'; +import Animated, {Extrapolate, interpolate, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -31,6 +31,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ fontFirstTitle: { fontSize: 66, }, + firstSlideInitialPosition: { + left: 200, + opacity: 0, + }, description: { fontWeight: '400', fontSize: 16, @@ -56,8 +60,59 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const SlideItem = ({theme, item, scrollX, index}: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); + const FIRST_SLIDE = 0; const SvgImg = item.image; + /** + * Code used to animate the first image load + */ + const FIRST_LOAD_ELEMENTS_POSITION = 400; + const [firstLoad, setFirstLoad] = useState(true); + + const initialImagePosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION); + const initialTitlePosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION); + const initialDescriptionPosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION); + + const initialElementsOpacity = useSharedValue(0); + + useEffect(() => { + if (index === FIRST_SLIDE) { + initialImagePosition.value = withTiming(0, {duration: 1000}); + initialTitlePosition.value = withTiming(0, {duration: 1250}); + initialDescriptionPosition.value = withTiming(0, {duration: 1500}); + + initialElementsOpacity.value = withTiming(1, {duration: 1500}); + setFirstLoad(false); + } + }, []); + + const translateFirstImageOnLoad = useAnimatedStyle(() => { + return { + transform: [{ + translateX: initialImagePosition.value, + }], + opacity: initialElementsOpacity.value, + }; + }); + + const translateFirstTitleOnLoad = useAnimatedStyle(() => { + return { + transform: [{ + translateX: initialTitlePosition.value, + }], + opacity: initialElementsOpacity.value, + }; + }); + + const translateFirstDescriptionOnLoad = useAnimatedStyle(() => { + return { + transform: [{ + translateX: initialDescriptionPosition.value, + }], + opacity: initialElementsOpacity.value, + }; + });// end of code for animating first image load + const inputRange = [(index - 1) * width, index * width, (index + 1) * width]; const translateImage = useAnimatedStyle(() => { @@ -119,7 +174,11 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { return ( { {item.title} {item.description} From 954d28e71e350b00bbf579b1314b0273ab9d2eef Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Fri, 4 Nov 2022 15:43:02 +0100 Subject: [PATCH 18/50] fix image opacity --- app/screens/onboarding/slide.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 09bbc206e..35ecfae86 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React, { useEffect, useState } from 'react'; -import {View, useWindowDimensions} from 'react-native'; +import {View, useWindowDimensions, Text} from 'react-native'; import Animated, {Extrapolate, interpolate, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -176,6 +176,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { Date: Fri, 4 Nov 2022 17:58:28 +0100 Subject: [PATCH 19/50] fix animations for texts and buttons in footer --- app/screens/onboarding/footer_buttons.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index 726df2637..05141003d 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -47,42 +47,47 @@ const FooterButtons = ({ const styles = getStyleSheet(theme); const BUTTON_SIZE = 80; - // keep in mind penultimate and ultimate slides to run buttons animations + // keep in mind penultimate and ultimate slides to run buttons text/opacity/size animations + const penultimateSlide = lastSlideIndex - 1; const isPenultimateSlide = currentIndex === (lastSlideIndex - 1); const needToAnimate = isLastSlide || isPenultimateSlide; - const inputRange = [(currentIndex - 1) * width, currentIndex * width, (currentIndex + 1) * width]; + const inputRange = [penultimateSlide * width, lastSlideIndex * width]; + + // the next button must resize in the last slide to be the 80% wide of the screen with animation const resizeStyle = useAnimatedStyle(() => { const interpolatedWidth = interpolate( scrollX.value, inputRange, - [BUTTON_SIZE, isLastSlide ? width * 0.8 : BUTTON_SIZE, width * 0.8], + [BUTTON_SIZE, width * 0.8], Extrapolate.CLAMP, ); - return {width: needToAnimate ? interpolatedWidth : BUTTON_SIZE}; + return {width: interpolatedWidth}; }); + // use for the opacity of the button text in the penultimate and last slide const opacityTextStyle = useAnimatedStyle(() => { const interpolatedScale = interpolate( scrollX.value, inputRange, - [isPenultimateSlide ? 1 : 0, 1, 0], + isLastSlide ? [0, 1] : [1, 0], // from last to penultimate it must fade out (from 1 to 0), once it starts getting the penultimate range it must fade in again (from 0 to 1) Extrapolate.CLAMP, ); return {opacity: needToAnimate ? interpolatedScale : 1}; }); + // the sign in button should fade out until dissappear in the last slide const opacitySignInButton = useAnimatedStyle(() => { const interpolatedScale = interpolate( scrollX.value, inputRange, - [1, (isLastSlide ? 0 : 1), 0], + [1, 0], Extrapolate.CLAMP, ); - return {opacity: needToAnimate ? interpolatedScale : 1}; + return {opacity: interpolatedScale}; }); let mainButtonText = ( From c9ae29f0107f6c3640bd5a516407f85d5d654edc Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Mon, 7 Nov 2022 22:33:53 +0100 Subject: [PATCH 20/50] use svg correctly in react native style --- .../onboarding/illustrations/calls.svg | 64 --- .../onboarding/illustrations/calls_svg.tsx | 247 ++++++++++ app/screens/onboarding/illustrations/chat.svg | 210 -------- .../onboarding/illustrations/chat_svg.tsx | 338 +++++++++++++ .../onboarding/illustrations/integrations.svg | 73 --- .../illustrations/integrations_svg.tsx | 458 ++++++++++++++++++ .../illustrations/team_communication.svg | 72 --- .../illustrations/team_communication_svg.tsx | 282 +++++++++++ app/screens/onboarding/slide.tsx | 12 +- .../{slides_data.ts => slides_data.tsx} | 31 +- 10 files changed, 1354 insertions(+), 433 deletions(-) delete mode 100644 app/screens/onboarding/illustrations/calls.svg create mode 100644 app/screens/onboarding/illustrations/calls_svg.tsx delete mode 100644 app/screens/onboarding/illustrations/chat.svg create mode 100644 app/screens/onboarding/illustrations/chat_svg.tsx delete mode 100644 app/screens/onboarding/illustrations/integrations.svg create mode 100644 app/screens/onboarding/illustrations/integrations_svg.tsx delete mode 100644 app/screens/onboarding/illustrations/team_communication.svg create mode 100644 app/screens/onboarding/illustrations/team_communication_svg.tsx rename app/screens/onboarding/{slides_data.ts => slides_data.tsx} (68%) diff --git a/app/screens/onboarding/illustrations/calls.svg b/app/screens/onboarding/illustrations/calls.svg deleted file mode 100644 index 7015eeb94..000000000 --- a/app/screens/onboarding/illustrations/calls.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/screens/onboarding/illustrations/calls_svg.tsx b/app/screens/onboarding/illustrations/calls_svg.tsx new file mode 100644 index 000000000..fa1a6f6d1 --- /dev/null +++ b/app/screens/onboarding/illustrations/calls_svg.tsx @@ -0,0 +1,247 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as React from 'react'; +import {StyleProp, ViewStyle} from 'react-native'; +import Svg, {Ellipse, Path, Mask, G} from 'react-native-svg'; + +type SvgProps = { + styles: StyleProp; +}; + +const CallsSvg = ({styles}: SvgProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default CallsSvg; diff --git a/app/screens/onboarding/illustrations/chat.svg b/app/screens/onboarding/illustrations/chat.svg deleted file mode 100644 index 8441ba906..000000000 --- a/app/screens/onboarding/illustrations/chat.svg +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/screens/onboarding/illustrations/chat_svg.tsx b/app/screens/onboarding/illustrations/chat_svg.tsx new file mode 100644 index 000000000..155174cc1 --- /dev/null +++ b/app/screens/onboarding/illustrations/chat_svg.tsx @@ -0,0 +1,338 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as React from 'react'; +import {StyleProp, ViewStyle} from 'react-native'; +import Svg, { + G, + Path, + Mask, + Ellipse, + Defs, + Pattern, + Use, + ClipPath, + Image, +} from 'react-native-svg'; + +type SvgProps = { + styles: StyleProp; +}; + +const ChatSvg = ({styles}: SvgProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ChatSvg; diff --git a/app/screens/onboarding/illustrations/integrations.svg b/app/screens/onboarding/illustrations/integrations.svg deleted file mode 100644 index 7153d8fec..000000000 --- a/app/screens/onboarding/illustrations/integrations.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/screens/onboarding/illustrations/integrations_svg.tsx b/app/screens/onboarding/illustrations/integrations_svg.tsx new file mode 100644 index 000000000..705567009 --- /dev/null +++ b/app/screens/onboarding/illustrations/integrations_svg.tsx @@ -0,0 +1,458 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as React from 'react'; +import {StyleProp, ViewStyle} from 'react-native'; +import Svg, { + Rect, + Path, + Circle, + Ellipse, + Mask, + G, + Defs, + Pattern, + Use, + Image, +} from 'react-native-svg'; + +type SvgProps = { + styles: StyleProp; +}; + +const IntegrationsSvg = ({styles}: SvgProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default IntegrationsSvg; diff --git a/app/screens/onboarding/illustrations/team_communication.svg b/app/screens/onboarding/illustrations/team_communication.svg deleted file mode 100644 index b93259269..000000000 --- a/app/screens/onboarding/illustrations/team_communication.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/screens/onboarding/illustrations/team_communication_svg.tsx b/app/screens/onboarding/illustrations/team_communication_svg.tsx new file mode 100644 index 000000000..d707ccea0 --- /dev/null +++ b/app/screens/onboarding/illustrations/team_communication_svg.tsx @@ -0,0 +1,282 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as React from 'react'; +import {StyleProp, ViewStyle} from 'react-native'; +import Svg, {Ellipse, Path, Mask, G, Defs, ClipPath} from 'react-native-svg'; + +type SvgProps = { + styles: StyleProp; +}; + +const TeamCommunicationSvg = ({styles}: SvgProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default TeamCommunicationSvg; diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 35ecfae86..2811fe5ac 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -1,8 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, { useEffect, useState } from 'react'; -import {View, useWindowDimensions, Text} from 'react-native'; +import React, {useEffect, useState} from 'react'; +import {View, useWindowDimensions} from 'react-native'; import Animated, {Extrapolate, interpolate, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -61,7 +61,6 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); const FIRST_SLIDE = 0; - const SvgImg = item.image; /** * Code used to animate the first image load @@ -181,12 +180,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { (index === FIRST_SLIDE && firstLoad ? styles.firstSlideInitialPosition : undefined), ]} > - + {item.image} { const intl = useIntl(); + const callsSvg = (); + const chatSvg = (); + const teamCommunicationSvg = (); + const integrationsSvg = (); const slidesData: OnboardingItem[] = [ { id: '1', title: intl.formatMessage({id: 'onboarding_screen.welcome', defaultMessage: 'Welcome'}), description: intl.formatMessage({id: 'onboaring.welcome_description', defaultMessage: 'Mattermost is an open source platform for developer collaboration. Secure, flexible, and integrated with your tools.'}), - image: require('./illustrations/chat.svg').default, + image: chatSvg, }, { id: '2', title: intl.formatMessage({id: 'onboarding.realtime_collaboration', defaultMessage: 'Collaborate in real-time'}), description: intl.formatMessage({id: 'onboarding.realtime_collaboration_description', defaultMessage: 'Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.'}), - image: require('./illustrations/team_communication.svg').default, + image: teamCommunicationSvg, }, { id: '3', title: intl.formatMessage({id: 'onboarding.calls', defaultMessage: 'Start secure audio calls instantly'}), description: intl.formatMessage({id: 'onboarding.calls_description', defaultMessage: 'When typing isn’t fast enough, switch from channel-based chat to secure audio calls with a single tap.'}), - image: require('./illustrations/calls.svg').default, + image: callsSvg, }, { id: '4', title: intl.formatMessage({id: 'onboarding.integrations', defaultMessage: 'Integrate with tools you love'}), description: intl.formatMessage({id: 'onboarding.integrations_description', defaultMessage: 'Go beyond chat with tightly-integratedproduct solutions matched to common development processes.'}), - image: require('./illustrations/integrations.svg').default, + image: integrationsSvg, }, ]; From d9239f3996f1fb389138754b64ed43cf4647ac2c Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 8 Nov 2022 16:39:50 +0100 Subject: [PATCH 21/50] remove the need for rerender the footer buttons when last slide --- app/screens/onboarding/footer_buttons.tsx | 59 ++++++++++++----------- app/screens/onboarding/index.tsx | 56 +++++++++------------ 2 files changed, 54 insertions(+), 61 deletions(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index 05141003d..a587bba2a 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -12,11 +12,9 @@ import {makeStyleSheetFromTheme} from '@utils/theme'; type Props = { theme: Theme; - isLastSlide: boolean; lastSlideIndex: number; nextSlideHandler: () => void; signInHandler: () => void; - currentIndex: number; scrollX: Animated.SharedValue; }; @@ -38,9 +36,7 @@ const FooterButtons = ({ theme, nextSlideHandler, signInHandler, - isLastSlide, lastSlideIndex, - currentIndex, scrollX, }: Props) => { const {width} = useWindowDimensions(); @@ -49,10 +45,8 @@ const FooterButtons = ({ // keep in mind penultimate and ultimate slides to run buttons text/opacity/size animations const penultimateSlide = lastSlideIndex - 1; - const isPenultimateSlide = currentIndex === (lastSlideIndex - 1); - const needToAnimate = isLastSlide || isPenultimateSlide; - const inputRange = [penultimateSlide * width, lastSlideIndex * width]; + const inputRange = [(penultimateSlide * width), lastSlideIndex * width]; // the next button must resize in the last slide to be the 80% wide of the screen with animation const resizeStyle = useAnimatedStyle(() => { @@ -67,15 +61,26 @@ const FooterButtons = ({ }); // use for the opacity of the button text in the penultimate and last slide - const opacityTextStyle = useAnimatedStyle(() => { + const opacityNextTextStyle = useAnimatedStyle(() => { const interpolatedScale = interpolate( scrollX.value, - inputRange, - isLastSlide ? [0, 1] : [1, 0], // from last to penultimate it must fade out (from 1 to 0), once it starts getting the penultimate range it must fade in again (from 0 to 1) + [penultimateSlide * width, ((penultimateSlide * width) + (width / 2))], + [1, 0], // from last to penultimate it must fade out (from 1 to 0), once it starts getting the penultimate range it must fade in again (from 0 to 1) Extrapolate.CLAMP, ); - return {opacity: needToAnimate ? interpolatedScale : 1}; + return {opacity: interpolatedScale}; + }); + + const opacitySignInTextStyle = useAnimatedStyle(() => { + const interpolatedScale = interpolate( + scrollX.value, + [lastSlideIndex * width, ((penultimateSlide * width) + (width / 2))], + [1, 0], // from last to penultimate it must fade out (from 1 to 0), once it starts getting the penultimate range it must fade in again (from 0 to 1) + Extrapolate.CLAMP, + ); + + return {opacity: interpolatedScale}; }); // the sign in button should fade out until dissappear in the last slide @@ -90,8 +95,8 @@ const FooterButtons = ({ return {opacity: interpolatedScale}; }); - let mainButtonText = ( - + const nextButtonText = ( + ); - let mainButtonAction = nextSlideHandler; - - if (isLastSlide) { - mainButtonText = ( - - - - ); - mainButtonAction = signInHandler; - } + const signInButtonText = ( + + + + ); return ( mainButtonAction()} + onPress={() => nextSlideHandler()} style={[styles.button, buttonBackgroundStyle(theme, 'm', 'primary', 'default'), resizeStyle]} > - {mainButtonText} + {nextButtonText} + {signInButtonText} { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); - const [isLastSlide, setIsLastSlide] = useState(false); const slidesData = useSlidesData().slidesData; const lastSlideIndex = slidesData.length - 1; const slidesRef = useAnimatedRef(); - const currentIndex = useSharedValue(0); - const scrollX = useSharedValue(0); - const nextSlide = () => { + // const currentIndex = useSharedValue(0); + const scrollX = useSharedValue(0); + const currentIndex = useDerivedValue(() => Math.round(scrollX.value / width)); + + const moveToSlide = useCallback((slideIndexToMove: number) => { + slidesRef.current?.scrollTo({x: (slideIndexToMove * width), animated: true}); + }, [slidesRef.current]); + + const nextSlide = useCallback(() => { const nextSlideIndex = currentIndex.value + 1; if (slidesRef.current && currentIndex.value < lastSlideIndex) { moveToSlide(nextSlideIndex); + } else if (slidesRef.current && currentIndex.value === lastSlideIndex) { + signInHandler(); } - }; + }, [currentIndex.value, slidesRef.current, moveToSlide]); - const moveToSlide = (slideIndexToMove: number) => { - currentIndex.value = slideIndexToMove; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - slidesRef?.current?.scrollTo({x: (slideIndexToMove * width), animated: true}); - }; - - const signInHandler = () => { - // temporal validation - setIsLastSlide(!isLastSlide); - }; + const signInHandler = useCallback(() => { + goToScreen(Screens.SERVER, '', {theme}); + }, []); const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { return ( @@ -64,17 +65,10 @@ const Onboarding = ({ ); }, []); - const toogleIsLastSlideValue = (isLast: boolean) => { - setIsLastSlide(isLast); - }; - - const handleScroll = useAnimatedScrollHandler(({contentOffset: {x}}) => { - const calculatedIndex = Math.round(x / width); - scrollX.value = x; - if (calculatedIndex !== currentIndex.value) { - currentIndex.value = calculatedIndex; - runOnJS(toogleIsLastSlideValue)(calculatedIndex === lastSlideIndex); - } + const scrollHandler = useAnimatedScrollHandler({ + onScroll: (event) => { + scrollX.value = event.contentOffset.x; + }, }); return ( @@ -93,7 +87,7 @@ const Onboarding = ({ showsHorizontalScrollIndicator={false} pagingEnabled={true} bounces={false} - onScroll={handleScroll} + onScroll={scrollHandler} ref={slidesRef} > {slidesData.map((item, index) => { @@ -108,10 +102,8 @@ const Onboarding = ({ /> From d11662508e92f54e33bdc1f63fa363195861a07e Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Wed, 9 Nov 2022 13:03:31 +0100 Subject: [PATCH 22/50] make scroll to easing and depending on how many items to move, fix SVG --- app/init/launch.ts | 7 +- .../illustrations/integrations_svg.tsx | 296 ++++++++++++------ app/screens/onboarding/index.tsx | 34 +- 3 files changed, 223 insertions(+), 114 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index 6a372a61a..54a0ef675 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -127,7 +127,7 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } - return launchToOnboarding(props); + return launchToOnboarding(props, resetNavigation); }; const launchToHome = async (props: LaunchProps) => { @@ -181,7 +181,10 @@ const launchToServer = (props: LaunchProps, resetNavigation: Boolean) => { return goToScreen(Screens.SERVER, title, {...props}); }; -const launchToOnboarding = (props: LaunchProps) => { +const launchToOnboarding = (props: LaunchProps, resetNavigation = true) => { + if (resetNavigation) { + launchToServer(props, resetNavigation); + } return resetToOnboarding(props); }; diff --git a/app/screens/onboarding/illustrations/integrations_svg.tsx b/app/screens/onboarding/illustrations/integrations_svg.tsx index 705567009..118761a02 100644 --- a/app/screens/onboarding/illustrations/integrations_svg.tsx +++ b/app/screens/onboarding/illustrations/integrations_svg.tsx @@ -11,9 +11,8 @@ import Svg, { Mask, G, Defs, - Pattern, - Use, - Image, + LinearGradient, + Stop, } from 'react-native-svg'; type SvgProps = { @@ -283,8 +282,12 @@ const IntegrationsSvg = ({styles}: SvgProps) => { strokeOpacity={0.16} /> + { strokeOpacity={0.16} /> + + + + + + { strokeOpacity={0.16} /> + + { strokeOpacity={0.16} /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + { strokeOpacity={0.16} /> + - - - - + + - - - - - - - - - - - - - - - - + ); diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index b8a535395..089cd3197 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -1,9 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback} from 'react'; -import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView} from 'react-native'; -import Animated, {useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; +import React, {useCallback, useEffect, useRef} from 'react'; +import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, Animated as AnimatedRN} from 'react-native'; +import Animated, {Easing, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; import {Screens} from '@app/constants'; import Background from '@screens/background'; @@ -30,15 +30,31 @@ const Onboarding = ({ const styles = getStyleSheet(theme); const slidesData = useSlidesData().slidesData; const lastSlideIndex = slidesData.length - 1; - const slidesRef = useAnimatedRef(); + const slidesRef = useAnimatedRef(); - // const currentIndex = useSharedValue(0); const scrollX = useSharedValue(0); + const scrollAnimation = useRef(new AnimatedRN.Value(0)); + const currentIndex = useDerivedValue(() => Math.round(scrollX.value / width)); - const moveToSlide = useCallback((slideIndexToMove: number) => { - slidesRef.current?.scrollTo({x: (slideIndexToMove * width), animated: true}); - }, [slidesRef.current]); + useEffect(() => { + scrollAnimation.current?.addListener((animation) => { + slidesRef.current?.scrollTo({ + x: animation.value, + animated: false, + }); + }); + return () => scrollAnimation.current.removeAllListeners(); + }, []); + + const moveToSlide = (slideIndexToMove: number) => { + AnimatedRN.timing(scrollAnimation.current, { + toValue: (slideIndexToMove * width), + duration: Math.abs(currentIndex.value - slideIndexToMove) * 200, + useNativeDriver: true, + easing: Easing.linear, + }).start(); + }; const nextSlide = useCallback(() => { const nextSlideIndex = currentIndex.value + 1; @@ -88,7 +104,7 @@ const Onboarding = ({ pagingEnabled={true} bounces={false} onScroll={scrollHandler} - ref={slidesRef} + ref={slidesRef as any} > {slidesData.map((item, index) => { return renderSlide({item, index} as ListRenderItemInfo); From 46b66a5baf748b3ffaf4d016797d3ff0dbf32e14 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Wed, 9 Nov 2022 22:47:26 +0100 Subject: [PATCH 23/50] initial logic for showing/not showing the onboarding --- app/init/launch.ts | 28 +++++++++++++- app/screens/navigation.ts | 3 +- app/screens/onboarding/index.tsx | 64 ++++++++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index 54a0ef675..52326aa13 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -19,6 +19,7 @@ import {convertToNotificationData} from '@utils/notification'; import {parseDeepLink} from '@utils/url'; import type {DeepLinkChannel, DeepLinkDM, DeepLinkGM, DeepLinkPermalink, DeepLinkWithData, LaunchProps} from '@typings/launch'; +import { fetchConfigAndLicense } from '@actions/remote/systems'; const initialNotificationTypes = [PushNotification.NOTIFICATION_TYPE.MESSAGE, PushNotification.NOTIFICATION_TYPE.SESSION]; @@ -98,6 +99,14 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { hasCurrentUser = Boolean(currentUserId); } + // if (!onboardingAlreadyShown) { + // here, check if there is not an active session and redirect to onboarding with a flag, so the sign in button will + // redirect to the sign in + // return launchToOnboarding(props, goToLoginPage); + // } + + return launchToOnboarding(props, resetNavigation, false, false, true, serverUrl); + let launchType = props.launchType; if (!hasCurrentUser) { // migrating from v1 @@ -127,6 +136,10 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } + // if (onboardingAlreadyShown) { + // // launchToServer(props, resetNavigation); + // } + return launchToOnboarding(props, resetNavigation); }; @@ -181,11 +194,22 @@ const launchToServer = (props: LaunchProps, resetNavigation: Boolean) => { return goToScreen(Screens.SERVER, title, {...props}); }; -const launchToOnboarding = (props: LaunchProps, resetNavigation = true) => { +const launchToOnboarding = ( + props: LaunchProps, + resetNavigation = true, + notActiveSession = true, + whiteLabeledApp = false, + goToLogIn = true, + serverUrl = '', +) => { + // here, if there is an active session, redirect to home + // if there is a whitelabeled app, redirect to either SERVER or LOGIN but don't show the onboarding if (resetNavigation) { launchToServer(props, resetNavigation); } - return resetToOnboarding(props); + + // if there is not an active session, pass the prop and redirect to the LOGIN page (keep in mind all the redirection login to check for SSO stuff) + return resetToOnboarding(props, goToLogIn); }; export const relaunchApp = (props: LaunchProps, resetNavigation = false) => { diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index b98575420..2bb7cd916 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -281,7 +281,7 @@ export function resetToSelectServer(passProps: LaunchProps) { }); } -export function resetToOnboarding(passProps: LaunchProps) { +export function resetToOnboarding(passProps: LaunchProps, goToLogIn: boolean) { const theme = getDefaultThemeByAppearance(); const isDark = tinyColor(theme.sidebarBg).isDark(); StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); @@ -295,6 +295,7 @@ export function resetToOnboarding(passProps: LaunchProps) { passProps: { ...passProps, theme, + goToLogIn, }, options: { layout: { diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 089cd3197..a1611ede4 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -7,7 +7,7 @@ import Animated, {Easing, useAnimatedRef, useAnimatedScrollHandler, useDerivedVa import {Screens} from '@app/constants'; import Background from '@screens/background'; -import {goToScreen} from '@screens/navigation'; +import {goToScreen, loginAnimationOptions} from '@screens/navigation'; import {makeStyleSheetFromTheme} from '@utils/theme'; import FooterButtons from './footer_buttons'; @@ -16,15 +16,22 @@ import SlideItem from './slide'; import useSlidesData, {OnboardingItem} from './slides_data'; import type {LaunchProps} from '@typings/launch'; +import { loginOptions } from '@app/utils/server'; +import { fetchConfigAndLicense } from '@actions/remote/systems'; +import { queryServerByIdentifier } from '@app/queries/app/servers'; interface OnboardingProps extends LaunchProps { theme: Theme; + goToLogIn: boolean; + serverUrl: string; } const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView); const Onboarding = ({ theme, + goToLogIn, + serverUrl, }: OnboardingProps) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); @@ -65,9 +72,60 @@ const Onboarding = ({ } }, [currentIndex.value, slidesRef.current, moveToSlide]); + const initLogin = async () => { + const data = await fetchConfigAndLicense(serverUrl, true); + if (data.error) { + console.log('Error getting the config and license information'); + return; + } + + displayLogin(data.config!, data.license!); + }; + + const displayLogin = (config: ClientConfig, license: ClientLicense) => { + const {enabledSSOs, hasLoginForm, numberSSOs, ssoOptions} = loginOptions(config, license); + const passProps = { + config, + extra, + hasLoginForm, + launchError, + launchType, + license, + serverDisplayName: displayName, + serverUrl, + ssoOptions, + theme, + }; + + const redirectSSO = !hasLoginForm && numberSSOs === 1; + const screen = redirectSSO ? Screens.SSO : Screens.LOGIN; + if (redirectSSO) { + // @ts-expect-error ssoType not in definition + passProps.ssoType = enabledSSOs[0]; + } + + goToScreen(screen, '', passProps, loginAnimationOptions()); + }; + const signInHandler = useCallback(() => { - goToScreen(Screens.SERVER, '', {theme}); - }, []); + if (goToLogIn) { + initLogin(); + } + const topBar = { + visible: true, + drawBehind: true, + noBorder: true, + elevation: 0, + background: { + color: 'transparent', + }, + backButton: { + color: theme.centerChannelColor, + title: '', + }, + }; + goToScreen(Screens.SERVER, '', {theme}, {topBar}); + }, [goToLogIn]); const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { return ( From 5b09f03ac1a52befaf0da1b5454b17ad73e6bb19 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Fri, 11 Nov 2022 22:30:36 +0100 Subject: [PATCH 24/50] make the redirect to login work --- app/init/launch.ts | 6 +++--- app/screens/navigation.ts | 3 ++- app/screens/onboarding/index.tsx | 9 +++------ 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index 52326aa13..017574007 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -104,7 +104,7 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { // redirect to the sign in // return launchToOnboarding(props, goToLoginPage); // } - + console.log('\n\n first launch to onboarding \n\n', serverUrl); return launchToOnboarding(props, resetNavigation, false, false, true, serverUrl); let launchType = props.launchType; @@ -139,7 +139,7 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { // if (onboardingAlreadyShown) { // // launchToServer(props, resetNavigation); // } - + console.log('\n\n second launch to onboarding \n\n', serverUrl); return launchToOnboarding(props, resetNavigation); }; @@ -209,7 +209,7 @@ const launchToOnboarding = ( } // if there is not an active session, pass the prop and redirect to the LOGIN page (keep in mind all the redirection login to check for SSO stuff) - return resetToOnboarding(props, goToLogIn); + return resetToOnboarding(props, true, serverUrl); }; export const relaunchApp = (props: LaunchProps, resetNavigation = false) => { diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 2bb7cd916..680406507 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -281,7 +281,7 @@ export function resetToSelectServer(passProps: LaunchProps) { }); } -export function resetToOnboarding(passProps: LaunchProps, goToLogIn: boolean) { +export function resetToOnboarding(passProps: LaunchProps, goToLogIn: boolean, serverUrl: string) { const theme = getDefaultThemeByAppearance(); const isDark = tinyColor(theme.sidebarBg).isDark(); StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); @@ -296,6 +296,7 @@ export function resetToOnboarding(passProps: LaunchProps, goToLogIn: boolean) { ...passProps, theme, goToLogIn, + serverUrl, }, options: { layout: { diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index a1611ede4..12a4b9e9f 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -5,7 +5,9 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, Animated as AnimatedRN} from 'react-native'; import Animated, {Easing, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; +import {fetchConfigAndLicense} from '@actions/remote/systems'; import {Screens} from '@app/constants'; +import {loginOptions} from '@app/utils/server'; import Background from '@screens/background'; import {goToScreen, loginAnimationOptions} from '@screens/navigation'; import {makeStyleSheetFromTheme} from '@utils/theme'; @@ -16,9 +18,6 @@ import SlideItem from './slide'; import useSlidesData, {OnboardingItem} from './slides_data'; import type {LaunchProps} from '@typings/launch'; -import { loginOptions } from '@app/utils/server'; -import { fetchConfigAndLicense } from '@actions/remote/systems'; -import { queryServerByIdentifier } from '@app/queries/app/servers'; interface OnboardingProps extends LaunchProps { theme: Theme; @@ -84,12 +83,10 @@ const Onboarding = ({ const displayLogin = (config: ClientConfig, license: ClientLicense) => { const {enabledSSOs, hasLoginForm, numberSSOs, ssoOptions} = loginOptions(config, license); + const displayName = 'displayName'; const passProps = { config, - extra, hasLoginForm, - launchError, - launchType, license, serverDisplayName: displayName, serverUrl, From ca053ed9c4deb719ef58ca058718081df39a3f50 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Sat, 12 Nov 2022 17:06:07 +0100 Subject: [PATCH 25/50] more advances on initial launch --- app/init/launch.ts | 15 +++++---------- app/screens/navigation.ts | 9 ++++++--- app/screens/onboarding/index.tsx | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index 017574007..9adbbcd27 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -19,7 +19,6 @@ import {convertToNotificationData} from '@utils/notification'; import {parseDeepLink} from '@utils/url'; import type {DeepLinkChannel, DeepLinkDM, DeepLinkGM, DeepLinkPermalink, DeepLinkWithData, LaunchProps} from '@typings/launch'; -import { fetchConfigAndLicense } from '@actions/remote/systems'; const initialNotificationTypes = [PushNotification.NOTIFICATION_TYPE.MESSAGE, PushNotification.NOTIFICATION_TYPE.SESSION]; @@ -59,7 +58,6 @@ const launchAppFromNotification = async (notification: NotificationWithData) => launchApp(props); }; -// pablo - here I need to validate the logic for launching the onboarding screen const launchApp = async (props: LaunchProps, resetNavigation = true) => { let serverUrl: string | undefined; switch (props?.launchType) { @@ -102,10 +100,9 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { // if (!onboardingAlreadyShown) { // here, check if there is not an active session and redirect to onboarding with a flag, so the sign in button will // redirect to the sign in - // return launchToOnboarding(props, goToLoginPage); + // return launchToOnboarding(props, goToLoginServerUrlPage); // } - console.log('\n\n first launch to onboarding \n\n', serverUrl); - return launchToOnboarding(props, resetNavigation, false, false, true, serverUrl); + return launchToOnboarding(props, resetNavigation, false, false, serverUrl); let launchType = props.launchType; if (!hasCurrentUser) { @@ -139,7 +136,6 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { // if (onboardingAlreadyShown) { // // launchToServer(props, resetNavigation); // } - console.log('\n\n second launch to onboarding \n\n', serverUrl); return launchToOnboarding(props, resetNavigation); }; @@ -199,8 +195,7 @@ const launchToOnboarding = ( resetNavigation = true, notActiveSession = true, whiteLabeledApp = false, - goToLogIn = true, - serverUrl = '', + goToLoginServerUrl = '', ) => { // here, if there is an active session, redirect to home // if there is a whitelabeled app, redirect to either SERVER or LOGIN but don't show the onboarding @@ -208,8 +203,8 @@ const launchToOnboarding = ( launchToServer(props, resetNavigation); } - // if there is not an active session, pass the prop and redirect to the LOGIN page (keep in mind all the redirection login to check for SSO stuff) - return resetToOnboarding(props, true, serverUrl); + // goToLoginServerUrl will contain a value when there is a server already configured but not a active session + return resetToOnboarding(props, goToLoginServerUrl); }; export const relaunchApp = (props: LaunchProps, resetNavigation = false) => { diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 680406507..32c03db7c 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -281,7 +281,7 @@ export function resetToSelectServer(passProps: LaunchProps) { }); } -export function resetToOnboarding(passProps: LaunchProps, goToLogIn: boolean, serverUrl: string) { +export function resetToOnboarding(passProps: LaunchProps, goToLoginServerUrl: string) { const theme = getDefaultThemeByAppearance(); const isDark = tinyColor(theme.sidebarBg).isDark(); StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); @@ -295,8 +295,7 @@ export function resetToOnboarding(passProps: LaunchProps, goToLogIn: boolean, se passProps: { ...passProps, theme, - goToLogIn, - serverUrl, + goToLoginServerUrl, }, options: { layout: { @@ -411,6 +410,10 @@ export function goToScreen(name: string, title: string, passProps = {}, options }, }; + if (name === Screens.SERVER) { + console.log('\n\n** screen pass props', passProps, componentId, name, '\n\n'); + } + return Navigation.push(componentId, { component: { id: name, diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 12a4b9e9f..3f4213957 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -21,16 +21,14 @@ import type {LaunchProps} from '@typings/launch'; interface OnboardingProps extends LaunchProps { theme: Theme; - goToLogIn: boolean; - serverUrl: string; + goToLoginServerUrl: string; } const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView); const Onboarding = ({ theme, - goToLogIn, - serverUrl, + goToLoginServerUrl, }: OnboardingProps) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); @@ -72,7 +70,7 @@ const Onboarding = ({ }, [currentIndex.value, slidesRef.current, moveToSlide]); const initLogin = async () => { - const data = await fetchConfigAndLicense(serverUrl, true); + const data = await fetchConfigAndLicense(goToLoginServerUrl, true); if (data.error) { console.log('Error getting the config and license information'); return; @@ -89,7 +87,7 @@ const Onboarding = ({ hasLoginForm, license, serverDisplayName: displayName, - serverUrl, + serverUrl: goToLoginServerUrl, ssoOptions, theme, }; @@ -105,9 +103,6 @@ const Onboarding = ({ }; const signInHandler = useCallback(() => { - if (goToLogIn) { - initLogin(); - } const topBar = { visible: true, drawBehind: true, @@ -121,8 +116,13 @@ const Onboarding = ({ title: '', }, }; + + if (goToLoginServerUrl) { + initLogin(); + return; + } goToScreen(Screens.SERVER, '', {theme}, {topBar}); - }, [goToLogIn]); + }, [goToLoginServerUrl]); const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { return ( From 375d64c35e06e8f5d38f50696c52df20d879ae78 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 15 Nov 2022 00:11:24 +0100 Subject: [PATCH 26/50] Add logic to don't show the onboarding once it has been shown once --- app/actions/app/global.ts | 12 ++++++++++++ app/constants/database.ts | 1 + app/init/launch.ts | 17 ++++++++--------- app/queries/app/global.ts | 13 +++++++++++++ app/screens/onboarding/index.tsx | 4 ++++ 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/app/actions/app/global.ts b/app/actions/app/global.ts index 09706cfc2..d6d8f921b 100644 --- a/app/actions/app/global.ts +++ b/app/actions/app/global.ts @@ -28,6 +28,18 @@ export const storeMultiServerTutorial = async (prepareRecordsOnly = false) => { } }; +export const storeOnboardingViewedValue = async (prepareRecordsOnly = false) => { + try { + const {operator} = DatabaseManager.getAppDatabaseAndOperator(); + return operator.handleGlobal({ + globals: [{id: GLOBAL_IDENTIFIERS.ONBOARDING, value: 'true'}], + prepareRecordsOnly, + }); + } catch (error) { + return {error}; + } +}; + export const storeProfileLongPressTutorial = async (prepareRecordsOnly = false) => { try { const {operator} = DatabaseManager.getAppDatabaseAndOperator(); diff --git a/app/constants/database.ts b/app/constants/database.ts index 4ac2f8695..44946ab45 100644 --- a/app/constants/database.ts +++ b/app/constants/database.ts @@ -72,6 +72,7 @@ export const GLOBAL_IDENTIFIERS = { DEVICE_TOKEN: 'deviceToken', MULTI_SERVER_TUTORIAL: 'multiServerTutorial', PROFILE_LONG_PRESS_TUTORIAL: 'profileLongPressTutorial', + ONBOARDING: 'onboarding', }; export enum OperationType { diff --git a/app/init/launch.ts b/app/init/launch.ts index 9adbbcd27..eccfbf422 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -9,6 +9,7 @@ import {appEntry, pushNotificationEntry, upgradeEntry} from '@actions/remote/ent import {Screens, DeepLink, Events, Launch, PushNotification} from '@constants'; import DatabaseManager from '@database/manager'; import {getActiveServerUrl, getServerCredentials, removeServerCredentials} from '@init/credentials'; +import {onboadingViewedValue} from '@queries/app/global'; import {getThemeForCurrentTeam} from '@queries/servers/preference'; import {getCurrentUserId} from '@queries/servers/system'; import {queryMyTeams} from '@queries/servers/team'; @@ -59,6 +60,7 @@ const launchAppFromNotification = async (notification: NotificationWithData) => }; const launchApp = async (props: LaunchProps, resetNavigation = true) => { + const onboadingViewed = await onboadingViewedValue(); let serverUrl: string | undefined; switch (props?.launchType) { case Launch.DeepLink: @@ -97,12 +99,9 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { hasCurrentUser = Boolean(currentUserId); } - // if (!onboardingAlreadyShown) { - // here, check if there is not an active session and redirect to onboarding with a flag, so the sign in button will - // redirect to the sign in - // return launchToOnboarding(props, goToLoginServerUrlPage); - // } - return launchToOnboarding(props, resetNavigation, false, false, serverUrl); + if (!onboadingViewed) { + return launchToOnboarding(props, resetNavigation, false, false, serverUrl); + } let launchType = props.launchType; if (!hasCurrentUser) { @@ -133,9 +132,9 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } - // if (onboardingAlreadyShown) { - // // launchToServer(props, resetNavigation); - // } + if (onboadingViewed) { + return launchToServer(props, resetNavigation); + } return launchToOnboarding(props, resetNavigation); }; diff --git a/app/queries/app/global.ts b/app/queries/app/global.ts index 062680458..bc37842fa 100644 --- a/app/queries/app/global.ts +++ b/app/queries/app/global.ts @@ -28,6 +28,19 @@ export const observeMultiServerTutorial = (appDatabase: Database) => { ); }; +export const onboadingViewedValue = async (): Promise => { + const appDatabase = DatabaseManager.appDatabase?.database; + if (!appDatabase) { + return false; + } + try { + const onboardingVal = await appDatabase.get(GLOBAL).find(GLOBAL_IDENTIFIERS.ONBOARDING); + return Boolean(onboardingVal?.value) || false; + } catch { + return false; + } +}; + export const observeProfileLongPresTutorial = () => { const appDatabase = DatabaseManager.appDatabase?.database; if (!appDatabase) { diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 3f4213957..ed964790b 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -5,6 +5,7 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, Animated as AnimatedRN} from 'react-native'; import Animated, {Easing, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; +import {storeOnboardingViewedValue} from '@actions/app/global'; import {fetchConfigAndLicense} from '@actions/remote/systems'; import {Screens} from '@app/constants'; import {loginOptions} from '@app/utils/server'; @@ -117,6 +118,9 @@ const Onboarding = ({ }, }; + // mark the onboarding as already viewed + storeOnboardingViewedValue(); + if (goToLoginServerUrl) { initLogin(); return; From b398c90f898844cf3ea93a0aa3ce4d0559768516 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 15 Nov 2022 13:33:27 +0100 Subject: [PATCH 27/50] simplyfy signatures --- app/init/launch.ts | 27 ++++++--------------------- app/screens/navigation.ts | 3 +-- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index eccfbf422..bb079026d 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -99,8 +99,9 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { hasCurrentUser = Boolean(currentUserId); } - if (!onboadingViewed) { - return launchToOnboarding(props, resetNavigation, false, false, serverUrl); + // invert this logic + if (onboadingViewed) { + return resetToOnboarding({...props, goToLoginServerUrl: serverUrl}); } let launchType = props.launchType; @@ -132,10 +133,11 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } - if (onboadingViewed) { + // invert this logic + if (!onboadingViewed) { return launchToServer(props, resetNavigation); } - return launchToOnboarding(props, resetNavigation); + return resetToOnboarding(props); }; const launchToHome = async (props: LaunchProps) => { @@ -189,23 +191,6 @@ const launchToServer = (props: LaunchProps, resetNavigation: Boolean) => { return goToScreen(Screens.SERVER, title, {...props}); }; -const launchToOnboarding = ( - props: LaunchProps, - resetNavigation = true, - notActiveSession = true, - whiteLabeledApp = false, - goToLoginServerUrl = '', -) => { - // here, if there is an active session, redirect to home - // if there is a whitelabeled app, redirect to either SERVER or LOGIN but don't show the onboarding - if (resetNavigation) { - launchToServer(props, resetNavigation); - } - - // goToLoginServerUrl will contain a value when there is a server already configured but not a active session - return resetToOnboarding(props, goToLoginServerUrl); -}; - export const relaunchApp = (props: LaunchProps, resetNavigation = false) => { return launchApp(props, resetNavigation); }; diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 32c03db7c..f2274f81e 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -281,7 +281,7 @@ export function resetToSelectServer(passProps: LaunchProps) { }); } -export function resetToOnboarding(passProps: LaunchProps, goToLoginServerUrl: string) { +export function resetToOnboarding(passProps: LaunchProps & {goToLoginServerUrl?: string}) { const theme = getDefaultThemeByAppearance(); const isDark = tinyColor(theme.sidebarBg).isDark(); StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); @@ -295,7 +295,6 @@ export function resetToOnboarding(passProps: LaunchProps, goToLoginServerUrl: st passProps: { ...passProps, theme, - goToLoginServerUrl, }, options: { layout: { From f1f43880a7a5d0d0be66db684ec803a9de5bd042 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 15 Nov 2022 13:50:19 +0100 Subject: [PATCH 28/50] Add logic to guard behind the showOnboarding config value the Onboarding feature --- app/init/launch.ts | 9 +++++---- assets/base/config.json | 3 ++- fastlane/Fastfile | 5 +++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index bb079026d..5805f4f87 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -6,6 +6,7 @@ import {Alert, DeviceEventEmitter, Linking, Platform} from 'react-native'; import {Notifications} from 'react-native-notifications'; import {appEntry, pushNotificationEntry, upgradeEntry} from '@actions/remote/entry'; +import LocalConfig from '@assets/config.json'; import {Screens, DeepLink, Events, Launch, PushNotification} from '@constants'; import DatabaseManager from '@database/manager'; import {getActiveServerUrl, getServerCredentials, removeServerCredentials} from '@init/credentials'; @@ -99,8 +100,8 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { hasCurrentUser = Boolean(currentUserId); } - // invert this logic - if (onboadingViewed) { + // invert this logic for onboardingViewed + if (LocalConfig.ShowOnboarding && onboadingViewed) { return resetToOnboarding({...props, goToLoginServerUrl: serverUrl}); } @@ -133,8 +134,8 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } - // invert this logic - if (!onboadingViewed) { + // invert this logic for onboardingViewed + if (LocalConfig.ShowOnboarding && !onboadingViewed) { return launchToServer(props, resetNavigation); } return resetToOnboarding(props); diff --git a/assets/base/config.json b/assets/base/config.json index 2212700d5..e1dcc81e6 100644 --- a/assets/base/config.json +++ b/assets/base/config.json @@ -36,5 +36,6 @@ "ShowSentryDebugOptions": false, - "CustomRequestHeaders": {} + "CustomRequestHeaders": {}, + "ShowOnboarding": false } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 11d85d774..7a1742c5e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -165,6 +165,11 @@ lane :configure do json['SentryDsnAndroid'] = ENV['SENTRY_DSN_ANDROID'] end + # Configure Show Onboarding + if ENV['SHOW_ONBOARDING'] == 'true' + json['ShowOnboarding'] = true + end + # Save the config.json file save_json_as_file('../dist/assets/config.json', json) From 9c1fd176f984afda50a3ee12be8f4dcde3ad5b6d Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 15 Nov 2022 14:37:09 +0100 Subject: [PATCH 29/50] redirect to onboarding after deleting server; fix styles for server screen in IOS --- app/init/launch.ts | 13 ++++++++++--- app/managers/session_manager.ts | 8 +++----- app/screens/onboarding/index.tsx | 11 ++++++++--- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index 5805f4f87..54d429a92 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -60,8 +60,15 @@ const launchAppFromNotification = async (notification: NotificationWithData) => launchApp(props); }; +/** + * + * @param props set of properties used to determine how to launch the app depending on the containing values + * @param resetNavigation used when loading the add_server screen and remove all the navigation stack + + * @returns a redirection to a screen, either onboarding, add_server, login or home depending on the scenario + */ const launchApp = async (props: LaunchProps, resetNavigation = true) => { - const onboadingViewed = await onboadingViewedValue(); + const onboardingViewed = LocalConfig.ShowOnboarding ? await onboadingViewedValue() : false; let serverUrl: string | undefined; switch (props?.launchType) { case Launch.DeepLink: @@ -101,7 +108,7 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } // invert this logic for onboardingViewed - if (LocalConfig.ShowOnboarding && onboadingViewed) { + if ((LocalConfig.ShowOnboarding && onboardingViewed)) { return resetToOnboarding({...props, goToLoginServerUrl: serverUrl}); } @@ -135,7 +142,7 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } // invert this logic for onboardingViewed - if (LocalConfig.ShowOnboarding && !onboadingViewed) { + if (LocalConfig.ShowOnboarding && !onboardingViewed) { return launchToServer(props, resetNavigation); } return resetToOnboarding(props); diff --git a/app/managers/session_manager.ts b/app/managers/session_manager.ts index 98411e776..335a1349c 100644 --- a/app/managers/session_manager.ts +++ b/app/managers/session_manager.ts @@ -18,7 +18,7 @@ import NetworkManager from '@managers/network_manager'; import WebsocketManager from '@managers/websocket_manager'; import {queryServerName} from '@queries/app/servers'; import {getCurrentUser} from '@queries/servers/user'; -import {getThemeFromState} from '@screens/navigation'; +import {getThemeFromState, resetToOnboarding} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import {addNewServer} from '@utils/server'; @@ -167,17 +167,15 @@ class SessionManager { if (activeServerUrl === serverUrl) { let displayName = ''; - let launchType: LaunchType = Launch.AddServer; + const launchType: LaunchType = Launch.Normal; if (!Object.keys(DatabaseManager.serverDatabases).length) { EphemeralStore.theme = undefined; - launchType = Launch.Normal; - if (activeServerDisplayName) { displayName = activeServerDisplayName; } } - relaunchApp({launchType, serverUrl, displayName}, true); + resetToOnboarding({launchType, serverUrl, displayName}); } }; diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index ed964790b..4ce6d039f 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -11,7 +11,7 @@ import {Screens} from '@app/constants'; import {loginOptions} from '@app/utils/server'; import Background from '@screens/background'; import {goToScreen, loginAnimationOptions} from '@screens/navigation'; -import {makeStyleSheetFromTheme} from '@utils/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import FooterButtons from './footer_buttons'; import Paginator from './paginator'; @@ -107,14 +107,19 @@ const Onboarding = ({ const topBar = { visible: true, drawBehind: true, + translucid: true, noBorder: true, elevation: 0, background: { color: 'transparent', }, backButton: { - color: theme.centerChannelColor, - title: '', + color: changeOpacity(theme.centerChannelColor, 0.56), + }, + scrollEdgeAppearance: { + active: true, + noBorder: true, + translucid: true, }, }; From 27557b171ef1cb73631423655203b8b88e2b758f Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 15 Nov 2022 16:15:06 +0100 Subject: [PATCH 30/50] remove unnecessary code --- app/init/launch.ts | 6 ++---- app/screens/navigation.ts | 4 ---- package.json | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index 54d429a92..6892135fc 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -107,8 +107,7 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { hasCurrentUser = Boolean(currentUserId); } - // invert this logic for onboardingViewed - if ((LocalConfig.ShowOnboarding && onboardingViewed)) { + if (LocalConfig.ShowOnboarding && !onboardingViewed) { return resetToOnboarding({...props, goToLoginServerUrl: serverUrl}); } @@ -141,8 +140,7 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } - // invert this logic for onboardingViewed - if (LocalConfig.ShowOnboarding && !onboardingViewed) { + if (!LocalConfig.ShowOnboarding || onboardingViewed) { return launchToServer(props, resetNavigation); } return resetToOnboarding(props); diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index f2274f81e..0f5e2a14f 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -409,10 +409,6 @@ export function goToScreen(name: string, title: string, passProps = {}, options }, }; - if (name === Screens.SERVER) { - console.log('\n\n** screen pass props', passProps, componentId, name, '\n\n'); - } - return Navigation.push(componentId, { component: { id: name, diff --git a/package.json b/package.json index e793dfa26..6e845eea7 100644 --- a/package.json +++ b/package.json @@ -191,7 +191,7 @@ "mmjstool": "mmjstool", "pod-install": "cd ios && bundle exec pod install", "postinstall": "patch-package && ./scripts/postinstall.sh", - "preinstall": "", + "preinstall": "npx solidarity", "prestorybook": "rnstl", "start": "react-native start", "storybook": "start-storybook -p 7007", From 6e3ed08163719c02daab6a15ba63d54a09d15ddc Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 15 Nov 2022 17:36:36 +0100 Subject: [PATCH 31/50] remove unnecessarys console.log --- app/screens/onboarding/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 4ce6d039f..89ddf2f34 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -73,7 +73,6 @@ const Onboarding = ({ const initLogin = async () => { const data = await fetchConfigAndLicense(goToLoginServerUrl, true); if (data.error) { - console.log('Error getting the config and license information'); return; } From bdbbd2cb36063ed85752e303a65c621ffa8db332 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Wed, 16 Nov 2022 15:01:06 +0100 Subject: [PATCH 32/50] pr feedback: rename svg; remove unnecessary go to login screen logic; refactor save/getOnboardingViewed value function --- app/actions/app/global.ts | 8 +- app/init/launch.ts | 11 +-- app/managers/session_manager.ts | 13 ++- app/queries/app/global.ts | 11 +-- app/screens/navigation.ts | 2 +- .../{calls_svg.tsx => calls.tsx} | 2 +- .../illustrations/{chat_svg.tsx => chat.tsx} | 2 +- ...{integrations_svg.tsx => integrations.tsx} | 2 +- ...ication_svg.tsx => team_communication.tsx} | 2 +- app/screens/onboarding/index.tsx | 95 ++++--------------- app/screens/onboarding/slides_data.tsx | 8 +- 11 files changed, 49 insertions(+), 107 deletions(-) rename app/screens/onboarding/illustrations/{calls_svg.tsx => calls.tsx} (99%) rename app/screens/onboarding/illustrations/{chat_svg.tsx => chat.tsx} (99%) rename app/screens/onboarding/illustrations/{integrations_svg.tsx => integrations.tsx} (99%) rename app/screens/onboarding/illustrations/{team_communication_svg.tsx => team_communication.tsx} (99%) diff --git a/app/actions/app/global.ts b/app/actions/app/global.ts index d6d8f921b..8e05287bd 100644 --- a/app/actions/app/global.ts +++ b/app/actions/app/global.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {logError} from '@app/utils/log'; import {GLOBAL_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; @@ -28,14 +29,15 @@ export const storeMultiServerTutorial = async (prepareRecordsOnly = false) => { } }; -export const storeOnboardingViewedValue = async (prepareRecordsOnly = false) => { +export const storeOnboardingViewedValue = async (value = true) => { try { const {operator} = DatabaseManager.getAppDatabaseAndOperator(); return operator.handleGlobal({ - globals: [{id: GLOBAL_IDENTIFIERS.ONBOARDING, value: 'true'}], - prepareRecordsOnly, + globals: [{id: GLOBAL_IDENTIFIERS.ONBOARDING, value}], + prepareRecordsOnly: false, }); } catch (error) { + logError('storeOnboardingViewedValue', error); return {error}; } }; diff --git a/app/init/launch.ts b/app/init/launch.ts index 6892135fc..a2df188d4 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -10,7 +10,7 @@ import LocalConfig from '@assets/config.json'; import {Screens, DeepLink, Events, Launch, PushNotification} from '@constants'; import DatabaseManager from '@database/manager'; import {getActiveServerUrl, getServerCredentials, removeServerCredentials} from '@init/credentials'; -import {onboadingViewedValue} from '@queries/app/global'; +import {getOnboardingViewed} from '@queries/app/global'; import {getThemeForCurrentTeam} from '@queries/servers/preference'; import {getCurrentUserId} from '@queries/servers/system'; import {queryMyTeams} from '@queries/servers/team'; @@ -68,7 +68,6 @@ const launchAppFromNotification = async (notification: NotificationWithData) => * @returns a redirection to a screen, either onboarding, add_server, login or home depending on the scenario */ const launchApp = async (props: LaunchProps, resetNavigation = true) => { - const onboardingViewed = LocalConfig.ShowOnboarding ? await onboadingViewedValue() : false; let serverUrl: string | undefined; switch (props?.launchType) { case Launch.DeepLink: @@ -107,10 +106,6 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { hasCurrentUser = Boolean(currentUserId); } - if (LocalConfig.ShowOnboarding && !onboardingViewed) { - return resetToOnboarding({...props, goToLoginServerUrl: serverUrl}); - } - let launchType = props.launchType; if (!hasCurrentUser) { // migrating from v1 @@ -140,7 +135,9 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } - if (!LocalConfig.ShowOnboarding || onboardingViewed) { + const onboardingViewed = LocalConfig.ShowOnboarding ? await getOnboardingViewed() : false; + + if (onboardingViewed) { return launchToServer(props, resetNavigation); } return resetToOnboarding(props); diff --git a/app/managers/session_manager.ts b/app/managers/session_manager.ts index a9779900e..071578539 100644 --- a/app/managers/session_manager.ts +++ b/app/managers/session_manager.ts @@ -5,6 +5,7 @@ import CookieManager, {Cookie} from '@react-native-cookies/cookies'; import {AppState, AppStateStatus, DeviceEventEmitter, Platform} from 'react-native'; import FastImage from 'react-native-fast-image'; +import {storeOnboardingViewedValue} from '@actions/app/global'; import {cancelSessionNotification, logout, scheduleSessionNotification} from '@actions/remote/session'; import {Events, Launch} from '@constants'; import DatabaseManager from '@database/manager'; @@ -17,7 +18,7 @@ import NetworkManager from '@managers/network_manager'; import WebsocketManager from '@managers/websocket_manager'; import {queryServerName} from '@queries/app/servers'; import {getCurrentUser} from '@queries/servers/user'; -import {getThemeFromState, resetToOnboarding} from '@screens/navigation'; +import {getThemeFromState} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import {deleteFileCache, deleteFileCacheByDir} from '@utils/file'; import {addNewServer} from '@utils/server'; @@ -167,15 +168,19 @@ class SessionManager { if (activeServerUrl === serverUrl) { let displayName = ''; - const launchType: LaunchType = Launch.Normal; + let launchType: LaunchType = Launch.AddServer; if (!Object.keys(DatabaseManager.serverDatabases).length) { EphemeralStore.theme = undefined; + launchType = Launch.Normal; + + // set the onboardingViewed value to false so the launch will show the onboarding screen after all servers were removed + storeOnboardingViewedValue(false); + if (activeServerDisplayName) { displayName = activeServerDisplayName; } } - - resetToOnboarding({launchType, serverUrl, displayName}); + relaunchApp({launchType, serverUrl, displayName}, true); } }; diff --git a/app/queries/app/global.ts b/app/queries/app/global.ts index bc37842fa..94d898479 100644 --- a/app/queries/app/global.ts +++ b/app/queries/app/global.ts @@ -28,14 +28,11 @@ export const observeMultiServerTutorial = (appDatabase: Database) => { ); }; -export const onboadingViewedValue = async (): Promise => { - const appDatabase = DatabaseManager.appDatabase?.database; - if (!appDatabase) { - return false; - } +export const getOnboardingViewed = async (): Promise => { try { - const onboardingVal = await appDatabase.get(GLOBAL).find(GLOBAL_IDENTIFIERS.ONBOARDING); - return Boolean(onboardingVal?.value) || false; + const {database} = DatabaseManager.getAppDatabaseAndOperator(); + const onboardingVal = await database.get(GLOBAL).find(GLOBAL_IDENTIFIERS.ONBOARDING); + return onboardingVal?.value ?? false; } catch { return false; } diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 0f5e2a14f..b98575420 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -281,7 +281,7 @@ export function resetToSelectServer(passProps: LaunchProps) { }); } -export function resetToOnboarding(passProps: LaunchProps & {goToLoginServerUrl?: string}) { +export function resetToOnboarding(passProps: LaunchProps) { const theme = getDefaultThemeByAppearance(); const isDark = tinyColor(theme.sidebarBg).isDark(); StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content'); diff --git a/app/screens/onboarding/illustrations/calls_svg.tsx b/app/screens/onboarding/illustrations/calls.tsx similarity index 99% rename from app/screens/onboarding/illustrations/calls_svg.tsx rename to app/screens/onboarding/illustrations/calls.tsx index fa1a6f6d1..31813a95e 100644 --- a/app/screens/onboarding/illustrations/calls_svg.tsx +++ b/app/screens/onboarding/illustrations/calls.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import * as React from 'react'; +import React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import Svg, {Ellipse, Path, Mask, G} from 'react-native-svg'; diff --git a/app/screens/onboarding/illustrations/chat_svg.tsx b/app/screens/onboarding/illustrations/chat.tsx similarity index 99% rename from app/screens/onboarding/illustrations/chat_svg.tsx rename to app/screens/onboarding/illustrations/chat.tsx index 155174cc1..2295b8981 100644 --- a/app/screens/onboarding/illustrations/chat_svg.tsx +++ b/app/screens/onboarding/illustrations/chat.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import * as React from 'react'; +import React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import Svg, { G, diff --git a/app/screens/onboarding/illustrations/integrations_svg.tsx b/app/screens/onboarding/illustrations/integrations.tsx similarity index 99% rename from app/screens/onboarding/illustrations/integrations_svg.tsx rename to app/screens/onboarding/illustrations/integrations.tsx index 118761a02..641150b53 100644 --- a/app/screens/onboarding/illustrations/integrations_svg.tsx +++ b/app/screens/onboarding/illustrations/integrations.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import * as React from 'react'; +import React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import Svg, { Rect, diff --git a/app/screens/onboarding/illustrations/team_communication_svg.tsx b/app/screens/onboarding/illustrations/team_communication.tsx similarity index 99% rename from app/screens/onboarding/illustrations/team_communication_svg.tsx rename to app/screens/onboarding/illustrations/team_communication.tsx index d707ccea0..ffd254568 100644 --- a/app/screens/onboarding/illustrations/team_communication_svg.tsx +++ b/app/screens/onboarding/illustrations/team_communication.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import * as React from 'react'; +import React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import Svg, {Ellipse, Path, Mask, G, Defs, ClipPath} from 'react-native-svg'; diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 89ddf2f34..d89e44534 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -6,12 +6,10 @@ import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, import Animated, {Easing, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; import {storeOnboardingViewedValue} from '@actions/app/global'; -import {fetchConfigAndLicense} from '@actions/remote/systems'; import {Screens} from '@app/constants'; -import {loginOptions} from '@app/utils/server'; import Background from '@screens/background'; import {goToScreen, loginAnimationOptions} from '@screens/navigation'; -import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {makeStyleSheetFromTheme} from '@utils/theme'; import FooterButtons from './footer_buttons'; import Paginator from './paginator'; @@ -22,14 +20,27 @@ import type {LaunchProps} from '@typings/launch'; interface OnboardingProps extends LaunchProps { theme: Theme; - goToLoginServerUrl: string; } const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView); +const getStyleSheet = makeStyleSheetFromTheme(() => ({ + onBoardingContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + verticalAlign: 'top', + }, + scrollContainer: { + flex: 1, + alignItems: 'center', + height: '100%', + justifyContent: 'center', + }, +})); + const Onboarding = ({ theme, - goToLoginServerUrl, }: OnboardingProps) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); @@ -70,67 +81,12 @@ const Onboarding = ({ } }, [currentIndex.value, slidesRef.current, moveToSlide]); - const initLogin = async () => { - const data = await fetchConfigAndLicense(goToLoginServerUrl, true); - if (data.error) { - return; - } - - displayLogin(data.config!, data.license!); - }; - - const displayLogin = (config: ClientConfig, license: ClientLicense) => { - const {enabledSSOs, hasLoginForm, numberSSOs, ssoOptions} = loginOptions(config, license); - const displayName = 'displayName'; - const passProps = { - config, - hasLoginForm, - license, - serverDisplayName: displayName, - serverUrl: goToLoginServerUrl, - ssoOptions, - theme, - }; - - const redirectSSO = !hasLoginForm && numberSSOs === 1; - const screen = redirectSSO ? Screens.SSO : Screens.LOGIN; - if (redirectSSO) { - // @ts-expect-error ssoType not in definition - passProps.ssoType = enabledSSOs[0]; - } - - goToScreen(screen, '', passProps, loginAnimationOptions()); - }; - const signInHandler = useCallback(() => { - const topBar = { - visible: true, - drawBehind: true, - translucid: true, - noBorder: true, - elevation: 0, - background: { - color: 'transparent', - }, - backButton: { - color: changeOpacity(theme.centerChannelColor, 0.56), - }, - scrollEdgeAppearance: { - active: true, - noBorder: true, - translucid: true, - }, - }; - // mark the onboarding as already viewed storeOnboardingViewedValue(); - if (goToLoginServerUrl) { - initLogin(); - return; - } - goToScreen(Screens.SERVER, '', {theme}, {topBar}); - }, [goToLoginServerUrl]); + goToScreen(Screens.SERVER, '', {theme}, loginAnimationOptions()); + }, []); const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { return ( @@ -191,19 +147,4 @@ const Onboarding = ({ ); }; -const getStyleSheet = makeStyleSheetFromTheme(() => ({ - onBoardingContainer: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - verticalAlign: 'top', - }, - scrollContainer: { - flex: 1, - alignItems: 'center', - height: '100%', - justifyContent: 'center', - }, -})); - export default Onboarding; diff --git a/app/screens/onboarding/slides_data.tsx b/app/screens/onboarding/slides_data.tsx index a02002dfc..a0f75c3e1 100644 --- a/app/screens/onboarding/slides_data.tsx +++ b/app/screens/onboarding/slides_data.tsx @@ -5,10 +5,10 @@ import React from 'react'; import {useIntl} from 'react-intl'; import {StyleSheet} from 'react-native'; -import CallsSvg from './illustrations/calls_svg'; -import ChatSvg from './illustrations/chat_svg'; -import IntegrationsSvg from './illustrations/integrations_svg'; -import TeamCommunicationSvg from './illustrations/team_communication_svg'; +import CallsSvg from './illustrations/calls'; +import ChatSvg from './illustrations/chat'; +import IntegrationsSvg from './illustrations/integrations'; +import TeamCommunicationSvg from './illustrations/team_communication'; export type OnboardingItem = { id: string; From 0d5f86b0ab6c8195eae1344907f153387b1fa60d Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Wed, 16 Nov 2022 18:07:05 +0100 Subject: [PATCH 33/50] pr feedback, reorganize styles, move types to correct folder, remove unnecesary values --- app/screens/onboarding/footer_buttons.tsx | 31 ++++++++++++----- app/screens/onboarding/illustrations/chat.tsx | 8 ----- app/screens/onboarding/index.tsx | 31 ++++++++--------- app/screens/onboarding/paginator.tsx | 33 ++++++++----------- app/screens/onboarding/slide.tsx | 17 +++++----- app/screens/onboarding/slides_data.tsx | 15 ++------- tsconfig.json | 2 +- types/screens/onboarding.ts | 7 ++++ 8 files changed, 70 insertions(+), 74 deletions(-) create mode 100644 types/screens/onboarding.ts diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index a587bba2a..2b381be04 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -5,8 +5,8 @@ import React from 'react'; import {Pressable, useWindowDimensions, View} from 'react-native'; import Animated, {Extrapolate, interpolate, useAnimatedStyle} from 'react-native-reanimated'; -import CompassIcon from '@app/components/compass_icon'; -import FormattedText from '@app/components/formatted_text'; +import CompassIcon from '@components/compass_icon'; +import FormattedText from '@components/formatted_text'; import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; import {makeStyleSheetFromTheme} from '@utils/theme'; @@ -28,9 +28,25 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ marginLeft: 5, marginTop: 2, }, + nextButtonText: { + flexDirection: 'row', + position: 'absolute', + }, + signInButtonText: { + flexDirection: 'row', + }, + footerButtonsContainer: { + flexDirection: 'column', + height: 150, + marginTop: 15, + width: '100%', + marginHorizontal: 10, + alignItems: 'center', + }, })); const AnimatedButton = Animated.createAnimatedComponent(Pressable); +const BUTTON_SIZE = 80; const FooterButtons = ({ theme, @@ -41,7 +57,6 @@ const FooterButtons = ({ }: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); - const BUTTON_SIZE = 80; // keep in mind penultimate and ultimate slides to run buttons text/opacity/size animations const penultimateSlide = lastSlideIndex - 1; @@ -96,7 +111,7 @@ const FooterButtons = ({ }); const nextButtonText = ( - + + + nextSlideHandler()} + onPress={nextSlideHandler} style={[styles.button, buttonBackgroundStyle(theme, 'm', 'primary', 'default'), resizeStyle]} > {nextButtonText} @@ -131,7 +146,7 @@ const FooterButtons = ({ signInHandler()} + onPress={signInHandler} style={[styles.button, buttonBackgroundStyle(theme, 'm', 'link', 'default'), opacitySignInButton]} > { id='image0_1023_92018' width={133} height={302} - - // @ts-expect-error string source xlinkHref='data:image/png;base64,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' /> diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index d89e44534..44e011da9 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -2,19 +2,19 @@ // See LICENSE.txt for license information. import React, {useCallback, useEffect, useRef} from 'react'; -import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, Animated as AnimatedRN} from 'react-native'; +import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, Animated as AnimatedRN, StyleSheet} from 'react-native'; import Animated, {Easing, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; import {storeOnboardingViewedValue} from '@actions/app/global'; import {Screens} from '@app/constants'; import Background from '@screens/background'; import {goToScreen, loginAnimationOptions} from '@screens/navigation'; -import {makeStyleSheetFromTheme} from '@utils/theme'; +import {OnboardingItem} from '@typings/screens/onboarding'; import FooterButtons from './footer_buttons'; import Paginator from './paginator'; import SlideItem from './slide'; -import useSlidesData, {OnboardingItem} from './slides_data'; +import useSlidesData from './slides_data'; import type {LaunchProps} from '@typings/launch'; @@ -22,9 +22,7 @@ interface OnboardingProps extends LaunchProps { theme: Theme; } -const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView); - -const getStyleSheet = makeStyleSheetFromTheme(() => ({ +const styles = StyleSheet.create({ onBoardingContainer: { flex: 1, alignItems: 'center', @@ -34,17 +32,15 @@ const getStyleSheet = makeStyleSheetFromTheme(() => ({ scrollContainer: { flex: 1, alignItems: 'center', - height: '100%', justifyContent: 'center', }, -})); +}); const Onboarding = ({ theme, }: OnboardingProps) => { const {width} = useWindowDimensions(); - const styles = getStyleSheet(theme); - const slidesData = useSlidesData().slidesData; + const {slidesData} = useSlidesData(); const lastSlideIndex = slidesData.length - 1; const slidesRef = useAnimatedRef(); @@ -63,14 +59,14 @@ const Onboarding = ({ return () => scrollAnimation.current.removeAllListeners(); }, []); - const moveToSlide = (slideIndexToMove: number) => { + const moveToSlide = useCallback((slideIndexToMove: number) => { AnimatedRN.timing(scrollAnimation.current, { toValue: (slideIndexToMove * width), duration: Math.abs(currentIndex.value - slideIndexToMove) * 200, useNativeDriver: true, easing: Easing.linear, }).start(); - }; + }, [currentIndex.value]); const nextSlide = useCallback(() => { const nextSlideIndex = currentIndex.value + 1; @@ -95,7 +91,7 @@ const Onboarding = ({ theme={theme} scrollX={scrollX} index={index} - key={item.id} + key={`key-${index.toString()}`} /> ); }, []); @@ -112,12 +108,11 @@ const Onboarding = ({ testID='onboarding.screen' > - - + ); }; diff --git a/app/screens/onboarding/paginator.tsx b/app/screens/onboarding/paginator.tsx index b1798af18..ca1aa8297 100644 --- a/app/screens/onboarding/paginator.tsx +++ b/app/screens/onboarding/paginator.tsx @@ -7,10 +7,8 @@ import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated'; import {makeStyleSheetFromTheme} from '@utils/theme'; -import {OnboardingItem} from './slides_data'; - type Props = { - data: OnboardingItem[]; + dataLength: number; theme: Theme; scrollX: Animated.SharedValue; moveToSlide: (slideIndexToMove: number) => void; @@ -46,20 +44,25 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ width: DOT_SIZE, opacity: 0.15, }, + paginatorContainer: { + flexDirection: 'row', + height: 15, + }, })); const Paginator = ({ theme, - data, + dataLength, scrollX, moveToSlide, }: Props) => { + const styles = getStyleSheet(theme); return ( - - {data.map((item: OnboardingItem, index: number) => { + + {[...Array(dataLength)].map((item: number, index: number) => { return ( { }); return ( - moveToSlide(index)} - > - - - + moveToSlide(index)}> + + + ); }; diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 2811fe5ac..4bc19ea2c 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -5,11 +5,10 @@ import React, {useEffect, useState} from 'react'; import {View, useWindowDimensions} from 'react-native'; import Animated, {Extrapolate, interpolate, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {OnboardingItem} from '@typings/screens/onboarding'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; -import {OnboardingItem} from './slides_data'; - type Props = { item: OnboardingItem; theme: Theme; @@ -24,6 +23,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ height: 100, color: theme.centerChannelColor, textAlign: 'center', + fontFamily: 'Metropolis-SemiBold', + paddingHorizontal: 20, }, fontTitle: { fontSize: 40, @@ -36,10 +37,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ opacity: 0, }, description: { - fontWeight: '400', - fontSize: 16, textAlign: 'center', paddingHorizontal: 20, + fontFamily: 'Open Sans', height: 80, color: changeOpacity(theme.centerChannelColor, 0.64), ...typography('Body', 200, 'Regular'), @@ -57,15 +57,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, })); +const FIRST_SLIDE = 0; +const FIRST_LOAD_ELEMENTS_POSITION = 400; + const SlideItem = ({theme, item, scrollX, index}: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); - const FIRST_SLIDE = 0; /** * Code used to animate the first image load */ - const FIRST_LOAD_ELEMENTS_POSITION = 400; const [firstLoad, setFirstLoad] = useState(true); const initialImagePosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION); @@ -182,7 +183,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { > {item.image} - + { ); }; -export default SlideItem; +export default React.memo(SlideItem); diff --git a/app/screens/onboarding/slides_data.tsx b/app/screens/onboarding/slides_data.tsx index a0f75c3e1..467fa2887 100644 --- a/app/screens/onboarding/slides_data.tsx +++ b/app/screens/onboarding/slides_data.tsx @@ -5,18 +5,13 @@ import React from 'react'; import {useIntl} from 'react-intl'; import {StyleSheet} from 'react-native'; +import {OnboardingItem} from '@typings/screens/onboarding'; + import CallsSvg from './illustrations/calls'; import ChatSvg from './illustrations/chat'; import IntegrationsSvg from './illustrations/integrations'; import TeamCommunicationSvg from './illustrations/team_communication'; -export type OnboardingItem = { - id: string; - title: string; - description: string; - image: React.ReactElement; -} - const styles = StyleSheet.create({ image: { justifyContent: 'center', @@ -35,25 +30,21 @@ const useSalidesData = () => { const slidesData: OnboardingItem[] = [ { - id: '1', - title: intl.formatMessage({id: 'onboarding_screen.welcome', defaultMessage: 'Welcome'}), + title: intl.formatMessage({id: 'onboarding.welcome', defaultMessage: 'Welcome'}), description: intl.formatMessage({id: 'onboaring.welcome_description', defaultMessage: 'Mattermost is an open source platform for developer collaboration. Secure, flexible, and integrated with your tools.'}), image: chatSvg, }, { - id: '2', title: intl.formatMessage({id: 'onboarding.realtime_collaboration', defaultMessage: 'Collaborate in real-time'}), description: intl.formatMessage({id: 'onboarding.realtime_collaboration_description', defaultMessage: 'Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.'}), image: teamCommunicationSvg, }, { - id: '3', title: intl.formatMessage({id: 'onboarding.calls', defaultMessage: 'Start secure audio calls instantly'}), description: intl.formatMessage({id: 'onboarding.calls_description', defaultMessage: 'When typing isn’t fast enough, switch from channel-based chat to secure audio calls with a single tap.'}), image: callsSvg, }, { - id: '4', title: intl.formatMessage({id: 'onboarding.integrations', defaultMessage: 'Integrate with tools you love'}), description: intl.formatMessage({id: 'onboarding.integrations_description', defaultMessage: 'Go beyond chat with tightly-integratedproduct solutions matched to common development processes.'}), image: integrationsSvg, diff --git a/tsconfig.json b/tsconfig.json index 6b405d1cf..0dec6dd84 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -63,7 +63,7 @@ "*": ["./*", "node_modules/*"], } }, - "include": ["app/**/*", "share_extensionn/**/*", "test/**/*", "types/**/*"], + "include": ["app/**/*", "share_extension/**/*", "test/**/*", "types/**/*"], "exclude": [ "node_modules", "build", diff --git a/types/screens/onboarding.ts b/types/screens/onboarding.ts new file mode 100644 index 000000000..a1809e20c --- /dev/null +++ b/types/screens/onboarding.ts @@ -0,0 +1,7 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +export type OnboardingItem = { + title: string; + description: string; + image: React.ReactElement; +}; From eb10a70053bb417b55b53e2f72fcff18505133f4 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Wed, 16 Nov 2022 18:13:30 +0100 Subject: [PATCH 34/50] Add translated text entries --- assets/base/i18n/en.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 8d38f01d8..5be3baffa 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -497,6 +497,9 @@ "mobile.oauth.switch_to_browser.error_title": "Sign in error", "mobile.oauth.switch_to_browser.title": "Redirecting...", "mobile.oauth.try_again": "Try again", + "mobile.onboarding.next": "Next", + "mobile.onboarding.sign_in": "Sign in", + "mobile.onboarding.sign_in_to_get_started": "Sign in to get started", "mobile.open_gm.error": "We couldn't open a group message with those users. Please check your connection and try again.", "mobile.participants.header": "Thread Participants", "mobile.permission_denied_dismiss": "Don't Allow", @@ -650,6 +653,14 @@ "notification.message_not_found": "Message not found", "notification.not_channel_member": "This message belongs to a channel where you are not a member.", "notification.not_team_member": "This message belongs to a team where you are not a member.", + "onboarding.calls": "Start secure audio calls instantly", + "onboarding.calls_description": "When typing isn’t fast enough, switch from channel-based chat to secure audio calls with a single tap.", + "onboarding.integrations": "Integrate with tools you love", + "onboarding.integrations_description": "Go beyond chat with tightly-integratedproduct solutions matched to common development processes.", + "onboarding.realtime_collaboration": "Collaborate in real-time", + "onboarding.realtime_collaboration_description": "Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.", + "onboarding.welcome": "Welcome", + "onboaring.welcome_description": "Mattermost is an open source platform for developer collaboration. Secure, flexible, and integrated with your tools.", "password_send.description": "To reset your password, enter the email address you used to sign up", "password_send.error": "Please enter a valid email address.", "password_send.generic_error": "We were unable to send you a reset password link. Please contact your System Admin for assistance.", From 595238e9c1a8f14b9d427fd3f7bb5734d84c3603 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Thu, 17 Nov 2022 13:06:21 +0100 Subject: [PATCH 35/50] fix some design pr feedback --- app/screens/onboarding/footer_buttons.tsx | 18 ++++++------ app/screens/onboarding/index.tsx | 27 +++++------------ app/screens/onboarding/paginator.tsx | 16 +++++++++-- app/screens/onboarding/slide.tsx | 35 ++++++++++++----------- app/screens/onboarding/slides_data.tsx | 11 +++---- app/utils/typography.ts | 7 ++++- assets/base/i18n/en.json | 2 +- 7 files changed, 61 insertions(+), 55 deletions(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index 2b381be04..1c89ee5bf 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -37,16 +37,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, footerButtonsContainer: { flexDirection: 'column', - height: 150, - marginTop: 15, + height: 120, + marginTop: 25, + marginBottom: 15, width: '100%', - marginHorizontal: 10, alignItems: 'center', }, })); const AnimatedButton = Animated.createAnimatedComponent(Pressable); -const BUTTON_SIZE = 80; +const BUTTON_SIZE = 100; const FooterButtons = ({ theme, @@ -115,7 +115,7 @@ const FooterButtons = ({ ); @@ -139,7 +139,7 @@ const FooterButtons = ({ {nextButtonText} {signInButtonText} @@ -147,12 +147,12 @@ const FooterButtons = ({ diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 44e011da9..830cd1f6a 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -1,9 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useRef} from 'react'; -import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, Animated as AnimatedRN, StyleSheet} from 'react-native'; -import Animated, {Easing, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; +import React, {useCallback} from 'react'; +import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet} from 'react-native'; +import Animated, {useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; import {storeOnboardingViewedValue} from '@actions/app/global'; import {Screens} from '@app/constants'; @@ -45,27 +45,14 @@ const Onboarding = ({ const slidesRef = useAnimatedRef(); const scrollX = useSharedValue(0); - const scrollAnimation = useRef(new AnimatedRN.Value(0)); const currentIndex = useDerivedValue(() => Math.round(scrollX.value / width)); - useEffect(() => { - scrollAnimation.current?.addListener((animation) => { - slidesRef.current?.scrollTo({ - x: animation.value, - animated: false, - }); - }); - return () => scrollAnimation.current.removeAllListeners(); - }, []); - const moveToSlide = useCallback((slideIndexToMove: number) => { - AnimatedRN.timing(scrollAnimation.current, { - toValue: (slideIndexToMove * width), - duration: Math.abs(currentIndex.value - slideIndexToMove) * 200, - useNativeDriver: true, - easing: Easing.linear, - }).start(); + slidesRef.current?.scrollTo({ + x: (slideIndexToMove * width), + animated: true, + }); }, [currentIndex.value]); const nextSlide = useCallback(() => { diff --git a/app/screens/onboarding/paginator.tsx b/app/screens/onboarding/paginator.tsx index ca1aa8297..69a9ef906 100644 --- a/app/screens/onboarding/paginator.tsx +++ b/app/screens/onboarding/paginator.tsx @@ -47,6 +47,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ paginatorContainer: { flexDirection: 'row', height: 15, + justifyContent: 'space-between', + width: 120, }, })); @@ -59,7 +61,7 @@ const Paginator = ({ const styles = getStyleSheet(theme); return ( - {[...Array(dataLength)].map((item: number, index: number) => { + {[...Array(dataLength)].map((_, index: number) => { return ( { +const Dot = ({ + index, + scrollX, + theme, + moveToSlide, +}: DotProps) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); const inputRange = [(index - 1) * width, index * width, (index + 1) * width]; @@ -108,7 +115,10 @@ const Dot = ({index, scrollX, theme, moveToSlide}: DotProps) => { }); return ( - moveToSlide(index)}> + moveToSlide(index)} + hitSlop={{top: 8, left: 8, right: 8, bottom: 8}} + > diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 4bc19ea2c..020fec814 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -17,20 +17,26 @@ type Props = { }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + image: { + justifyContent: 'center', + maxHeight: 180, + }, title: { - fontWeight: '600', - marginBottom: 5, height: 100, color: theme.centerChannelColor, textAlign: 'center', - fontFamily: 'Metropolis-SemiBold', paddingHorizontal: 20, }, fontTitle: { - fontSize: 40, + marginTop: 32, + ...typography('Heading', 1000, 'SemiBold'), }, fontFirstTitle: { - fontSize: 66, + ...typography('Heading', 1200, 'SemiBold'), + paddingTop: 40, + }, + widthLastSlide: { + paddingHorizontal: 50, }, firstSlideInitialPosition: { left: 200, @@ -39,17 +45,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ description: { textAlign: 'center', paddingHorizontal: 20, - fontFamily: 'Open Sans', height: 80, color: changeOpacity(theme.centerChannelColor, 0.64), ...typography('Body', 200, 'Regular'), }, - image: { - justifyContent: 'center', - height: 60, - maxHeight: 180, - width: 60, - }, itemContainer: { flex: 1, alignItems: 'center', @@ -58,6 +57,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ })); const FIRST_SLIDE = 0; +const LAST_SLIDE = 3; const FIRST_LOAD_ELEMENTS_POSITION = 400; const SlideItem = ({theme, item, scrollX, index}: Props) => { @@ -77,11 +77,11 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { useEffect(() => { if (index === FIRST_SLIDE) { - initialImagePosition.value = withTiming(0, {duration: 1000}); - initialTitlePosition.value = withTiming(0, {duration: 1250}); - initialDescriptionPosition.value = withTiming(0, {duration: 1500}); + initialImagePosition.value = withTiming(0, {duration: 500}); + initialTitlePosition.value = withTiming(0, {duration: 700}); + initialDescriptionPosition.value = withTiming(0, {duration: 900}); - initialElementsOpacity.value = withTiming(1, {duration: 1500}); + initialElementsOpacity.value = withTiming(1, {duration: 900}); setFirstLoad(false); } }, []); @@ -175,6 +175,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { { style={[ styles.title, (index === FIRST_SLIDE ? styles.fontFirstTitle : styles.fontTitle), + (index === LAST_SLIDE ? styles.widthLastSlide : undefined), translateTitle, opacity, translateFirstTitleOnLoad, @@ -202,6 +204,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { translateDescription, opacity, (index === FIRST_SLIDE && firstLoad ? styles.firstSlideInitialPosition : undefined), + (index === LAST_SLIDE ? styles.widthLastSlide : undefined), translateFirstDescriptionOnLoad, ]} > diff --git a/app/screens/onboarding/slides_data.tsx b/app/screens/onboarding/slides_data.tsx index 467fa2887..b5d2eed8f 100644 --- a/app/screens/onboarding/slides_data.tsx +++ b/app/screens/onboarding/slides_data.tsx @@ -15,9 +15,10 @@ import TeamCommunicationSvg from './illustrations/team_communication'; const styles = StyleSheet.create({ image: { justifyContent: 'center', - height: 60, - maxHeight: 180, - width: 60, + height: 200, + }, + lastSlideImage: { + height: 250, }, }); @@ -26,7 +27,7 @@ const useSalidesData = () => { const callsSvg = (); const chatSvg = (); const teamCommunicationSvg = (); - const integrationsSvg = (); + const integrationsSvg = (); const slidesData: OnboardingItem[] = [ { @@ -46,7 +47,7 @@ const useSalidesData = () => { }, { title: intl.formatMessage({id: 'onboarding.integrations', defaultMessage: 'Integrate with tools you love'}), - description: intl.formatMessage({id: 'onboarding.integrations_description', defaultMessage: 'Go beyond chat with tightly-integratedproduct solutions matched to common development processes.'}), + description: intl.formatMessage({id: 'onboarding.integrations_description', defaultMessage: 'Go beyond chat with tightly-integrated product solutions matched to common development processes.'}), image: integrationsSvg, }, ]; diff --git a/app/utils/typography.ts b/app/utils/typography.ts index 812bf5f35..50ecbf763 100644 --- a/app/utils/typography.ts +++ b/app/utils/typography.ts @@ -6,7 +6,7 @@ import {StyleSheet, TextStyle} from 'react-native'; // type FontFamilies = 'OpenSans' | 'Metropolis'; type FontTypes = 'Heading' | 'Body'; type FontStyles = 'SemiBold' | 'Regular' | 'Light'; -type FontSizes = 25 | 50 | 75 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000; +type FontSizes = 25 | 50 | 75 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000 | 1200; const fontFamily = StyleSheet.create({ OpenSans: { @@ -30,6 +30,11 @@ const fontStyle = StyleSheet.create({ }); const fontSize = StyleSheet.create({ + 1200: { + fontSize: 66, + lineHeight: 48, + letterSpacing: -0.02, + }, 1000: { fontSize: 40, lineHeight: 48, diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 5be3baffa..562926c8a 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -656,7 +656,7 @@ "onboarding.calls": "Start secure audio calls instantly", "onboarding.calls_description": "When typing isn’t fast enough, switch from channel-based chat to secure audio calls with a single tap.", "onboarding.integrations": "Integrate with tools you love", - "onboarding.integrations_description": "Go beyond chat with tightly-integratedproduct solutions matched to common development processes.", + "onboarding.integrations_description": "Go beyond chat with tightly-integrated product solutions matched to common development processes.", "onboarding.realtime_collaboration": "Collaborate in real-time", "onboarding.realtime_collaboration_description": "Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.", "onboarding.welcome": "Welcome", From b8535c09bd19603773754b935a4a312510a53ac3 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Thu, 17 Nov 2022 19:36:16 +0100 Subject: [PATCH 36/50] modify animation times so it looks better and make slighter animation for ios --- app/screens/onboarding/index.tsx | 1 + app/screens/onboarding/slide.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 830cd1f6a..3255d8b5c 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -100,6 +100,7 @@ const Onboarding = ({ style={styles.scrollContainer} > { useEffect(() => { if (index === FIRST_SLIDE) { - initialImagePosition.value = withTiming(0, {duration: 500}); - initialTitlePosition.value = withTiming(0, {duration: 700}); - initialDescriptionPosition.value = withTiming(0, {duration: 900}); + initialImagePosition.value = withTiming(0, {duration: 600}); + initialTitlePosition.value = withTiming(0, {duration: 800}); + initialDescriptionPosition.value = withTiming(0, {duration: 1000}); - initialElementsOpacity.value = withTiming(1, {duration: 900}); + initialElementsOpacity.value = withTiming(1, {duration: 1000}); setFirstLoad(false); } }, []); From 2295ab46b51d008a7fab70bf4bcdfa6bf38ec16e Mon Sep 17 00:00:00 2001 From: Matthew Birtch Date: Thu, 17 Nov 2022 17:33:05 -0500 Subject: [PATCH 37/50] updating illustrations --- .../onboarding/illustrations/calls.tsx | 35 +++-- app/screens/onboarding/illustrations/chat.tsx | 134 ++---------------- .../onboarding/illustrations/integrations.tsx | 58 ++++---- .../illustrations/team_communication.tsx | 71 ++++------ app/screens/onboarding/slides_data.tsx | 30 +++- 5 files changed, 101 insertions(+), 227 deletions(-) diff --git a/app/screens/onboarding/illustrations/calls.tsx b/app/screens/onboarding/illustrations/calls.tsx index 31813a95e..1e0cfe727 100644 --- a/app/screens/onboarding/illustrations/calls.tsx +++ b/app/screens/onboarding/illustrations/calls.tsx @@ -1,15 +1,21 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; +import * as React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; -import Svg, {Ellipse, Path, Mask, G} from 'react-native-svg'; +import Svg, { + Ellipse, + Path, + Mask, + G, +} from 'react-native-svg'; -type SvgProps = { +type Props = { + theme: Theme; styles: StyleProp; }; -const CallsSvg = ({styles}: SvgProps) => { +const CallsSvg = ({theme, styles}: Props) => { return ( { cy={116.733} rx={72.9374} ry={72.8026} - fill='#fff' + fill={theme.centerChannelBg} /> { cy={116.733} rx={72.9374} ry={72.8026} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.16} /> { ry={72.8026} fill='#fff' /> - { /> { /> ; }; -const ChatSvg = ({styles}: SvgProps) => { +const ChatSvg = ({theme, styles}: Props) => { return ( { /> { /> { /> { /> { /> { /> { fillOpacity={0.12} /> - - - - - - - - - - - - - - - - - - - - - ); }; diff --git a/app/screens/onboarding/illustrations/integrations.tsx b/app/screens/onboarding/illustrations/integrations.tsx index 641150b53..bbae61885 100644 --- a/app/screens/onboarding/illustrations/integrations.tsx +++ b/app/screens/onboarding/illustrations/integrations.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; +import * as React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import Svg, { Rect, @@ -15,11 +15,12 @@ import Svg, { Stop, } from 'react-native-svg'; -type SvgProps = { +type Props = { + theme: Theme; styles: StyleProp; }; -const IntegrationsSvg = ({styles}: SvgProps) => { +const IntegrationsSvg = ({theme, styles}: Props) => { return ( { /> { cy={142.286} rx={5.31027} ry={5.31027} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={36.1098} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={36.1098} height={2.12411} rx={1.06206} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={36.1098} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={50.9786} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={50.9786} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={50.9786} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={25.4893} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={25.4893} height={2.12411} rx={1.06206} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={25.4893} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={55.2268} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={55.2268} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={55.2268} height={2.12411} rx={1.06205} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.24} /> { width={25.8032} height={1.84308} rx={0.921542} - fill='#fff' + fill={theme.sidebarText} /> { width={6.14362} height={6.14362} rx={2} - fill='#fff' + fill={theme.sidebarText} /> { width={6.14362} height={6.14362} rx={2} - fill='#fff' + fill={theme.sidebarText} /> { width={93} height={181} rx={16} - fill='#D9D9D9' + fill='#fff' /> ; -}; +} -const TeamCommunicationSvg = ({styles}: SvgProps) => { +const TeamCommunicationSvg = ({theme, styles}: Props) => { return ( { cy={90.6649} rx={66.4115} ry={66.2887} - fill='#fff' + fill={theme.centerChannelBg} /> { cy={90.6649} rx={66.4115} ry={66.2887} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.16} /> { /> { /> { cy={150.952} rx={53.6174} ry={53.5182} - fill='#fff' + fill={theme.centerChannelBg} /> { cy={150.952} rx={53.6174} ry={53.5182} - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.16} /> { { /> { /> { /> { fillRule='evenodd' clipRule='evenodd' d='M161.915 54.431a1.5 1.5 0 011.626-1.362c4.754.418 12.243 3.2 19.554 7.49 6.326 3.711 12.707 8.66 17.203 14.432l2.18-9.876a1.501 1.501 0 012.93.647l-2.91 13.182a1.501 1.501 0 01-1.789 1.141l-13.182-2.91a1.5 1.5 0 11.647-2.93l9.339 2.063c-4.155-5.145-9.993-9.675-15.936-13.162-7.141-4.189-14.182-6.726-18.3-7.089a1.5 1.5 0 01-1.362-1.626zM102.839 185.763a1.5 1.5 0 01-1.439 1.559c-4.086.162-10.71-1.381-17.324-4.201-5.578-2.377-11.338-5.741-15.723-9.971l-.909 10.107a1.5 1.5 0 01-2.987-.268l1.209-13.446a1.5 1.5 0 011.628-1.36l13.446 1.209a1.501 1.501 0 01-.269 2.988l-9.463-.851c3.957 3.641 9.106 6.642 14.244 8.833 6.433 2.742 12.581 4.099 16.029 3.962a1.5 1.5 0 011.558 1.439z' - fill='#3F4350' + fill={theme.centerChannelColor} fillOpacity={0.32} /> - - - - - ); }; diff --git a/app/screens/onboarding/slides_data.tsx b/app/screens/onboarding/slides_data.tsx index b5d2eed8f..b75516a78 100644 --- a/app/screens/onboarding/slides_data.tsx +++ b/app/screens/onboarding/slides_data.tsx @@ -5,6 +5,7 @@ import React from 'react'; import {useIntl} from 'react-intl'; import {StyleSheet} from 'react-native'; +import {useTheme} from '@context/theme'; import {OnboardingItem} from '@typings/screens/onboarding'; import CallsSvg from './illustrations/calls'; @@ -24,10 +25,31 @@ const styles = StyleSheet.create({ const useSalidesData = () => { const intl = useIntl(); - const callsSvg = (); - const chatSvg = (); - const teamCommunicationSvg = (); - const integrationsSvg = (); + const theme = useTheme(); + const callsSvg = ( + + ); + const chatSvg = ( + + ); + const teamCommunicationSvg = ( + + ); + const integrationsSvg = ( + + ); const slidesData: OnboardingItem[] = [ { From 5b3d95c913138f68c7bc95f04893d9612a6371e0 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Fri, 18 Nov 2022 15:14:34 +0100 Subject: [PATCH 38/50] dark mode improvements, aligh icon vertically in next button --- app/screens/navigation.ts | 2 +- app/screens/onboarding/footer_buttons.tsx | 2 +- app/screens/onboarding/illustrations/calls.tsx | 2 +- app/screens/onboarding/illustrations/integrations.tsx | 2 +- app/screens/onboarding/illustrations/team_communication.tsx | 2 +- app/utils/navigation/index.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index b98575420..22484381c 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -148,7 +148,7 @@ Appearance.addChangeListener(() => { const theme = getThemeFromState(); const screens = NavigationStore.getAllNavigationComponents(); - if (screens.includes(Screens.SERVER)) { + if (screens.includes(Screens.SERVER) || screens.includes(Screens.ONBOARDING)) { for (const screen of screens) { if (appearanceControlledScreens.has(screen)) { Navigation.updateProps(screen, {theme}); diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index 1c89ee5bf..01390fee4 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -26,7 +26,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ color: theme.buttonColor, fontSize: 12, marginLeft: 5, - marginTop: 2, + marginTop: 4.5, }, nextButtonText: { flexDirection: 'row', diff --git a/app/screens/onboarding/illustrations/calls.tsx b/app/screens/onboarding/illustrations/calls.tsx index 1e0cfe727..183a9ffc7 100644 --- a/app/screens/onboarding/illustrations/calls.tsx +++ b/app/screens/onboarding/illustrations/calls.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import * as React from 'react'; +import React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import Svg, { Ellipse, diff --git a/app/screens/onboarding/illustrations/integrations.tsx b/app/screens/onboarding/illustrations/integrations.tsx index bbae61885..17fad9ac7 100644 --- a/app/screens/onboarding/illustrations/integrations.tsx +++ b/app/screens/onboarding/illustrations/integrations.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import * as React from 'react'; +import React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import Svg, { Rect, diff --git a/app/screens/onboarding/illustrations/team_communication.tsx b/app/screens/onboarding/illustrations/team_communication.tsx index b1439dbcb..d5ec426c7 100644 --- a/app/screens/onboarding/illustrations/team_communication.tsx +++ b/app/screens/onboarding/illustrations/team_communication.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import * as React from 'react'; +import React from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import Svg, { Ellipse, diff --git a/app/utils/navigation/index.ts b/app/utils/navigation/index.ts index c4f128310..0f7a0737c 100644 --- a/app/utils/navigation/index.ts +++ b/app/utils/navigation/index.ts @@ -7,7 +7,7 @@ import {Navigation, Options} from 'react-native-navigation'; import {Screens} from '@constants'; -export const appearanceControlledScreens = new Set([Screens.SERVER, Screens.LOGIN, Screens.FORGOT_PASSWORD, Screens.MFA, Screens.SSO]); +export const appearanceControlledScreens = new Set([Screens.ONBOARDING, Screens.SERVER, Screens.LOGIN, Screens.FORGOT_PASSWORD, Screens.MFA, Screens.SSO]); export function mergeNavigationOptions(componentId: string, options: Options) { Navigation.mergeOptions(componentId, options); From 002c2c95ebd3dcc348bde04a43b1b88200504c73 Mon Sep 17 00:00:00 2001 From: Matthew Birtch Date: Fri, 18 Nov 2022 09:32:10 -0500 Subject: [PATCH 39/50] fixed issue with masks in chat illustration --- app/screens/onboarding/illustrations/chat.tsx | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/app/screens/onboarding/illustrations/chat.tsx b/app/screens/onboarding/illustrations/chat.tsx index 1ae3f9630..5ded6fa87 100644 --- a/app/screens/onboarding/illustrations/chat.tsx +++ b/app/screens/onboarding/illustrations/chat.tsx @@ -6,7 +6,6 @@ import {StyleProp, ViewStyle} from 'react-native'; import Svg, { G, Path, - Mask, Ellipse, } from 'react-native-svg'; @@ -33,22 +32,6 @@ const ChatSvg = ({theme, styles}: Props) => { d='M159.269 86.21c-4.852 8.63-44.21 25.658-34.827 69.563 6.642 31.104 21.796 43.514 18.021 48.707-2.799 3.792-5.001 8.411 1.652 2.953 8.03-6.561 10.887-2.206 5.679-18.017-4.967-15.052-9.338-55.153 15.474-69.378 24.813-14.225 32.912-21.338 20.649-38.803-4.486-6.412-26.648 4.975-26.648 4.975z' fill='#AD831F' /> - - - - - - { d='M202.768 90.703c2.294 31.805 3.085 74.469 31.718 81.581 28.632 7.113 27.531 3.597 27.531 14.26s-2.214 9.088-3.522 2.298c-1.675-8.779-32.957 6.894-54.179-10.617-21.222-17.512-30.181-51.362-33.416-86.086-2.271-24.59 31.868-1.436 31.868-1.436z' fill='#AD831F' /> - - - - - - { fill={theme.centerChannelColor} fillOpacity={0.08} /> - - - - - - { d='M185.962 32.608s-2.776 1.655-1.525 6.584c-3.373-5.033 1.055-11.938 1.055-11.938a1.156 1.156 0 00.941.49 1.147 1.147 0 00.94-.49c3.35-3.448.952-4.597 0-4.195-1.194.4-2.429.662-3.682.782-1.95.322-3.854-2.838-3.854-2.838l.493 1.436a5.545 5.545 0 01-2.81-4.596c1.778-5.688 12.733-6.699 16.816 1.884 4.084 8.584 0 23.395 11.931 21.671-23.7 10.514-20.305-8.79-20.305-8.79z' fill='#4A2407' /> - - - - - - Date: Wed, 23 Nov 2022 18:36:17 +0100 Subject: [PATCH 40/50] fix dark theme dynamic change --- app/screens/onboarding/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 3255d8b5c..bf4287f41 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -81,7 +81,7 @@ const Onboarding = ({ key={`key-${index.toString()}`} /> ); - }, []); + }, [theme]); const scrollHandler = useAnimatedScrollHandler({ onScroll: (event) => { From 0aabcb9918a4acf123a0c8b7fd6d1e315d28a0f3 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Thu, 24 Nov 2022 09:47:12 +0100 Subject: [PATCH 41/50] animate onboarding screen appear from screen to onboarding by clicking back button --- app/screens/onboarding/index.tsx | 51 +++++++++++++++++++++++++------- app/screens/onboarding/slide.tsx | 15 +++++----- app/screens/server/index.tsx | 32 ++++++++++---------- 3 files changed, 64 insertions(+), 34 deletions(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index bf4287f41..7fe18e8e2 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -1,9 +1,10 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback} from 'react'; -import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet} from 'react-native'; -import Animated, {useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated'; +import React, {useCallback, useEffect} from 'react'; +import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet, Platform} from 'react-native'; +import {Navigation} from 'react-native-navigation'; +import Animated, {useAnimatedRef, useAnimatedScrollHandler, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; import {storeOnboardingViewedValue} from '@actions/app/global'; import {Screens} from '@app/constants'; @@ -36,16 +37,22 @@ const styles = StyleSheet.create({ }, }); +const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView); + const Onboarding = ({ theme, }: OnboardingProps) => { const {width} = useWindowDimensions(); const {slidesData} = useSlidesData(); - const lastSlideIndex = slidesData.length - 1; + const LAST_SLIDE_INDEX = slidesData.length - 1; + const dimensions = useWindowDimensions(); const slidesRef = useAnimatedRef(); const scrollX = useSharedValue(0); + // used to smothly animate the whole onboarding screen during the appear event scenario (from server screen back to onboarding screen) + const translateX = useSharedValue(dimensions.width); + const currentIndex = useDerivedValue(() => Math.round(scrollX.value / width)); const moveToSlide = useCallback((slideIndexToMove: number) => { @@ -57,9 +64,9 @@ const Onboarding = ({ const nextSlide = useCallback(() => { const nextSlideIndex = currentIndex.value + 1; - if (slidesRef.current && currentIndex.value < lastSlideIndex) { + if (slidesRef.current && currentIndex.value < LAST_SLIDE_INDEX) { moveToSlide(nextSlideIndex); - } else if (slidesRef.current && currentIndex.value === lastSlideIndex) { + } else if (slidesRef.current && currentIndex.value === LAST_SLIDE_INDEX) { signInHandler(); } }, [currentIndex.value, slidesRef.current, moveToSlide]); @@ -68,7 +75,7 @@ const Onboarding = ({ // mark the onboarding as already viewed storeOnboardingViewedValue(); - goToScreen(Screens.SERVER, '', {theme}, loginAnimationOptions()); + goToScreen(Screens.SERVER, '', {animated: true, theme}, loginAnimationOptions()); }, []); const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { @@ -79,6 +86,7 @@ const Onboarding = ({ scrollX={scrollX} index={index} key={`key-${index.toString()}`} + lastSlideIndex={LAST_SLIDE_INDEX} /> ); }, [theme]); @@ -89,15 +97,36 @@ const Onboarding = ({ }, }); + useEffect(() => { + const listener = { + componentDidAppear: () => { + translateX.value = 0; + }, + componentDidDisappear: () => { + translateX.value = -dimensions.width; + }, + }; + const unsubscribe = Navigation.events().registerComponentListener(listener, Screens.ONBOARDING); + + return () => unsubscribe.remove(); + }, [dimensions]); + + const transform = useAnimatedStyle(() => { + const duration = Platform.OS === 'android' ? 250 : 350; + return { + transform: [{translateX: withTiming(translateX.value, {duration})}], + }; + }, []); + return ( - - + ); }; diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 9f5a96408..5869883fc 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -14,6 +14,7 @@ type Props = { theme: Theme; scrollX: Animated.SharedValue; index: number; + lastSlideIndex: number; }; const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ @@ -57,10 +58,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ })); const FIRST_SLIDE = 0; -const LAST_SLIDE = 3; -const FIRST_LOAD_ELEMENTS_POSITION = 400; -const SlideItem = ({theme, item, scrollX, index}: Props) => { +const SlideItem = ({theme, item, scrollX, index, lastSlideIndex}: Props) => { const {width} = useWindowDimensions(); const styles = getStyleSheet(theme); @@ -69,9 +68,9 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { */ const [firstLoad, setFirstLoad] = useState(true); - const initialImagePosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION); - const initialTitlePosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION); - const initialDescriptionPosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION); + const initialImagePosition = useSharedValue(width); + const initialTitlePosition = useSharedValue(width); + const initialDescriptionPosition = useSharedValue(width); const initialElementsOpacity = useSharedValue(0); @@ -189,7 +188,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { style={[ styles.title, (index === FIRST_SLIDE ? styles.fontFirstTitle : styles.fontTitle), - (index === LAST_SLIDE ? styles.widthLastSlide : undefined), + (index === lastSlideIndex ? styles.widthLastSlide : undefined), translateTitle, opacity, translateFirstTitleOnLoad, @@ -204,7 +203,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => { translateDescription, opacity, (index === FIRST_SLIDE && firstLoad ? styles.firstSlideInitialPosition : undefined), - (index === LAST_SLIDE ? styles.widthLastSlide : undefined), + (index === lastSlideIndex ? styles.widthLastSlide : undefined), translateFirstDescriptionOnLoad, ]} > diff --git a/app/screens/server/index.tsx b/app/screens/server/index.tsx index ec6a44c99..943debf86 100644 --- a/app/screens/server/index.tsx +++ b/app/screens/server/index.tsx @@ -36,6 +36,7 @@ import ServerHeader from './header'; import type {DeepLinkWithData, LaunchProps} from '@typings/launch'; interface ServerProps extends LaunchProps { + animated?: boolean; closeButtonId?: string; componentId: string; theme: Theme; @@ -48,9 +49,24 @@ const defaultServerUrlMessage = { defaultMessage: 'Please enter a valid server URL', }; +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + appInfo: { + color: changeOpacity(theme.centerChannelColor, 0.56), + }, + flex: { + flex: 1, + }, + scrollContainer: { + alignItems: 'center', + height: '100%', + justifyContent: 'center', + }, +})); + const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView); const Server = ({ + animated, closeButtonId, componentId, displayName: defaultDisplayName, @@ -63,7 +79,7 @@ const Server = ({ const intl = useIntl(); const managedConfig = useManagedConfig(); const dimensions = useWindowDimensions(); - const translateX = useSharedValue(0); + const translateX = useSharedValue(animated ? dimensions.width : 0); const keyboardAwareRef = useRef(null); const [connecting, setConnecting] = useState(false); const [displayName, setDisplayName] = useState(''); @@ -362,18 +378,4 @@ const Server = ({ ); }; -const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ - appInfo: { - color: changeOpacity(theme.centerChannelColor, 0.56), - }, - flex: { - flex: 1, - }, - scrollContainer: { - alignItems: 'center', - height: '100%', - justifyContent: 'center', - }, -})); - export default Server; From 32e2247b460d2df3829309608618101c86230abb Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Fri, 25 Nov 2022 11:17:22 +0100 Subject: [PATCH 42/50] show onboarding again only on all servers removal --- app/managers/session_manager.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/managers/session_manager.ts b/app/managers/session_manager.ts index 071578539..309c80bd2 100644 --- a/app/managers/session_manager.ts +++ b/app/managers/session_manager.ts @@ -16,7 +16,7 @@ import PushNotifications from '@init/push_notifications'; import * as analytics from '@managers/analytics'; import NetworkManager from '@managers/network_manager'; import WebsocketManager from '@managers/websocket_manager'; -import {queryServerName} from '@queries/app/servers'; +import {queryAllServers, queryServerName} from '@queries/app/servers'; import {getCurrentUser} from '@queries/servers/user'; import {getThemeFromState} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; @@ -173,13 +173,19 @@ class SessionManager { EphemeralStore.theme = undefined; launchType = Launch.Normal; - // set the onboardingViewed value to false so the launch will show the onboarding screen after all servers were removed - storeOnboardingViewedValue(false); - if (activeServerDisplayName) { displayName = activeServerDisplayName; } } + + // set the onboardingViewed value to false so the launch will show the onboarding screen after all servers were removed + if (DatabaseManager.appDatabase) { + const servers = await queryAllServers(DatabaseManager.appDatabase.database); + if (!servers.length) { + storeOnboardingViewedValue(false); + } + } + relaunchApp({launchType, serverUrl, displayName}, true); } }; From 812bb61e3b1d4f678d954bd2ab944186aca9cbe5 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Fri, 25 Nov 2022 16:00:40 +0100 Subject: [PATCH 43/50] fix blank screen animation --- app/init/launch.ts | 10 ++++++---- app/screens/navigation.ts | 13 +++++++++++++ app/screens/onboarding/index.tsx | 16 +++++++--------- app/screens/server/header.tsx | 2 +- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/app/init/launch.ts b/app/init/launch.ts index a2df188d4..8ac7c9cc8 100644 --- a/app/init/launch.ts +++ b/app/init/launch.ts @@ -135,12 +135,14 @@ const launchApp = async (props: LaunchProps, resetNavigation = true) => { } } - const onboardingViewed = LocalConfig.ShowOnboarding ? await getOnboardingViewed() : false; + const onboardingViewed = LocalConfig.ShowOnboarding && await getOnboardingViewed(); - if (onboardingViewed) { - return launchToServer(props, resetNavigation); + // if the config value is set and the onboarding has not been seeing yet, show the onboarding + if (LocalConfig.ShowOnboarding && !onboardingViewed) { + return resetToOnboarding(props); } - return resetToOnboarding(props); + + return launchToServer(props, resetNavigation); }; const launchToHome = async (props: LaunchProps) => { diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 22484381c..81e7159c8 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -124,6 +124,19 @@ export const bottomSheetModalOptions = (theme: Theme, closeButtonId?: string) => // This locks phones to portrait for all screens while keeps // all orientations available for Tablets. Navigation.setDefaultOptions({ + animations: { + setRoot: { + enter: { + waitForRender: true, + enabled: true, + alpha: { + from: 0, + to: 1, + duration: 300, + }, + }, + }, + }, layout: { orientation: Device.IS_TABLET ? undefined : ['portrait'], }, diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 7fe18e8e2..6bbfe57e7 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React, {useCallback, useEffect} from 'react'; -import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet, Platform} from 'react-native'; +import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet, Platform, NativeSyntheticEvent, NativeScrollEvent} from 'react-native'; import {Navigation} from 'react-native-navigation'; import Animated, {useAnimatedRef, useAnimatedScrollHandler, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; @@ -60,7 +60,7 @@ const Onboarding = ({ x: (slideIndexToMove * width), animated: true, }); - }, [currentIndex.value]); + }, []); const nextSlide = useCallback(() => { const nextSlideIndex = currentIndex.value + 1; @@ -91,11 +91,9 @@ const Onboarding = ({ ); }, [theme]); - const scrollHandler = useAnimatedScrollHandler({ - onScroll: (event) => { - scrollX.value = event.contentOffset.x; - }, - }); + const scrollHandler = (event: NativeSyntheticEvent) => { + scrollX.value = event.nativeEvent.contentOffset.x; + }; useEffect(() => { const listener = { @@ -128,7 +126,7 @@ const Onboarding = ({ style={[styles.scrollContainer, transform]} key={'onboarding_content'} > - { return renderSlide({item, index} as ListRenderItemInfo); })} - + ({ ...typography('Heading', 400, 'SemiBold'), }, connect: { - width: 270, + width: 320, letterSpacing: -1, color: theme.centerChannelColor, marginVertical: 12, From b152dd7b644d9d145fbb4930be95dc319128d8b7 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Fri, 25 Nov 2022 19:08:31 +0100 Subject: [PATCH 44/50] fix linter --- app/screens/onboarding/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 6bbfe57e7..5971b34ab 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -4,7 +4,7 @@ import React, {useCallback, useEffect} from 'react'; import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet, Platform, NativeSyntheticEvent, NativeScrollEvent} from 'react-native'; import {Navigation} from 'react-native-navigation'; -import Animated, {useAnimatedRef, useAnimatedScrollHandler, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {useAnimatedRef, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; import {storeOnboardingViewedValue} from '@actions/app/global'; import {Screens} from '@app/constants'; From ed743a541457564fc1216d2ce65df9391ad57275 Mon Sep 17 00:00:00 2001 From: Matthew Birtch Date: Fri, 25 Nov 2022 16:42:23 -0500 Subject: [PATCH 45/50] updated styles to accommadate for max-width on tablet and minor spacing adjustments --- app/screens/onboarding/footer_buttons.tsx | 6 +++++- app/screens/onboarding/illustrations/chat.tsx | 2 +- app/screens/onboarding/slide.tsx | 12 ++++++++---- app/screens/onboarding/slides_data.tsx | 1 + 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index 01390fee4..7125e002d 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -10,6 +10,8 @@ import FormattedText from '@components/formatted_text'; import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; import {makeStyleSheetFromTheme} from '@utils/theme'; +import {ONBOARDING_CONTENT_MAX_WIDTH} from '@screens/onboarding/slide'; + type Props = { theme: Theme; lastSlideIndex: number; @@ -48,6 +50,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const AnimatedButton = Animated.createAnimatedComponent(Pressable); const BUTTON_SIZE = 100; + const FooterButtons = ({ theme, nextSlideHandler, @@ -56,6 +59,7 @@ const FooterButtons = ({ scrollX, }: Props) => { const {width} = useWindowDimensions(); + const buttonWidth = Math.min(width * .8, ONBOARDING_CONTENT_MAX_WIDTH); const styles = getStyleSheet(theme); // keep in mind penultimate and ultimate slides to run buttons text/opacity/size animations @@ -68,7 +72,7 @@ const FooterButtons = ({ const interpolatedWidth = interpolate( scrollX.value, inputRange, - [BUTTON_SIZE, width * 0.8], + [BUTTON_SIZE, buttonWidth], Extrapolate.CLAMP, ); diff --git a/app/screens/onboarding/illustrations/chat.tsx b/app/screens/onboarding/illustrations/chat.tsx index 5ded6fa87..4126a79ae 100644 --- a/app/screens/onboarding/illustrations/chat.tsx +++ b/app/screens/onboarding/illustrations/chat.tsx @@ -75,7 +75,7 @@ const ChatSvg = ({theme, styles}: Props) => { /> ({ image: { justifyContent: 'center', maxHeight: 180, }, title: { - height: 100, color: theme.centerChannelColor, textAlign: 'center', paddingHorizontal: 20, + maxWidth: ONBOARDING_CONTENT_MAX_WIDTH, + marginBottom: 12, }, fontTitle: { marginTop: 32, @@ -34,7 +37,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ }, fontFirstTitle: { ...typography('Heading', 1200, 'SemiBold'), - paddingTop: 40, + paddingTop: 48, + letterSpacing: -1, }, widthLastSlide: { paddingHorizontal: 50, @@ -46,9 +50,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ description: { textAlign: 'center', paddingHorizontal: 20, - height: 80, color: changeOpacity(theme.centerChannelColor, 0.64), ...typography('Body', 200, 'Regular'), + maxWidth: ONBOARDING_CONTENT_MAX_WIDTH, }, itemContainer: { flex: 1, @@ -214,4 +218,4 @@ const SlideItem = ({theme, item, scrollX, index, lastSlideIndex}: Props) => { ); }; -export default React.memo(SlideItem); +export default React.memo(SlideItem); \ No newline at end of file diff --git a/app/screens/onboarding/slides_data.tsx b/app/screens/onboarding/slides_data.tsx index b75516a78..cfe43efef 100644 --- a/app/screens/onboarding/slides_data.tsx +++ b/app/screens/onboarding/slides_data.tsx @@ -20,6 +20,7 @@ const styles = StyleSheet.create({ }, lastSlideImage: { height: 250, + left: 8, }, }); From 3bea41041f15aaa65ebc127a5ec2b9423a7fd4b7 Mon Sep 17 00:00:00 2001 From: Matthew Birtch Date: Fri, 25 Nov 2022 16:44:19 -0500 Subject: [PATCH 46/50] updated styles to accommadate for max-width on tablet and minor spacing adjustments --- app/screens/onboarding/footer_buttons.tsx | 6 ++---- app/screens/onboarding/slide.tsx | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/screens/onboarding/footer_buttons.tsx b/app/screens/onboarding/footer_buttons.tsx index 7125e002d..cf01c3ce2 100644 --- a/app/screens/onboarding/footer_buttons.tsx +++ b/app/screens/onboarding/footer_buttons.tsx @@ -7,11 +7,10 @@ import Animated, {Extrapolate, interpolate, useAnimatedStyle} from 'react-native import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; +import {ONBOARDING_CONTENT_MAX_WIDTH} from '@screens/onboarding/slide'; import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; import {makeStyleSheetFromTheme} from '@utils/theme'; -import {ONBOARDING_CONTENT_MAX_WIDTH} from '@screens/onboarding/slide'; - type Props = { theme: Theme; lastSlideIndex: number; @@ -50,7 +49,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ const AnimatedButton = Animated.createAnimatedComponent(Pressable); const BUTTON_SIZE = 100; - const FooterButtons = ({ theme, nextSlideHandler, @@ -59,7 +57,7 @@ const FooterButtons = ({ scrollX, }: Props) => { const {width} = useWindowDimensions(); - const buttonWidth = Math.min(width * .8, ONBOARDING_CONTENT_MAX_WIDTH); + const buttonWidth = Math.min(width * 0.8, ONBOARDING_CONTENT_MAX_WIDTH); const styles = getStyleSheet(theme); // keep in mind penultimate and ultimate slides to run buttons text/opacity/size animations diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 97829a39c..6c22d8fe7 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -218,4 +218,4 @@ const SlideItem = ({theme, item, scrollX, index, lastSlideIndex}: Props) => { ); }; -export default React.memo(SlideItem); \ No newline at end of file +export default React.memo(SlideItem); From b7f65962653314e199d1b1c40e0504fbf315ad9e Mon Sep 17 00:00:00 2001 From: Matthew Birtch Date: Fri, 25 Nov 2022 17:02:23 -0500 Subject: [PATCH 47/50] added non-breaking hyphen to ensure real-time doesn't break --- app/screens/onboarding/slides_data.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/onboarding/slides_data.tsx b/app/screens/onboarding/slides_data.tsx index cfe43efef..574c13619 100644 --- a/app/screens/onboarding/slides_data.tsx +++ b/app/screens/onboarding/slides_data.tsx @@ -59,7 +59,7 @@ const useSalidesData = () => { image: chatSvg, }, { - title: intl.formatMessage({id: 'onboarding.realtime_collaboration', defaultMessage: 'Collaborate in real-time'}), + title: intl.formatMessage({id: 'onboarding.realtime_collaboration', defaultMessage: 'Collaborate in real‑time'}), description: intl.formatMessage({id: 'onboarding.realtime_collaboration_description', defaultMessage: 'Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.'}), image: teamCommunicationSvg, }, From a633472456f53a16de030ccd2354458a3139bccd Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 29 Nov 2022 09:45:51 +0100 Subject: [PATCH 48/50] add hyphen to translation file too --- assets/base/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 69849593e..9bbd6a3cf 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -659,7 +659,7 @@ "onboarding.calls_description": "When typing isn’t fast enough, switch from channel-based chat to secure audio calls with a single tap.", "onboarding.integrations": "Integrate with tools you love", "onboarding.integrations_description": "Go beyond chat with tightly-integrated product solutions matched to common development processes.", - "onboarding.realtime_collaboration": "Collaborate in real-time", + "onboarding.realtime_collaboration": "Collaborate in real‑time", "onboarding.realtime_collaboration_description": "Persistent channels, direct messaging, and file sharing works seamlessly so you can stay connected, wherever you are.", "onboarding.welcome": "Welcome", "onboaring.welcome_description": "Mattermost is an open source platform for developer collaboration. Secure, flexible, and integrated with your tools.", From f6d4fe2e75f975675a51223d829e1213bc973fc0 Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 29 Nov 2022 13:19:02 +0100 Subject: [PATCH 49/50] PR feedback refactor --- app/actions/app/global.ts | 14 ++--- app/managers/session_manager.ts | 2 +- app/screens/onboarding/index.tsx | 73 ++++++++++++++------------ app/screens/onboarding/paginator.tsx | 58 ++++++++++---------- app/screens/onboarding/slide.tsx | 11 ++-- app/screens/onboarding/slides_data.tsx | 4 +- 6 files changed, 80 insertions(+), 82 deletions(-) diff --git a/app/actions/app/global.ts b/app/actions/app/global.ts index 2e0df6375..e0c5840c3 100644 --- a/app/actions/app/global.ts +++ b/app/actions/app/global.ts @@ -1,9 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {logError} from '@app/utils/log'; import {GLOBAL_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; +import {logError} from '@utils/log'; export const storeGlobal = async (id: string, value: unknown, prepareRecordsOnly = false) => { try { @@ -13,6 +13,7 @@ export const storeGlobal = async (id: string, value: unknown, prepareRecordsOnly prepareRecordsOnly, }); } catch (error) { + logError('storeGlobal', error); return {error}; } }; @@ -26,16 +27,7 @@ export const storeMultiServerTutorial = async (prepareRecordsOnly = false) => { }; export const storeOnboardingViewedValue = async (value = true) => { - try { - const {operator} = DatabaseManager.getAppDatabaseAndOperator(); - return operator.handleGlobal({ - globals: [{id: GLOBAL_IDENTIFIERS.ONBOARDING, value}], - prepareRecordsOnly: false, - }); - } catch (error) { - logError('storeOnboardingViewedValue', error); - return {error}; - } + return storeGlobal(GLOBAL_IDENTIFIERS.ONBOARDING, value, false); }; export const storeProfileLongPressTutorial = async (prepareRecordsOnly = false) => { diff --git a/app/managers/session_manager.ts b/app/managers/session_manager.ts index 309c80bd2..d8d44495c 100644 --- a/app/managers/session_manager.ts +++ b/app/managers/session_manager.ts @@ -182,7 +182,7 @@ class SessionManager { if (DatabaseManager.appDatabase) { const servers = await queryAllServers(DatabaseManager.appDatabase.database); if (!servers.length) { - storeOnboardingViewedValue(false); + await storeOnboardingViewedValue(false); } } diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index 5971b34ab..b06919d47 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -1,16 +1,24 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect} from 'react'; -import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet, Platform, NativeSyntheticEvent, NativeScrollEvent} from 'react-native'; +import React, {useCallback, useEffect, useRef} from 'react'; +import { + View, + useWindowDimensions, + SafeAreaView, + ScrollView, + StyleSheet, + Platform, + NativeSyntheticEvent, + NativeScrollEvent, +} from 'react-native'; import {Navigation} from 'react-native-navigation'; -import Animated, {useAnimatedRef, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated'; import {storeOnboardingViewedValue} from '@actions/app/global'; -import {Screens} from '@app/constants'; +import {Screens} from '@constants'; import Background from '@screens/background'; import {goToScreen, loginAnimationOptions} from '@screens/navigation'; -import {OnboardingItem} from '@typings/screens/onboarding'; import FooterButtons from './footer_buttons'; import Paginator from './paginator'; @@ -45,13 +53,12 @@ const Onboarding = ({ const {width} = useWindowDimensions(); const {slidesData} = useSlidesData(); const LAST_SLIDE_INDEX = slidesData.length - 1; - const dimensions = useWindowDimensions(); - const slidesRef = useAnimatedRef(); + const slidesRef = useRef(null); const scrollX = useSharedValue(0); // used to smothly animate the whole onboarding screen during the appear event scenario (from server screen back to onboarding screen) - const translateX = useSharedValue(dimensions.width); + const translateX = useSharedValue(width); const currentIndex = useDerivedValue(() => Math.round(scrollX.value / width)); @@ -60,6 +67,13 @@ const Onboarding = ({ x: (slideIndexToMove * width), animated: true, }); + }, [width]); + + const signInHandler = useCallback(() => { + // mark the onboarding as already viewed + storeOnboardingViewedValue(); + + goToScreen(Screens.SERVER, '', {animated: true, theme}, loginAnimationOptions()); }, []); const nextSlide = useCallback(() => { @@ -69,31 +83,11 @@ const Onboarding = ({ } else if (slidesRef.current && currentIndex.value === LAST_SLIDE_INDEX) { signInHandler(); } - }, [currentIndex.value, slidesRef.current, moveToSlide]); + }, [currentIndex, slidesRef.current, moveToSlide, signInHandler]); - const signInHandler = useCallback(() => { - // mark the onboarding as already viewed - storeOnboardingViewedValue(); - - goToScreen(Screens.SERVER, '', {animated: true, theme}, loginAnimationOptions()); - }, []); - - const renderSlide = useCallback(({item, index}: ListRenderItemInfo) => { - return ( - - ); - }, [theme]); - - const scrollHandler = (event: NativeSyntheticEvent) => { + const scrollHandler = useCallback((event: NativeSyntheticEvent) => { scrollX.value = event.nativeEvent.contentOffset.x; - }; + }, []); useEffect(() => { const listener = { @@ -101,13 +95,13 @@ const Onboarding = ({ translateX.value = 0; }, componentDidDisappear: () => { - translateX.value = -dimensions.width; + translateX.value = -width; }, }; const unsubscribe = Navigation.events().registerComponentListener(listener, Screens.ONBOARDING); return () => unsubscribe.remove(); - }, [dimensions]); + }, [width]); const transform = useAnimatedStyle(() => { const duration = Platform.OS === 'android' ? 250 : 350; @@ -133,10 +127,19 @@ const Onboarding = ({ pagingEnabled={true} bounces={false} onScroll={scrollHandler} - ref={slidesRef as any} + ref={slidesRef} > {slidesData.map((item, index) => { - return renderSlide({item, index} as ListRenderItemInfo); + return ( + + ); })} ({ - dot: { +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + const commonStyle = { height: DOT_SIZE / 2, borderRadius: 5, backgroundColor: theme.buttonBg, marginHorizontal: DOT_SIZE / 2, width: DOT_SIZE / 2, opacity: 0.25, - }, - fixedDot: { - height: DOT_SIZE / 2, - borderRadius: 5, - backgroundColor: theme.buttonBg, - marginHorizontal: DOT_SIZE / 2, - width: DOT_SIZE / 2, - opacity: 0.25, - position: 'absolute', - }, - outerDot: { - height: DOT_SIZE, - borderRadius: DOT_SIZE / 2, - backgroundColor: theme.buttonBg, - marginHorizontal: 4, - marginTop: -4, - position: 'absolute', - width: DOT_SIZE, - opacity: 0.15, - }, - paginatorContainer: { - flexDirection: 'row', - height: 15, - justifyContent: 'space-between', - width: 120, - }, -})); + }; + return { + dot: { + ...commonStyle, + }, + fixedDot: { + ...commonStyle, + position: 'absolute', + }, + outerDot: { + height: DOT_SIZE, + borderRadius: DOT_SIZE / 2, + backgroundColor: theme.buttonBg, + marginHorizontal: 4, + marginTop: -4, + position: 'absolute', + width: DOT_SIZE, + opacity: 0.15, + }, + paginatorContainer: { + flexDirection: 'row', + height: 15, + justifyContent: 'space-between', + width: 120, + }, + }; +}); const Paginator = ({ theme, diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 6c22d8fe7..5e5c15edc 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -1,14 +1,15 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useEffect, useState} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {View, useWindowDimensions} from 'react-native'; import Animated, {Extrapolate, interpolate, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import {OnboardingItem} from '@typings/screens/onboarding'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; +import type {OnboardingItem} from '@typings/screens/onboarding'; + type Props = { item: OnboardingItem; theme: Theme; @@ -114,9 +115,11 @@ const SlideItem = ({theme, item, scrollX, index, lastSlideIndex}: Props) => { }], opacity: initialElementsOpacity.value, }; - });// end of code for animating first image load + }); - const inputRange = [(index - 1) * width, index * width, (index + 1) * width]; + // end of code for animating first image load + + const inputRange = useMemo(() => [(index - 1) * width, index * width, (index + 1) * width], [width, index]); const translateImage = useAnimatedStyle(() => { const translateImageInterpolate = interpolate( diff --git a/app/screens/onboarding/slides_data.tsx b/app/screens/onboarding/slides_data.tsx index 574c13619..217853577 100644 --- a/app/screens/onboarding/slides_data.tsx +++ b/app/screens/onboarding/slides_data.tsx @@ -24,7 +24,7 @@ const styles = StyleSheet.create({ }, }); -const useSalidesData = () => { +const useSlidesData = () => { const intl = useIntl(); const theme = useTheme(); const callsSvg = ( @@ -78,4 +78,4 @@ const useSalidesData = () => { return {slidesData}; }; -export default useSalidesData; +export default useSlidesData; From 7039f8b508524de54b7bd048e07572d60688e50a Mon Sep 17 00:00:00 2001 From: Pablo Velez Vidal Date: Tue, 29 Nov 2022 16:15:16 +0100 Subject: [PATCH 50/50] remove nnecessary slidesRef dependency --- app/screens/onboarding/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index b06919d47..821a61376 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -83,7 +83,7 @@ const Onboarding = ({ } else if (slidesRef.current && currentIndex.value === LAST_SLIDE_INDEX) { signInHandler(); } - }, [currentIndex, slidesRef.current, moveToSlide, signInHandler]); + }, [currentIndex, moveToSlide, signInHandler]); const scrollHandler = useCallback((event: NativeSyntheticEvent) => { scrollX.value = event.nativeEvent.contentOffset.x;