From b94790960fb98e00b98907a27209f00f4262b0b6 Mon Sep 17 00:00:00 2001 From: Avinash Lingaloo Date: Wed, 18 May 2022 11:37:11 +0400 Subject: [PATCH] MM-39711 Gekidou Settings Main Screen (#6281) * added chevron to menu item component * starting with the skeleton * starting with the skeleton * starting with the skeleton * starting with the skeleton * remove extra line * tested on tablets * some corrections * corrections as per review * updated snapshot * correction after review * removed unnecessary screen * refactored * updated the testIDs * Update Package.resolved * refactor * removed Mattermost as default server name --- .../__snapshots__/index.test.tsx.snap | 124 ++++++------ app/components/menu_item/index.tsx | 63 ++++-- app/constants/screens.ts | 24 +-- .../components/options/settings/index.tsx | 15 +- app/screens/index.tsx | 8 +- app/screens/settings/constant.ts | 37 ++++ app/screens/settings/index.tsx | 27 +++ app/screens/settings/setting_option.tsx | 46 +++++ app/screens/settings/settings.tsx | 187 ++++++++++++++++++ assets/base/i18n/en.json | 7 + package-lock.json | 1 + 11 files changed, 445 insertions(+), 94 deletions(-) create mode 100644 app/screens/settings/constant.ts create mode 100644 app/screens/settings/index.tsx create mode 100644 app/screens/settings/setting_option.tsx create mode 100644 app/screens/settings/settings.tsx diff --git a/app/components/menu_item/__snapshots__/index.test.tsx.snap b/app/components/menu_item/__snapshots__/index.test.tsx.snap index 7f502a137..39d2569e3 100644 --- a/app/components/menu_item/__snapshots__/index.test.tsx.snap +++ b/app/components/menu_item/__snapshots__/index.test.tsx.snap @@ -30,8 +30,7 @@ exports[`DrawerItem should match snapshot 1`] = ` @@ -39,45 +38,48 @@ exports[`DrawerItem should match snapshot 1`] = ` style={ Array [ Object { - "alignItems": "center", - "height": 50, - "justifyContent": "center", - "marginLeft": 5, - "width": 45, + "flexDirection": "row", + "minHeight": 50, }, undefined, ] } > - - - + > + + @@ -90,6 +92,7 @@ exports[`DrawerItem should match snapshot 1`] = ` "includeFontPadding": false, "textAlignVertical": "center", }, + undefined, Object { "color": "#d24b4e", }, @@ -97,21 +100,22 @@ exports[`DrawerItem should match snapshot 1`] = ` "textAlign": "center", "textAlignVertical": "center", }, + false, ] } > default message - + @@ -147,8 +151,7 @@ exports[`DrawerItem should match snapshot without separator and centered false 1 @@ -156,45 +159,48 @@ exports[`DrawerItem should match snapshot without separator and centered false 1 style={ Array [ Object { - "alignItems": "center", - "height": 50, - "justifyContent": "center", - "marginLeft": 5, - "width": 45, + "flexDirection": "row", + "minHeight": 50, }, undefined, ] } > - - - + > + + @@ -207,10 +213,12 @@ exports[`DrawerItem should match snapshot without separator and centered false 1 "includeFontPadding": false, "textAlignVertical": "center", }, + undefined, Object { "color": "#d24b4e", }, Object {}, + false, ] } > diff --git a/app/components/menu_item/index.tsx b/app/components/menu_item/index.tsx index 49aaab63a..239027bd0 100644 --- a/app/components/menu_item/index.tsx +++ b/app/components/menu_item/index.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React, {ReactNode} from 'react'; -import {Platform, StyleProp, View, ViewStyle} from 'react-native'; +import {Platform, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; @@ -11,19 +11,24 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; export const ITEM_HEIGHT = 50; -type MenuItemProps = { +export type MenuItemProps = { centered?: boolean; defaultMessage?: string; i18nId?: string; iconContainerStyle?: StyleProp; iconName?: string; + containerStyle?: StyleProp; isDestructor?: boolean; labelComponent?: ReactNode; leftComponent?: ReactNode; + messageValues?: Record; onPress: () => void; separator?: boolean; + showArrow?: boolean; testID: string; theme: Theme; + labelStyle?: StyleProp; + isLink?: boolean; }; const getStyleSheet = makeStyleSheetFromTheme((theme) => { @@ -43,14 +48,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { color: changeOpacity(theme.centerChannelColor, 0.64), fontSize: 24, }, - wrapper: { - flex: 1, - }, labelContainer: { flex: 1, justifyContent: 'center', - paddingTop: 14, - paddingBottom: 14, + paddingVertical: 14, }, centerLabel: { textAlign: 'center', @@ -66,6 +67,19 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { backgroundColor: changeOpacity(theme.centerChannelColor, 0.2), height: 1, }, + chevron: { + alignSelf: 'center', + color: changeOpacity(theme.centerChannelColor, 0.64), + fontSize: 24, + marginRight: 8, + }, + linkContainer: { + marginHorizontal: 15, + color: theme.linkColor, + }, + mainContainer: { + flexDirection: 'column', + }, }; }); @@ -76,11 +90,16 @@ const MenuItem = (props: MenuItemProps) => { i18nId, iconContainerStyle, iconName, + containerStyle, isDestructor = false, + isLink = false, labelComponent, + labelStyle, leftComponent, + messageValues, onPress, separator = true, + showArrow = false, testID, theme, } = props; @@ -92,11 +111,6 @@ const MenuItem = (props: MenuItemProps) => { destructor.color = theme.errorTextColor; } - let divider; - if (separator) { - divider = (); - } - let icon; if (leftComponent) { icon = leftComponent; @@ -119,9 +133,12 @@ const MenuItem = (props: MenuItemProps) => { defaultMessage={defaultMessage} style={[ style.label, + labelStyle, destructor, centered ? style.centerLabel : {}, + isLink && style.linkContainer, ]} + values={messageValues} /> ); } @@ -132,18 +149,24 @@ const MenuItem = (props: MenuItemProps) => { onPress={onPress} underlayColor={changeOpacity(theme.centerChannelColor, Platform.select({android: 0.1, ios: 0.3}) || 0.3)} > - - {icon && ( - - {icon} - - )} - + + + {icon && ( + + {icon} + + )} {label} - {divider} + {Boolean(showArrow) && ( + + )} + {Boolean(separator) && ()} ); diff --git a/app/constants/screens.ts b/app/constants/screens.ts index ef4478b45..c1e0d53b8 100644 --- a/app/constants/screens.ts +++ b/app/constants/screens.ts @@ -3,7 +3,6 @@ export const ABOUT = 'About'; export const ACCOUNT = 'Account'; -export const EMOJI_PICKER = 'EmojiPicker'; export const APPS_FORM = 'AppForm'; export const BOTTOM_SHEET = 'BottomSheet'; export const BROWSE_CHANNELS = 'BrowseChannels'; @@ -20,6 +19,7 @@ export const CUSTOM_STATUS_CLEAR_AFTER = 'CustomStatusClearAfter'; export const EDIT_POST = 'EditPost'; export const EDIT_PROFILE = 'EditProfile'; export const EDIT_SERVER = 'EditServer'; +export const EMOJI_PICKER = 'EmojiPicker'; export const FIND_CHANNELS = 'FindChannels'; export const FORGOT_PASSWORD = 'ForgotPassword'; export const GALLERY = 'Gallery'; @@ -32,40 +32,41 @@ export const LATEX = 'Latex'; export const LOGIN = 'Login'; export const MENTIONS = 'Mentions'; export const MFA = 'MFA'; -export const SELECT_TEAM = 'SelectTeam'; export const PERMALINK = 'Permalink'; export const POST_OPTIONS = 'PostOptions'; export const REACTIONS = 'Reactions'; export const SAVED_POSTS = 'SavedPosts'; export const SEARCH = 'Search'; +export const SELECT_TEAM = 'SelectTeam'; export const SERVER = 'Server'; +export const SETTINGS = 'Settings'; +export const SNACK_BAR = 'SnackBar'; export const SSO = 'SSO'; export const THREAD = 'Thread'; export const THREAD_FOLLOW_BUTTON = 'ThreadFollowButton'; export const THREAD_OPTIONS = 'ThreadOptions'; export const USER_PROFILE = 'UserProfile'; -export const SNACK_BAR = 'SnackBar'; export default { ABOUT, ACCOUNT, - EMOJI_PICKER, APPS_FORM, BOTTOM_SHEET, BROWSE_CHANNELS, CHANNEL, - CREATE_OR_EDIT_CHANNEL, CHANNEL_ADD_PEOPLE, - CHANNEL_EDIT, CHANNEL_DETAILS, + CHANNEL_EDIT, CODE, CREATE_DIRECT_MESSAGE, + CREATE_OR_EDIT_CHANNEL, CREATE_TEAM, - CUSTOM_STATUS_CLEAR_AFTER, CUSTOM_STATUS, + CUSTOM_STATUS_CLEAR_AFTER, EDIT_POST, EDIT_PROFILE, EDIT_SERVER, + EMOJI_PICKER, FIND_CHANNELS, FORGOT_PASSWORD, GALLERY, @@ -78,19 +79,20 @@ export default { LOGIN, MENTIONS, MFA, - SELECT_TEAM, PERMALINK, POST_OPTIONS, REACTIONS, SAVED_POSTS, SEARCH, + SELECT_TEAM, SERVER, + SETTINGS, + SNACK_BAR, SSO, THREAD, THREAD_FOLLOW_BUTTON, THREAD_OPTIONS, USER_PROFILE, - SNACK_BAR, }; export const MODAL_SCREENS_WITHOUT_BACK = [ @@ -99,12 +101,12 @@ export const MODAL_SCREENS_WITHOUT_BACK = [ CREATE_DIRECT_MESSAGE, CREATE_TEAM, CUSTOM_STATUS, - EMOJI_PICKER, EDIT_POST, EDIT_PROFILE, EDIT_SERVER, - GALLERY, + EMOJI_PICKER, FIND_CHANNELS, + GALLERY, PERMALINK, REACTIONS, SAVED_POSTS, diff --git a/app/screens/home/account/components/options/settings/index.tsx b/app/screens/home/account/components/options/settings/index.tsx index 1df0aa0ff..9ebb67a30 100644 --- a/app/screens/home/account/components/options/settings/index.tsx +++ b/app/screens/home/account/components/options/settings/index.tsx @@ -2,10 +2,13 @@ // See LICENSE.txt for license information. import React, {useCallback} from 'react'; +import {useIntl} from 'react-intl'; import {TextStyle} from 'react-native'; import FormattedText from '@components/formatted_text'; import MenuItem from '@components/menu_item'; +import Screens from '@constants/screens'; +import {showModal} from '@screens/navigation'; import {preventDoubleTap} from '@utils/tap'; type Props = { @@ -15,8 +18,18 @@ type Props = { } const Settings = ({isTablet, style, theme}: Props) => { + const intl = useIntl(); + const openSettings = useCallback(preventDoubleTap(() => { - // TODO: Open Saved messages screen in either a screen or in line for tablets + if (isTablet) { + //todo: https://mattermost.atlassian.net/browse/MM-39711 + // eslint-disable-next-line no-console + console.log('Settings on tablets need to be figured out and implemented - @Avinash'); + } + showModal( + Screens.SETTINGS, + intl.formatMessage({id: 'mobile.screen.settings', defaultMessage: 'Settings'}), + ); }), [isTablet]); return ( diff --git a/app/screens/index.tsx b/app/screens/index.tsx index a47b21d28..5a28418e1 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -171,7 +171,6 @@ Navigation.setLazyComponentRegistrator((screenName) => { break; case Screens.SNACK_BAR: { const snackBarScreen = withServerDatabase(require('@screens/snack_bar').default); - Navigation.registerComponent(Screens.SNACK_BAR, () => Platform.select({ default: snackBarScreen, @@ -181,9 +180,10 @@ Navigation.setLazyComponentRegistrator((screenName) => { break; } case Screens.THREAD_OPTIONS: - screen = withServerDatabase( - require('@screens/thread_options').default, - ); + screen = withServerDatabase(require('@screens/thread_options').default); + break; + case Screens.SETTINGS: + screen = withServerDatabase(require('@screens/settings').default); break; } diff --git a/app/screens/settings/constant.ts b/app/screens/settings/constant.ts new file mode 100644 index 000000000..cc5ca275c --- /dev/null +++ b/app/screens/settings/constant.ts @@ -0,0 +1,37 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {t} from '@i18n'; + +export const SettingOptionConfig = { + notification: { + defaultMessage: 'Notifications', + i18nId: t('general_settings.notifications'), + iconName: 'bell-outline', + testID: 'general_settings.notifications', + }, + display: { + defaultMessage: 'Display', + i18nId: t('general_settings.display'), + iconName: 'layers-outline', + testID: 'general_settings.display', + }, + advanced_settings: { + defaultMessage: 'Advanced Settings', + i18nId: t('general_settings.advanced_settings'), + iconName: 'tune', + testID: 'general_settings.advanced', + }, + about: { + defaultMessage: 'About {appTitle}', + i18nId: t('general_settings.about'), + iconName: 'information-outline', + testID: 'general_settings.about', + }, + help: { + defaultMessage: 'Help', + i18nId: t('general_settings.help'), + testID: 'general_settings.help', + showArrow: false, + }, +}; diff --git a/app/screens/settings/index.tsx b/app/screens/settings/index.tsx new file mode 100644 index 000000000..dfca8eaff --- /dev/null +++ b/app/screens/settings/index.tsx @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; +import withObservables from '@nozbe/with-observables'; +import {of as of$} from 'rxjs'; +import {switchMap} from 'rxjs/operators'; + +import {observeConfig} from '@queries/servers/system'; +import {isValidUrl} from '@utils/url'; + +import Settings from './settings'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { + const config = observeConfig(database); + const siteName = config.pipe(switchMap((c) => of$(c?.SiteName))); + const showHelp = observeConfig(database).pipe(switchMap((c) => of$(c?.HelpLink ? isValidUrl(c.HelpLink) : false))); + + return { + showHelp, + siteName, + }; +}); + +export default withDatabase(enhanced(Settings)); diff --git a/app/screens/settings/setting_option.tsx b/app/screens/settings/setting_option.tsx new file mode 100644 index 000000000..57dbb788e --- /dev/null +++ b/app/screens/settings/setting_option.tsx @@ -0,0 +1,46 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Platform} from 'react-native'; + +import MenuItem, {MenuItemProps} from '@components/menu_item'; +import {useTheme} from '@context/theme'; +import {makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +import {SettingOptionConfig} from './constant'; + +type Props = { + type: 'notification' | 'display' | 'advanced_settings' | 'about' | 'help'; + onPress: () => void; +} & Omit; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + return { + menuLabel: { + color: theme.centerChannelColor, + ...typography('Body', 200), + }, + }; +}); + +const SettingOption = ({type, onPress, ...rest}: Props) => { + const theme = useTheme(); + const styles = getStyleSheet(theme); + const optionConfig = SettingOptionConfig[type]; + const options = {...optionConfig, ...rest}; + + return ( + + ); +}; + +export default SettingOption; diff --git a/app/screens/settings/settings.tsx b/app/screens/settings/settings.tsx new file mode 100644 index 000000000..10c97b7ab --- /dev/null +++ b/app/screens/settings/settings.tsx @@ -0,0 +1,187 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback, useEffect, useMemo} from 'react'; +import {useIntl} from 'react-intl'; +import {Alert, BackHandler, Platform, ScrollView, View} from 'react-native'; +import {Navigation} from 'react-native-navigation'; +import {Edge, SafeAreaView} from 'react-native-safe-area-context'; + +import CompassIcon from '@components/compass_icon'; +import {Screens} from '@constants'; +import {useServerDisplayName} from '@context/server'; +import {useTheme} from '@context/theme'; +import {dismissModal, goToScreen, setButtons} from '@screens/navigation'; +import {preventDoubleTap} from '@utils/tap'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +import SettingOption from './setting_option'; + +const edges: Edge[] = ['left', 'right']; +const CLOSE_BUTTON_ID = 'close-settings'; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { + return { + container: { + flex: 1, + backgroundColor: theme.centerChannelBg, + }, + wrapper: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.06), + ...Platform.select({ + ios: { + flex: 1, + paddingTop: 35, + }, + }), + }, + divider: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), + height: 1, + }, + middleDivider: { + borderTopWidth: 1, + borderBottomWidth: 1, + borderColor: changeOpacity(theme.centerChannelColor, 0.1), + height: 35, + }, + group: { + backgroundColor: theme.centerChannelBg, + }, + innerContainerStyle: { + paddingLeft: 8, + }, + menuLabel: { + color: theme.centerChannelColor, + ...typography('Body', 200), + }, + }; +}); + +type SettingsProps = { + componentId: string; + siteName: string; + showHelp: boolean; +} + +//todo: handle display on tablet and Profile the whole feature - https://mattermost.atlassian.net/browse/MM-39711 + +const Settings = ({componentId, showHelp, siteName}: SettingsProps) => { + const theme = useTheme(); + const intl = useIntl(); + const styles = getStyleSheet(theme); + const serverDisplayName = useServerDisplayName(); + const serverName = siteName || serverDisplayName; + + const closeButton = useMemo(() => { + return { + id: CLOSE_BUTTON_ID, + icon: CompassIcon.getImageSourceSync('close', 24, theme.centerChannelColor), + testID: CLOSE_BUTTON_ID, + }; + }, [theme.centerChannelColor]); + + const close = useCallback(() => { + dismissModal({componentId}); + + return true; + }, []); + + useEffect(() => { + setButtons(componentId, { + leftButtons: [closeButton], + }); + }, []); + + useEffect(() => { + const backHandler = BackHandler.addEventListener('hardwareBackPress', close); + return () => { + backHandler.remove(); + }; + }, []); + + useEffect(() => { + const unsubscribe = Navigation.events().registerComponentListener({ + navigationButtonPressed: ({buttonId}: { buttonId: string }) => { + if (buttonId === CLOSE_BUTTON_ID) { + close(); + } + }, + }, componentId); + + return () => { + unsubscribe.remove(); + }; + }, []); + + const onPressHandler = () => { + return Alert.alert( + 'The functionality you are trying to use has not yet been implemented.', + ); + }; + + const goToAbout = preventDoubleTap(() => { + const screen = Screens.ABOUT; + const title = intl.formatMessage({id: 'about.title', defaultMessage: 'About {appTitle}'}, {appTitle: serverName}); + + goToScreen(screen, title); + }); + + let middleDividerStyle = styles.divider; + if (Platform.OS === 'ios') { + middleDividerStyle = styles.middleDivider; + } + + return ( + + + + + + + + + + + + {showHelp && + + } + + + + ); +}; + +export default Settings; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 5607c1faf..cdb174136 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -9,6 +9,7 @@ "about.teamEditionSt": "All your team communication in one place, instantly searchable and accessible anywhere.", "about.teamEditiont0": "Team Edition", "about.teamEditiont1": "Enterprise Edition", + "about.title": "About {appTitle}", "account.logout": "Log out", "account.logout_from": "Log out of {serverName}", "account.saved_messages": "Saved Messages", @@ -216,6 +217,11 @@ "gallery.save_failed": "Unable to save the file", "gallery.unsupported": "Preview isn't supported for this file type. Try downloading or sharing to open it in another app.", "gallery.video_saved": "Video saved", + "general_settings.about": "About {appTitle}", + "general_settings.advanced_settings": "Advanced Settings", + "general_settings.display": "Display", + "general_settings.help": "Help", + "general_settings.notifications": "Notifications", "get_post_link_modal.title": "Copy Link", "global_threads.allThreads": "All Your Threads", "global_threads.emptyThreads.message": "Any threads you are mentioned in or have participated in will show here along with any threads you have followed.", @@ -437,6 +443,7 @@ "mobile.routes.table": "Table", "mobile.routes.user_profile": "Profile", "mobile.screen.saved_posts": "Saved Messages", + "mobile.screen.settings": "Settings", "mobile.screen.your_profile": "Your Profile", "mobile.search.jump": "Jump to recent messages", "mobile.server_identifier.exists": "You are already connected to this server.", diff --git a/package-lock.json b/package-lock.json index 6afc1ca95..77184dc7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "mattermost-mobile", "version": "2.0.0", "hasInstallScript": true, "license": "Apache 2.0",