forked from Ivasoft/mattermost-mobile
MM-45221 - Gekidou Settings fixes - part 2 (#6491)
* modifying setting option * navigates to email screen * UI construction [in progress] * hooking up withObservables * email settings - need to save now * adding a bit of paddings * setting initial value * Update notification_email.tsx * UI Polish - main setting screen * UI Polish - Mention * UI Polish - Notification main screen * code clean up * code clean up * UI Polish Notification * UI Polish * code clean up * UI Polish - OOO * fix observable for email interval * fix ooo * fix ooo * added setting_row_label component * further clean up * UI Polish - Display - [ IN PROGRESS ] * UI Polish - Display - [ IN PROGRESS ] * UI Polish - Timezone Select [ IN PROGRESS ] * Update index.test.tsx.snap * Update app/screens/settings/notification_email/notification_email.tsx Co-authored-by: Daniel Espino García <larkox@gmail.com> * refactor after review * update option_item so that action can accept type React.Dispatch<React.SetStateAction * UI - Polish - Display Theme UI Polish - Display/Theme [ IN PROGRESS ] UI - Polish - Display Save button * UI - Polish - Display Clock UI - Polish - Display Clock * UI Polish - Display - Timezone UI Polish - Display Timezone added the save button UI Polish - Display/TZ UI Polish - Display Timezone minor refactoring code clean up code clean up * UI Polish - Adv Settings * UI Polish - Settings/About UI Polish - Settings/About * UI Polish - Radio Button UI Polish - Radio Button * UI Polish - Android Polishing [ IN PROGRESS ] * UI Polish - Timezone fix timezone fix timezone select fix timezone select ui Update index.tsx * UI Polish - Display/Theme UI Polish - Custom Theme - Checked Radio btn * Update en.json * tweaks to settings styles * further tweaks to spacing * Revert "Merge branch 'gekidou-settings-finale' of https://github.com/mattermost/mattermost-mobile into gekidou-settings-finale" This reverts commitf1fd26987e, reversing changes made to684ba6a19c. * added space before 'default' * Update index.test.tsx.snap * Revert "Revert "Merge branch 'gekidou-settings-finale' of https://github.com/mattermost/mattermost-mobile into gekidou-settings-finale"" This reverts commitce77127cb2. Co-authored-by: Daniel Espino García <larkox@gmail.com> Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>
This commit is contained in:
@@ -112,7 +112,7 @@ exports[`DrawerItem should match snapshot 1`] = `
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(63,67,80,0.2)",
|
||||
"backgroundColor": "rgba(63,67,80,0.12)",
|
||||
"height": 1,
|
||||
},
|
||||
undefined,
|
||||
|
||||
@@ -44,7 +44,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
includeFontPadding: false,
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.12),
|
||||
height: 1,
|
||||
},
|
||||
chevron: {
|
||||
|
||||
@@ -9,29 +9,15 @@ import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
export type OptionItemProps = {
|
||||
action?: (React.Dispatch<React.SetStateAction<string | boolean>>)|((value: string | boolean) => void) ;
|
||||
description?: string;
|
||||
inline?: boolean;
|
||||
destructive?: boolean;
|
||||
icon?: string;
|
||||
info?: string;
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
testID?: string;
|
||||
type: OptionType;
|
||||
value?: string;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
optionLabelTextStyle?: StyleProp<TextStyle>;
|
||||
optionDescriptionTextStyle?: StyleProp<TextStyle>;
|
||||
}
|
||||
import RadioItem, {RadioItemProps} from './radio_item';
|
||||
|
||||
const OptionType = {
|
||||
ARROW: 'arrow',
|
||||
DEFAULT: 'default',
|
||||
TOGGLE: 'toggle',
|
||||
SELECT: 'select',
|
||||
NONE: 'none',
|
||||
RADIO: 'radio',
|
||||
SELECT: 'select',
|
||||
TOGGLE: 'toggle',
|
||||
} as const;
|
||||
|
||||
type OptionType = typeof OptionType[keyof typeof OptionType];
|
||||
@@ -96,6 +82,23 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
};
|
||||
});
|
||||
|
||||
export type OptionItemProps = {
|
||||
action?: (React.Dispatch<React.SetStateAction<string | boolean>>)|((value: string | boolean) => void);
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
description?: string;
|
||||
destructive?: boolean;
|
||||
icon?: string;
|
||||
info?: string;
|
||||
inline?: boolean;
|
||||
label: string;
|
||||
optionDescriptionTextStyle?: StyleProp<TextStyle>;
|
||||
optionLabelTextStyle?: StyleProp<TextStyle>;
|
||||
radioItemProps?: Partial<RadioItemProps>;
|
||||
selected?: boolean;
|
||||
testID?: string;
|
||||
type: OptionType;
|
||||
value?: string;
|
||||
}
|
||||
const OptionItem = ({
|
||||
action,
|
||||
containerStyle,
|
||||
@@ -107,6 +110,7 @@ const OptionItem = ({
|
||||
label,
|
||||
optionDescriptionTextStyle,
|
||||
optionLabelTextStyle,
|
||||
radioItemProps,
|
||||
selected,
|
||||
testID = 'optionItem',
|
||||
type,
|
||||
@@ -136,6 +140,7 @@ const OptionItem = ({
|
||||
}, [destructive, styles, isInLine]);
|
||||
|
||||
let actionComponent;
|
||||
let radioComponent;
|
||||
if (type === OptionType.SELECT && selected) {
|
||||
actionComponent = (
|
||||
<CompassIcon
|
||||
@@ -145,6 +150,13 @@ const OptionItem = ({
|
||||
testID={`${testID}.selected`}
|
||||
/>
|
||||
);
|
||||
} else if (type === OptionType.RADIO) {
|
||||
radioComponent = (
|
||||
<RadioItem
|
||||
selected={Boolean(selected)}
|
||||
{...radioItemProps}
|
||||
/>
|
||||
);
|
||||
} else if (type === OptionType.TOGGLE) {
|
||||
const trackColor = Platform.select({
|
||||
ios: {true: theme.buttonBg, false: changeOpacity(theme.centerChannelColor, 0.16)},
|
||||
@@ -192,6 +204,7 @@ const OptionItem = ({
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{type === OptionType.RADIO && radioComponent}
|
||||
<View style={labelStyle}>
|
||||
<Text
|
||||
style={[optionLabelTextStyle, labelTextStyle]}
|
||||
@@ -224,7 +237,7 @@ const OptionItem = ({
|
||||
</View>
|
||||
);
|
||||
|
||||
if (type === OptionType.DEFAULT || type === OptionType.SELECT || type === OptionType.ARROW) {
|
||||
if (type === OptionType.DEFAULT || type === OptionType.SELECT || type === OptionType.ARROW || type === OptionType.RADIO) {
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress}>
|
||||
{component}
|
||||
|
||||
69
app/components/option_item/radio_item.tsx
Normal file
69
app/components/option_item/radio_item.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const RADIO_SIZE = 24;
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
ring: {
|
||||
height: RADIO_SIZE,
|
||||
width: RADIO_SIZE,
|
||||
borderRadius: RADIO_SIZE / 2,
|
||||
marginRight: 16,
|
||||
borderWidth: 2,
|
||||
borderColor: theme.buttonBg,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
inActive: {
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
},
|
||||
center: {
|
||||
height: RADIO_SIZE / 2,
|
||||
width: RADIO_SIZE / 2,
|
||||
borderRadius: RADIO_SIZE / 2,
|
||||
backgroundColor: theme.buttonBg,
|
||||
},
|
||||
checkedBodyContainer: {
|
||||
backgroundColor: theme.buttonBg,
|
||||
},
|
||||
};
|
||||
});
|
||||
export type RadioItemProps = {
|
||||
selected: boolean;
|
||||
checkedBody?: boolean;
|
||||
}
|
||||
const RadioItem = ({selected, checkedBody}: RadioItemProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const getBody = useCallback(() => {
|
||||
if (checkedBody) {
|
||||
return (
|
||||
<View style={styles.checkedBodyContainer}>
|
||||
<CompassIcon
|
||||
color={theme.buttonColor}
|
||||
name='check'
|
||||
size={RADIO_SIZE / 1.5}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (<View style={styles.center}/>);
|
||||
}, [checkedBody]);
|
||||
|
||||
return (
|
||||
<View style={[styles.ring, !selected && styles.inActive]}>
|
||||
{selected && getBody()}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default RadioItem;
|
||||
@@ -1,345 +0,0 @@
|
||||
// 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 React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Alert, ScrollView, Text, View} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import Config from '@assets/config.json';
|
||||
import AppVersion from '@components/app_version';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import AboutLinks from '@constants/about_links';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {observeConfig, observeLicense} from '@queries/servers/system';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {tryOpenURL} from '@utils/url';
|
||||
|
||||
import LearnMore from './learn_more';
|
||||
import ServerVersion from './server_version';
|
||||
import Subtitle from './subtitle';
|
||||
import Title from './title';
|
||||
import TosPrivacyContainer from './tos_privacy';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const MATTERMOST_BUNDLE_IDS = ['com.mattermost.rnbeta', 'com.mattermost.rn'];
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
},
|
||||
scrollViewContent: {
|
||||
paddingBottom: 30,
|
||||
},
|
||||
logoContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
height: 200,
|
||||
paddingVertical: 40,
|
||||
},
|
||||
infoContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
marginBottom: 20,
|
||||
},
|
||||
title: {
|
||||
fontSize: 22,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
subtitle: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 19,
|
||||
marginBottom: 15,
|
||||
},
|
||||
info: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 16,
|
||||
lineHeight: 19,
|
||||
},
|
||||
licenseContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
marginTop: 20,
|
||||
},
|
||||
noticeContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
noticeLink: {
|
||||
color: theme.linkColor,
|
||||
fontSize: 11,
|
||||
lineHeight: 13,
|
||||
},
|
||||
hashContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
footerGroup: {
|
||||
flex: 1,
|
||||
},
|
||||
footerTitleText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 11,
|
||||
fontFamily: 'OpenSans-SemiBold',
|
||||
lineHeight: 13,
|
||||
},
|
||||
footerText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 11,
|
||||
lineHeight: 13,
|
||||
marginBottom: 10,
|
||||
},
|
||||
copyrightText: {
|
||||
marginBottom: 0,
|
||||
},
|
||||
tosPrivacyContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
marginBottom: 10,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type ConnectedAboutProps = {
|
||||
config: ClientConfig;
|
||||
license: ClientLicense;
|
||||
}
|
||||
|
||||
const ConnectedAbout = ({config, license}: ConnectedAboutProps) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const openURL = useCallback((url: string) => {
|
||||
const onError = () => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.link.error.title',
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.link.error.text',
|
||||
defaultMessage: 'Unable to open the link.',
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
tryOpenURL(url, onError);
|
||||
}, []);
|
||||
|
||||
const handleAboutTeam = useCallback(preventDoubleTap(() => {
|
||||
return openURL(Config.AboutTeamURL);
|
||||
}), []);
|
||||
|
||||
const handleAboutEnterprise = useCallback(preventDoubleTap(() => {
|
||||
return openURL(Config.AboutEnterpriseURL);
|
||||
}), []);
|
||||
|
||||
const handlePlatformNotice = useCallback(preventDoubleTap(() => {
|
||||
return openURL(Config.PlatformNoticeURL);
|
||||
}), []);
|
||||
|
||||
const handleMobileNotice = useCallback(preventDoubleTap(() => {
|
||||
return openURL(Config.MobileNoticeURL);
|
||||
}), []);
|
||||
|
||||
const handleTermsOfService = useCallback(preventDoubleTap(() => {
|
||||
return openURL(AboutLinks.TERMS_OF_SERVICE);
|
||||
}), []);
|
||||
|
||||
const handlePrivacyPolicy = useCallback(preventDoubleTap(() => {
|
||||
return openURL(AboutLinks.PRIVACY_POLICY);
|
||||
}), []);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={['left', 'right']}
|
||||
style={style.container}
|
||||
testID='about.screen'
|
||||
>
|
||||
<ScrollView
|
||||
style={style.scrollView}
|
||||
contentContainerStyle={style.scrollViewContent}
|
||||
testID='about.scroll_view'
|
||||
>
|
||||
<View style={style.logoContainer}>
|
||||
<CompassIcon
|
||||
name='mattermost'
|
||||
color={theme.centerChannelColor}
|
||||
size={120}
|
||||
testID='about.logo'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.infoContainer}>
|
||||
<View style={style.titleContainer}>
|
||||
<Text
|
||||
style={style.title}
|
||||
testID='about.site_name'
|
||||
>
|
||||
{`${config.SiteName} `}
|
||||
</Text>
|
||||
<Title
|
||||
config={config}
|
||||
license={license}
|
||||
/>
|
||||
</View>
|
||||
<Subtitle config={config}/>
|
||||
<AppVersion
|
||||
isWrapped={false}
|
||||
textStyle={style.info}
|
||||
/>
|
||||
<ServerVersion config={config}/>
|
||||
<FormattedText
|
||||
id={t('mobile.about.database')}
|
||||
defaultMessage='Database: {type}'
|
||||
style={style.info}
|
||||
values={{
|
||||
type: config.SQLDriverName,
|
||||
}}
|
||||
testID='about.database'
|
||||
/>
|
||||
{license.IsLicensed === 'true' && (
|
||||
<View style={style.licenseContainer}>
|
||||
<FormattedText
|
||||
id={t('mobile.about.licensed')}
|
||||
defaultMessage='Licensed to: {company}'
|
||||
style={style.info}
|
||||
values={{
|
||||
company: license.Company,
|
||||
}}
|
||||
testID='about.licensee'
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<LearnMore
|
||||
config={config}
|
||||
onHandleAboutEnterprise={handleAboutEnterprise}
|
||||
onHandleAboutTeam={handleAboutTeam}
|
||||
/>
|
||||
{!MATTERMOST_BUNDLE_IDS.includes(DeviceInfo.getBundleId()) &&
|
||||
<FormattedText
|
||||
id={t('mobile.about.powered_by')}
|
||||
defaultMessage='{site} is powered by Mattermost'
|
||||
style={style.footerText}
|
||||
values={{
|
||||
site: config.SiteName,
|
||||
}}
|
||||
testID='about.powered_by'
|
||||
/>
|
||||
}
|
||||
<FormattedText
|
||||
id={t('mobile.about.copyright')}
|
||||
defaultMessage='Copyright 2015-{currentYear} Mattermost, Inc. All rights reserved'
|
||||
style={[style.footerText, style.copyrightText]}
|
||||
values={{
|
||||
currentYear: new Date().getFullYear(),
|
||||
}}
|
||||
testID='about.copyright'
|
||||
/>
|
||||
<View style={style.tosPrivacyContainer}>
|
||||
<TosPrivacyContainer
|
||||
config={config}
|
||||
onPressTOS={handleTermsOfService}
|
||||
onPressPrivacyPolicy={handlePrivacyPolicy}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.noticeContainer}>
|
||||
<View style={style.footerGroup}>
|
||||
<FormattedText
|
||||
id={t('mobile.notice_text')}
|
||||
defaultMessage='Mattermost is made possible by the open source software used in our {platform} and {mobile}.'
|
||||
style={style.footerText}
|
||||
values={{
|
||||
platform: (
|
||||
<FormattedText
|
||||
id={t('mobile.notice_platform_link')}
|
||||
defaultMessage='server'
|
||||
style={style.noticeLink}
|
||||
onPress={handlePlatformNotice}
|
||||
/>
|
||||
),
|
||||
mobile: (
|
||||
<FormattedText
|
||||
id={t('mobile.notice_mobile_link')}
|
||||
defaultMessage='mobile apps'
|
||||
style={[style.noticeLink, {marginLeft: 5}]}
|
||||
onPress={handleMobileNotice}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
testID='about.notice_text'
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={style.hashContainer}>
|
||||
<View style={style.footerGroup}>
|
||||
<FormattedText
|
||||
id={t('about.hash')}
|
||||
defaultMessage='Build Hash:'
|
||||
style={style.footerTitleText}
|
||||
testID='about.build_hash.title'
|
||||
/>
|
||||
<Text
|
||||
style={style.footerText}
|
||||
testID='about.build_hash.value'
|
||||
>
|
||||
{config.BuildHash}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={style.footerGroup}>
|
||||
<FormattedText
|
||||
id={t('about.hashee')}
|
||||
defaultMessage='EE Build Hash:'
|
||||
style={style.footerTitleText}
|
||||
testID='about.build_hash_enterprise.title'
|
||||
/>
|
||||
<Text
|
||||
style={style.footerText}
|
||||
testID='about.build_hash_enterprise.value'
|
||||
>
|
||||
{config.BuildHashEnterprise}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={style.footerGroup}>
|
||||
<FormattedText
|
||||
id={t('about.date')}
|
||||
defaultMessage='Build Date:'
|
||||
style={style.footerTitleText}
|
||||
testID='about.build_date.title'
|
||||
/>
|
||||
<Text
|
||||
style={style.footerText}
|
||||
testID='about.build_date.value'
|
||||
>
|
||||
{config.BuildDate}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
config: observeConfig(database),
|
||||
license: observeLicense(database),
|
||||
}));
|
||||
|
||||
export default withDatabase(enhanced(ConnectedAbout));
|
||||
@@ -55,7 +55,7 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
||||
let extraStyles: StyleProp<ViewStyle>;
|
||||
switch (screenName) {
|
||||
case Screens.ABOUT:
|
||||
screen = withServerDatabase(require('@screens/about').default);
|
||||
screen = withServerDatabase(require('@screens/settings/about').default);
|
||||
break;
|
||||
case Screens.APPS_FORM:
|
||||
screen = withServerDatabase(require('@screens/apps_form').default);
|
||||
|
||||
325
app/screens/settings/about/about.tsx
Normal file
325
app/screens/settings/about/about.tsx
Normal file
@@ -0,0 +1,325 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Alert, Text, View} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import Config from '@assets/config.json';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import AboutLinks from '@constants/about_links';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import SettingContainer from '@screens/settings/setting_container';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {tryOpenURL} from '@utils/url';
|
||||
|
||||
import LearnMore from './learn_more';
|
||||
import Subtitle from './subtitle';
|
||||
import Title from './title';
|
||||
import TosPrivacyContainer from './tos_privacy';
|
||||
|
||||
const MATTERMOST_BUNDLE_IDS = ['com.mattermost.rnbeta', 'com.mattermost.rn'];
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
logoContainer: {
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 20,
|
||||
marginTop: 20,
|
||||
},
|
||||
lineStyles: {
|
||||
width: '100%',
|
||||
marginTop: 40,
|
||||
marginBottom: 24,
|
||||
},
|
||||
leftHeading: {
|
||||
...typography('Body', 200, 'SemiBold'),
|
||||
marginRight: 8,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rightHeading: {
|
||||
...typography('Body', 200, 'Regular'),
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
infoContainer: {
|
||||
flexDirection: 'column',
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
info: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
licenseContainer: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 20,
|
||||
},
|
||||
noticeContainer: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
noticeLink: {
|
||||
color: theme.linkColor,
|
||||
...typography('Body', 50, 'Regular'),
|
||||
},
|
||||
hashContainer: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
footerTitleText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 50, 'SemiBold'),
|
||||
},
|
||||
footerText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 50),
|
||||
marginBottom: 10,
|
||||
},
|
||||
copyrightText: {
|
||||
marginBottom: 0,
|
||||
},
|
||||
tosPrivacyContainer: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 10,
|
||||
},
|
||||
group: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type AboutProps = {
|
||||
config: ClientConfig;
|
||||
license: ClientLicense;
|
||||
}
|
||||
const About = ({config, license}: AboutProps) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const openURL = useCallback((url: string) => {
|
||||
const onError = () => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'settings.link.error.title',
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'settings.link.error.text',
|
||||
defaultMessage: 'Unable to open the link.',
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
tryOpenURL(url, onError);
|
||||
}, []);
|
||||
|
||||
const handleAboutTeam = useCallback(preventDoubleTap(() => {
|
||||
return openURL(Config.AboutTeamURL);
|
||||
}), []);
|
||||
|
||||
const handleAboutEnterprise = useCallback(preventDoubleTap(() => {
|
||||
return openURL(Config.AboutEnterpriseURL);
|
||||
}), []);
|
||||
|
||||
const handlePlatformNotice = useCallback(preventDoubleTap(() => {
|
||||
return openURL(Config.PlatformNoticeURL);
|
||||
}), []);
|
||||
|
||||
const handleMobileNotice = useCallback(preventDoubleTap(() => {
|
||||
return openURL(Config.MobileNoticeURL);
|
||||
}), []);
|
||||
|
||||
const handleTermsOfService = useCallback(preventDoubleTap(() => {
|
||||
return openURL(AboutLinks.TERMS_OF_SERVICE);
|
||||
}), []);
|
||||
|
||||
const handlePrivacyPolicy = useCallback(preventDoubleTap(() => {
|
||||
return openURL(AboutLinks.PRIVACY_POLICY);
|
||||
}), []);
|
||||
|
||||
const serverVersion = useMemo(() => {
|
||||
const buildNumber = config.BuildNumber;
|
||||
const version = config.Version;
|
||||
|
||||
let id = t('settings.about.serverVersion');
|
||||
let defaultMessage = '{version} (Build {number})';
|
||||
let values: {version: string; number?: string} = {
|
||||
version,
|
||||
number: buildNumber,
|
||||
};
|
||||
|
||||
if (buildNumber === version) {
|
||||
id = t('settings.about.serverVersionNoBuild');
|
||||
defaultMessage = '{version}';
|
||||
values = {
|
||||
version,
|
||||
number: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id, defaultMessage, values,
|
||||
};
|
||||
}, [config]);
|
||||
|
||||
return (
|
||||
<SettingContainer>
|
||||
<View style={styles.logoContainer}>
|
||||
<CompassIcon
|
||||
color={theme.centerChannelColor}
|
||||
name='mattermost'
|
||||
size={88}
|
||||
testID='about.logo'
|
||||
/>
|
||||
<Title
|
||||
config={config}
|
||||
license={license}
|
||||
/>
|
||||
<Subtitle config={config}/>
|
||||
<SettingSeparator lineStyles={styles.lineStyles}/>
|
||||
</View>
|
||||
<View style={styles.infoContainer}>
|
||||
<View style={styles.group}>
|
||||
<Text style={styles.leftHeading}>
|
||||
{intl.formatMessage({id: 'settings.about.version', defaultMessage: 'App Version:'})}
|
||||
</Text>
|
||||
<Text style={styles.rightHeading}>
|
||||
{intl.formatMessage({id: 'settings.about.build', defaultMessage: '{version} (Build {number})'},
|
||||
{version: DeviceInfo.getVersion(), number: DeviceInfo.getBuildNumber()})}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.group}>
|
||||
<Text style={styles.leftHeading}>
|
||||
{intl.formatMessage({id: 'settings.about.serverVersion', defaultMessage: 'Server Version:'})}
|
||||
</Text>
|
||||
<Text style={styles.rightHeading}>
|
||||
{intl.formatMessage({id: serverVersion.id, defaultMessage: serverVersion.defaultMessage}, serverVersion.values)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.group}>
|
||||
<Text style={styles.leftHeading}>
|
||||
{intl.formatMessage({id: 'settings.about.database', defaultMessage: 'Database:'})}
|
||||
</Text>
|
||||
<Text style={styles.rightHeading}>
|
||||
{intl.formatMessage({id: 'settings.about.database.value', defaultMessage: `${config.SQLDriverName}`})}
|
||||
</Text>
|
||||
</View>
|
||||
{license.IsLicensed === 'true' && (
|
||||
<View style={styles.licenseContainer}>
|
||||
<FormattedText
|
||||
defaultMessage='Licensed to: {company}'
|
||||
id={t('settings.about.licensed')}
|
||||
style={styles.info}
|
||||
testID='about.licensee'
|
||||
values={{company: license.Company}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<LearnMore
|
||||
config={config}
|
||||
onHandleAboutEnterprise={handleAboutEnterprise}
|
||||
onHandleAboutTeam={handleAboutTeam}
|
||||
/>
|
||||
{!MATTERMOST_BUNDLE_IDS.includes(DeviceInfo.getBundleId()) &&
|
||||
<FormattedText
|
||||
defaultMessage='{site} is powered by Mattermost'
|
||||
id={t('settings.about.powered_by')}
|
||||
style={styles.footerText}
|
||||
testID='about.powered_by'
|
||||
values={{site: config.SiteName}}
|
||||
/>
|
||||
}
|
||||
<FormattedText
|
||||
defaultMessage='Copyright 2015-{currentYear} Mattermost, Inc. All rights reserved'
|
||||
id={t('settings.about.copyright')}
|
||||
style={[styles.footerText, styles.copyrightText]}
|
||||
testID='about.copyright'
|
||||
values={{currentYear: new Date().getFullYear()}}
|
||||
/>
|
||||
<View style={styles.tosPrivacyContainer}>
|
||||
<TosPrivacyContainer
|
||||
config={config}
|
||||
onPressPrivacyPolicy={handlePrivacyPolicy}
|
||||
onPressTOS={handleTermsOfService}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.noticeContainer}>
|
||||
<FormattedText
|
||||
id={t('settings.notice_text')}
|
||||
defaultMessage='Mattermost is made possible by the open source software used in our {platform} and {mobile}.'
|
||||
style={styles.footerText}
|
||||
values={{
|
||||
platform: (
|
||||
<FormattedText
|
||||
defaultMessage='server'
|
||||
id={t('settings.notice_platform_link')}
|
||||
onPress={handlePlatformNotice}
|
||||
style={styles.noticeLink}
|
||||
/>
|
||||
),
|
||||
mobile: (
|
||||
<FormattedText
|
||||
defaultMessage='mobile apps'
|
||||
id={t('settings.notice_mobile_link')}
|
||||
onPress={handleMobileNotice}
|
||||
style={[styles.noticeLink, {marginLeft: 5}]}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
testID='about.notice_text'
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.hashContainer}>
|
||||
<View style={styles.footerGroup}>
|
||||
<FormattedText
|
||||
defaultMessage='Build Hash:'
|
||||
id={t('about.hash')}
|
||||
style={styles.footerTitleText}
|
||||
testID='about.build_hash.title'
|
||||
/>
|
||||
<Text
|
||||
style={styles.footerText}
|
||||
testID='about.build_hash.value'
|
||||
>
|
||||
{config.BuildHash}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.footerGroup}>
|
||||
<FormattedText
|
||||
defaultMessage='EE Build Hash:'
|
||||
id={t('about.hashee')}
|
||||
style={styles.footerTitleText}
|
||||
testID='about.build_hash_enterprise.title'
|
||||
/>
|
||||
<Text
|
||||
style={styles.footerText}
|
||||
testID='about.build_hash_enterprise.value'
|
||||
>
|
||||
{config.BuildHashEnterprise}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[styles.footerGroup, {marginBottom: 20}]}>
|
||||
<FormattedText
|
||||
defaultMessage='Build Date:'
|
||||
id={t('about.date')}
|
||||
style={styles.footerTitleText}
|
||||
testID='about.build_date.title'
|
||||
/>
|
||||
<Text
|
||||
style={styles.footerText}
|
||||
testID='about.build_date.value'
|
||||
>
|
||||
{config.BuildDate}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
18
app/screens/settings/about/index.tsx
Normal file
18
app/screens/settings/about/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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 {observeConfig, observeLicense} from '@queries/servers/system';
|
||||
|
||||
import About from './about';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
config: observeConfig(database),
|
||||
license: observeLicense(database),
|
||||
}));
|
||||
|
||||
export default withDatabase(enhanced(About));
|
||||
@@ -9,6 +9,25 @@ import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
learnContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
marginVertical: 20,
|
||||
},
|
||||
learn: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
learnLink: {
|
||||
color: theme.linkColor,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type LearnMoreProps = {
|
||||
config: ClientConfig;
|
||||
@@ -52,22 +71,4 @@ const LearnMore = ({config, onHandleAboutEnterprise, onHandleAboutTeam}: LearnMo
|
||||
);
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
learnContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
marginVertical: 20,
|
||||
},
|
||||
learn: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 16,
|
||||
},
|
||||
learnLink: {
|
||||
color: theme.linkColor,
|
||||
fontSize: 16,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default LearnMore;
|
||||
@@ -7,11 +7,22 @@ import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
subtitle: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||
...typography('Heading', 400, 'Regular'),
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 36,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type SubtitleProps = {
|
||||
config: ClientConfig;
|
||||
}
|
||||
|
||||
const Subtitle = ({config}: SubtitleProps) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
@@ -21,7 +32,7 @@ const Subtitle = ({config}: SubtitleProps) => {
|
||||
|
||||
if (config.BuildEnterpriseReady === 'true') {
|
||||
id = t('about.enterpriseEditionSt');
|
||||
defaultMessage = 'Modern communication from behind your firewall.';
|
||||
defaultMessage = 'Modern communication from\n behind your firewall.';
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -34,14 +45,4 @@ const Subtitle = ({config}: SubtitleProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
subtitle: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 19,
|
||||
marginBottom: 15,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default Subtitle;
|
||||
@@ -2,17 +2,34 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
title: {
|
||||
...typography('Heading', 800, 'SemiBold'),
|
||||
color: theme.centerChannelColor,
|
||||
paddingHorizontal: 36,
|
||||
},
|
||||
spacerTop: {
|
||||
marginTop: 8,
|
||||
},
|
||||
spacerBottom: {
|
||||
marginBottom: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type TitleProps = {
|
||||
config: ClientConfig;
|
||||
license: ClientLicense;
|
||||
}
|
||||
|
||||
const Title = ({config, license}: TitleProps) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
@@ -29,23 +46,24 @@ const Title = ({config, license}: TitleProps) => {
|
||||
defaultMessage = 'Enterprise Edition';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
style={style.title}
|
||||
testID='about.title'
|
||||
/>
|
||||
<>
|
||||
<Text
|
||||
style={[style.title, style.spacerTop]}
|
||||
testID='about.site_name'
|
||||
>
|
||||
{`${config.SiteName} `}
|
||||
</Text>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
style={[style.title, style.spacerBottom]}
|
||||
testID='about.title'
|
||||
/>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
title: {
|
||||
fontSize: 22,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default Title;
|
||||
@@ -8,6 +8,7 @@ import {TouchableOpacity} from 'react-native';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {deleteFileCache, getAllFilesInCachesDirectory, getFormattedFileSize} from '@utils/file';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
@@ -86,6 +87,7 @@ const AdvancedSettings = ({componentId}: AdvancedSettingsProps) => {
|
||||
label={intl.formatMessage({id: 'advanced_settings.delete_data', defaultMessage: 'Delete Documents & Data'})}
|
||||
type='none'
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
</TouchableOpacity>
|
||||
</SettingContainer>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import React, {useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
@@ -39,6 +40,12 @@ const TIMEZONE_FORMAT = [
|
||||
},
|
||||
];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
title: {
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
});
|
||||
|
||||
type DisplayProps = {
|
||||
currentUser: UserModel;
|
||||
hasMilitaryTimeFormat: boolean;
|
||||
@@ -76,9 +83,10 @@ const Display = ({currentUser, hasMilitaryTimeFormat, isThemeSwitchingEnabled, i
|
||||
<SettingItem
|
||||
optionName='theme'
|
||||
onPress={goToThemeSettings}
|
||||
rightComponent={
|
||||
rightComponent={Boolean(theme.type) &&
|
||||
<SettingRowLabel
|
||||
text={theme.type || ''}
|
||||
text={theme.type!}
|
||||
textStyle={styles.title}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {t} from '@i18n';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
|
||||
import {getSaveButton} from '../config';
|
||||
@@ -19,11 +18,6 @@ import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const footer = {
|
||||
id: t('settings_display.clock.preferTime'),
|
||||
defaultMessage: 'Select how you prefer time displayed.',
|
||||
};
|
||||
|
||||
const CLOCK_TYPE = {
|
||||
NORMAL: 'NORMAL',
|
||||
MILITARY: 'MILITARY',
|
||||
@@ -79,11 +73,11 @@ const DisplayClock = ({componentId, currentUserId, hasMilitaryTimeFormat}: Displ
|
||||
<SettingContainer>
|
||||
<SettingBlock
|
||||
disableHeader={true}
|
||||
footerText={footer}
|
||||
>
|
||||
<SettingOption
|
||||
action={onSelectClockPreference}
|
||||
label={intl.formatMessage({id: 'settings_display.clock.normal', defaultMessage: '12-hour clock (example: 4:00 PM)'})}
|
||||
label={intl.formatMessage({id: 'settings_display.clock.standard', defaultMessage: '12-hour clock'})}
|
||||
description={intl.formatMessage({id: 'settings_display.clock.normal.desc', defaultMessage: 'Example: 4:00 PM'})}
|
||||
selected={!isMilitaryTimeFormat}
|
||||
testID='clock_display_settings.normal_clock.action'
|
||||
type='select'
|
||||
@@ -92,12 +86,14 @@ const DisplayClock = ({componentId, currentUserId, hasMilitaryTimeFormat}: Displ
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={onSelectClockPreference}
|
||||
label={intl.formatMessage({id: 'settings_display.clock.military', defaultMessage: '24-hour clock (example: 16:00)'})}
|
||||
label={intl.formatMessage({id: 'settings_display.clock.mz', defaultMessage: '24-hour clock'})}
|
||||
description={intl.formatMessage({id: 'settings_display.clock.mz.desc', defaultMessage: 'Example: 16:00'})}
|
||||
selected={isMilitaryTimeFormat}
|
||||
testID='clock_display_settings.military_clock.action'
|
||||
type='select'
|
||||
value={CLOCK_TYPE.MILITARY}
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
</SettingBlock>
|
||||
</SettingContainer>
|
||||
);
|
||||
|
||||
@@ -3,40 +3,33 @@
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyles: {
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
});
|
||||
const radioItemProps = {checkedBody: true};
|
||||
|
||||
type CustomThemeProps = {
|
||||
customTheme: Theme;
|
||||
setTheme: (themeKey: string) => void;
|
||||
displayTheme: string | undefined;
|
||||
}
|
||||
|
||||
const CustomTheme = ({customTheme, setTheme}: CustomThemeProps) => {
|
||||
const CustomTheme = ({setTheme, displayTheme}: CustomThemeProps) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<SettingBlock
|
||||
containerStyles={styles.containerStyles}
|
||||
disableHeader={true}
|
||||
>
|
||||
<>
|
||||
<SettingSeparator isGroupSeparator={true}/>
|
||||
<SettingOption
|
||||
action={setTheme}
|
||||
type='select'
|
||||
value={customTheme.type}
|
||||
value={theme.type}
|
||||
label={intl.formatMessage({id: 'settings_display.custom_theme', defaultMessage: 'Custom Theme'})}
|
||||
selected={theme.type?.toLowerCase() === customTheme.type?.toLowerCase()}
|
||||
selected={theme.type?.toLowerCase() === displayTheme?.toLowerCase()}
|
||||
radioItemProps={radioItemProps}
|
||||
/>
|
||||
</SettingBlock>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,37 +1,45 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import {savePreference} from '@actions/remote/preference';
|
||||
import {Preferences} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {getSaveButton} from '@screens/settings/config';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
|
||||
import CustomTheme from './custom_theme';
|
||||
import {ThemeTiles} from './theme_tiles';
|
||||
|
||||
const SAVE_DISPLAY_THEME_BTN_ID = 'SAVE_DISPLAY_THEME_BTN_ID';
|
||||
|
||||
type DisplayThemeProps = {
|
||||
allowedThemeKeys: string[];
|
||||
componentId: string;
|
||||
currentTeamId: string;
|
||||
currentUserId: string;
|
||||
}
|
||||
|
||||
const DisplayTheme = ({allowedThemeKeys, currentTeamId, currentUserId}: DisplayThemeProps) => {
|
||||
const DisplayTheme = ({allowedThemeKeys, componentId, currentTeamId, currentUserId}: DisplayThemeProps) => {
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const [customTheme, setCustomTheme] = useState<Theme|null>();
|
||||
const intl = useIntl();
|
||||
const initialTheme = useMemo(() => theme.type, []); // dependency array should remain empty
|
||||
|
||||
useEffect(() => {
|
||||
if (theme.type === 'custom') {
|
||||
setCustomTheme(theme);
|
||||
}
|
||||
}, []);
|
||||
const [displayTheme, setDisplayTheme] = useState<string | undefined>(initialTheme);
|
||||
|
||||
const updateTheme = useCallback((selectedThemeKey: string) => {
|
||||
const selectedTheme = allowedThemeKeys.find((tk) => tk === selectedThemeKey);
|
||||
const saveButton = useMemo(() => getSaveButton(SAVE_DISPLAY_THEME_BTN_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]);
|
||||
|
||||
const close = () => popTopScreen(componentId);
|
||||
|
||||
const updateTheme = useCallback(() => {
|
||||
const selectedTheme = allowedThemeKeys.find((tk) => tk === displayTheme);
|
||||
if (!selectedTheme) {
|
||||
return;
|
||||
}
|
||||
@@ -42,18 +50,34 @@ const DisplayTheme = ({allowedThemeKeys, currentTeamId, currentUserId}: DisplayT
|
||||
value: JSON.stringify(Preferences.THEMES[selectedTheme]),
|
||||
};
|
||||
savePreference(serverUrl, [pref]);
|
||||
}, [serverUrl, allowedThemeKeys, currentTeamId]);
|
||||
close();
|
||||
}, [serverUrl, allowedThemeKeys, currentTeamId, displayTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
const buttons = {
|
||||
rightButtons: [{
|
||||
...saveButton,
|
||||
enabled: initialTheme?.toLowerCase() !== displayTheme?.toLowerCase(),
|
||||
}],
|
||||
};
|
||||
setButtons(componentId, buttons);
|
||||
}, [componentId, saveButton, displayTheme, initialTheme]);
|
||||
|
||||
useNavButtonPressed(SAVE_DISPLAY_THEME_BTN_ID, componentId, updateTheme, [updateTheme]);
|
||||
|
||||
useAndroidHardwareBackHandler(componentId, close);
|
||||
|
||||
return (
|
||||
<SettingContainer>
|
||||
<ThemeTiles
|
||||
allowedThemeKeys={allowedThemeKeys}
|
||||
onThemeChange={updateTheme}
|
||||
onThemeChange={setDisplayTheme}
|
||||
selectedTheme={displayTheme}
|
||||
/>
|
||||
{customTheme && (
|
||||
{theme.type === 'custom' && (
|
||||
<CustomTheme
|
||||
customTheme={customTheme}
|
||||
setTheme={updateTheme}
|
||||
setTheme={setDisplayTheme}
|
||||
displayTheme={displayTheme}
|
||||
/>
|
||||
)}
|
||||
</SettingContainer>
|
||||
|
||||
@@ -20,12 +20,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
container: {
|
||||
flexDirection: 'column',
|
||||
padding: TILE_PADDING,
|
||||
marginTop: 8,
|
||||
},
|
||||
imageWrapper: {
|
||||
position: 'relative',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 12,
|
||||
marginBottom: 8,
|
||||
},
|
||||
thumbnail: {
|
||||
resizeMode: 'stretch',
|
||||
@@ -39,6 +38,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
label: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200),
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
tilesContainer: {
|
||||
marginBottom: 30,
|
||||
@@ -46,10 +46,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -99,8 +95,8 @@ export const ThemeTile = ({
|
||||
>
|
||||
<View style={[styles.imageWrapper, layoutStyle.thumbnail]}>
|
||||
<ThemeThumbnail
|
||||
borderColorBase={selected ? activeTheme.sidebarTextActiveBorder : activeTheme.centerChannelBg}
|
||||
borderColorMix={selected ? activeTheme.sidebarTextActiveBorder : changeOpacity(activeTheme.centerChannelColor, 0.16)}
|
||||
borderColorBase={selected ? activeTheme.buttonBg : activeTheme.centerChannelBg}
|
||||
borderColorMix={selected ? activeTheme.buttonBg : changeOpacity(activeTheme.centerChannelColor, 0.16)}
|
||||
theme={theme}
|
||||
width={layoutStyle.thumbnail.width}
|
||||
/>
|
||||
@@ -120,29 +116,36 @@ export const ThemeTile = ({
|
||||
type ThemeTilesProps = {
|
||||
allowedThemeKeys: string[];
|
||||
onThemeChange: (v: string) => void;
|
||||
selectedTheme: string | undefined;
|
||||
}
|
||||
export const ThemeTiles = ({allowedThemeKeys, onThemeChange}: ThemeTilesProps) => {
|
||||
export const ThemeTiles = ({allowedThemeKeys, onThemeChange, selectedTheme}: ThemeTilesProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const styles = getStyleSheet(theme);
|
||||
return (
|
||||
<View style={styles.tilesContainer}>
|
||||
{
|
||||
allowedThemeKeys.map((themeKey: string) => (
|
||||
<ThemeTile
|
||||
key={themeKey}
|
||||
label={(
|
||||
<Text style={styles.label}>
|
||||
{themeKey}
|
||||
</Text>
|
||||
)}
|
||||
action={onThemeChange}
|
||||
actionValue={themeKey}
|
||||
selected={theme.type?.toLowerCase() === themeKey.toLowerCase()}
|
||||
theme={Preferences.THEMES[themeKey]}
|
||||
activeTheme={theme}
|
||||
/>
|
||||
))
|
||||
allowedThemeKeys.map((themeKey: string) => {
|
||||
if (!Preferences.THEMES[themeKey] || !selectedTheme) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeTile
|
||||
key={themeKey}
|
||||
label={(
|
||||
<Text style={styles.label}>
|
||||
{themeKey}
|
||||
</Text>
|
||||
)}
|
||||
action={onThemeChange}
|
||||
actionValue={themeKey}
|
||||
selected={selectedTheme?.toLowerCase() === themeKey.toLowerCase()}
|
||||
theme={Preferences.THEMES[themeKey]}
|
||||
activeTheme={theme}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import {Screens} from '@constants';
|
||||
@@ -32,9 +31,10 @@ type DisplayTimezoneProps = {
|
||||
const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const timezone = useMemo(() => getUserTimezoneProps(currentUser), [currentUser.timezone]);
|
||||
const [userTimezone, setUserTimezone] = useState(timezone);
|
||||
const initialTimezone = useMemo(() => getUserTimezoneProps(currentUser), []); // deps array should remain empty
|
||||
const [userTimezone, setUserTimezone] = useState(initialTimezone);
|
||||
const theme = useTheme();
|
||||
|
||||
const updateAutomaticTimezone = (useAutomaticTimezone: boolean) => {
|
||||
const automaticTimezone = getDeviceTimezone();
|
||||
setUserTimezone((prev) => ({
|
||||
@@ -55,8 +55,9 @@ const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => {
|
||||
const goToSelectTimezone = preventDoubleTap(() => {
|
||||
const screen = Screens.SETTINGS_DISPLAY_TIMEZONE_SELECT;
|
||||
const title = intl.formatMessage({id: 'settings_display.timezone.select', defaultMessage: 'Select Timezone'});
|
||||
|
||||
const passProps = {
|
||||
selectedTimezone: userTimezone.manualTimezone || timezone.manualTimezone || timezone.automaticTimezone,
|
||||
currentTimezone: userTimezone.manualTimezone || initialTimezone.manualTimezone || initialTimezone.automaticTimezone,
|
||||
onBack: updateManualTimezone,
|
||||
};
|
||||
|
||||
@@ -80,9 +81,9 @@ const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
const enabled =
|
||||
timezone.useAutomaticTimezone !== userTimezone.useAutomaticTimezone ||
|
||||
timezone.automaticTimezone !== userTimezone.automaticTimezone ||
|
||||
timezone.manualTimezone !== userTimezone.manualTimezone;
|
||||
initialTimezone.useAutomaticTimezone !== userTimezone.useAutomaticTimezone ||
|
||||
initialTimezone.automaticTimezone !== userTimezone.automaticTimezone ||
|
||||
initialTimezone.manualTimezone !== userTimezone.manualTimezone;
|
||||
|
||||
const buttons = {
|
||||
rightButtons: [{
|
||||
@@ -97,28 +98,35 @@ const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => {
|
||||
|
||||
useAndroidHardwareBackHandler(componentId, close);
|
||||
|
||||
const toggleDesc = useMemo(() => {
|
||||
if (userTimezone.useAutomaticTimezone) {
|
||||
return getTimezoneRegion(userTimezone.automaticTimezone);
|
||||
}
|
||||
return intl.formatMessage({id: 'settings_display.timezone.off', defaultMessage: 'Off'});
|
||||
}, [userTimezone.useAutomaticTimezone]);
|
||||
|
||||
return (
|
||||
<SettingContainer>
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={updateAutomaticTimezone}
|
||||
description={getTimezoneRegion(userTimezone.automaticTimezone)}
|
||||
description={toggleDesc}
|
||||
label={intl.formatMessage({id: 'settings_display.timezone.automatically', defaultMessage: 'Set automatically'})}
|
||||
selected={userTimezone.useAutomaticTimezone}
|
||||
type='toggle'
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
{!userTimezone.useAutomaticTimezone && (
|
||||
<View>
|
||||
<SettingSeparator/>
|
||||
<>
|
||||
{/* <SettingSeparator/> */}
|
||||
<SettingOption
|
||||
action={goToSelectTimezone}
|
||||
description={getTimezoneRegion(userTimezone.manualTimezone)}
|
||||
info={getTimezoneRegion(userTimezone.manualTimezone)}
|
||||
label={intl.formatMessage({id: 'settings_display.timezone.manual', defaultMessage: 'Change timezone'})}
|
||||
type='arrow'
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<SettingSeparator/>
|
||||
{/* <SettingSeparator/> */}
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {FlatList, View} from 'react-native';
|
||||
import {FlatList} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {getAllSupportedTimezones} from '@actions/remote/user';
|
||||
import Search from '@components/search';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {getSaveButton} from '@screens/settings/config';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {getTimezoneRegion} from '@utils/user';
|
||||
@@ -30,15 +33,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'Regular'),
|
||||
},
|
||||
searchBar: {
|
||||
height: 38,
|
||||
marginVertical: 5,
|
||||
marginBottom: 32,
|
||||
},
|
||||
inputContainerStyle: {
|
||||
searchBarInputContainerStyle: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
height: 38,
|
||||
},
|
||||
searchBarContainerStyle: {
|
||||
paddingHorizontal: 12,
|
||||
marginLeft: 12,
|
||||
marginBottom: 32,
|
||||
marginTop: 12,
|
||||
},
|
||||
};
|
||||
@@ -47,7 +48,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
const EDGES: Edge[] = ['left', 'right'];
|
||||
const EMPTY_TIMEZONES: string[] = [];
|
||||
const ITEM_HEIGHT = 48;
|
||||
|
||||
const SAVE_DISPLAY_TZ_BTN_ID = 'SAVE_DISPLAY_TZ_BTN_ID';
|
||||
const keyExtractor = (item: string) => item;
|
||||
const getItemLayout = (_data: string[], index: number) => ({
|
||||
length: ITEM_HEIGHT,
|
||||
@@ -56,15 +57,16 @@ const getItemLayout = (_data: string[], index: number) => ({
|
||||
});
|
||||
|
||||
type SelectTimezonesProps = {
|
||||
selectedTimezone: string;
|
||||
componentId: string;
|
||||
onBack: (tz: string) => void;
|
||||
currentTimezone: string;
|
||||
}
|
||||
const SelectTimezones = ({selectedTimezone, onBack}: SelectTimezonesProps) => {
|
||||
const SelectTimezones = ({componentId, onBack, currentTimezone}: SelectTimezonesProps) => {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const initialTimezones = useMemo(() => currentTimezone, []);
|
||||
const cancelButtonProps = useMemo(() => ({
|
||||
buttonTextStyle: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
@@ -77,17 +79,17 @@ const SelectTimezones = ({selectedTimezone, onBack}: SelectTimezonesProps) => {
|
||||
|
||||
const [timezones, setTimezones] = useState<string[]>(EMPTY_TIMEZONES);
|
||||
const [initialScrollIndex, setInitialScrollIndex] = useState<number|undefined>();
|
||||
const [value, setValue] = useState('');
|
||||
const [searchRegion, setSearchRegion] = useState<string|undefined>(undefined);
|
||||
const [manualTimezone, setManualTimezone] = useState(currentTimezone);
|
||||
|
||||
const filteredTimezones = (timezonePrefix: string) => {
|
||||
if (timezonePrefix.length === 0) {
|
||||
const filteredTimezones = useCallback(() => {
|
||||
if (!searchRegion) {
|
||||
return timezones;
|
||||
}
|
||||
|
||||
const lowerCasePrefix = timezonePrefix.toLowerCase();
|
||||
const lowerCasePrefix = searchRegion.toLowerCase();
|
||||
|
||||
// if initial scroll index is set when the items change
|
||||
// and the index is grater than the amount of items
|
||||
// and the index is greater than the amount of items
|
||||
// the list starts to render partial results until there is
|
||||
// and interaction, so setting the index as undefined corrects
|
||||
// the rendering
|
||||
@@ -99,21 +101,27 @@ const SelectTimezones = ({selectedTimezone, onBack}: SelectTimezonesProps) => {
|
||||
getTimezoneRegion(t).toLowerCase().indexOf(lowerCasePrefix) >= 0 ||
|
||||
t.toLowerCase().indexOf(lowerCasePrefix) >= 0
|
||||
));
|
||||
};
|
||||
}, [searchRegion, timezones, initialScrollIndex]);
|
||||
|
||||
const onPressTimezone = useCallback((tzne: string) => {
|
||||
onBack(tzne);
|
||||
popTopScreen();
|
||||
}, [onBack]);
|
||||
const onPressTimezone = useCallback((tz: string) => {
|
||||
setManualTimezone(tz);
|
||||
}, []);
|
||||
|
||||
const renderItem = ({item: timezone}: {item: string}) => {
|
||||
const renderItem = useCallback(({item: timezone}: {item: string}) => {
|
||||
return (
|
||||
<TimezoneRow
|
||||
isSelected={timezone === manualTimezone}
|
||||
onPressTimezone={onPressTimezone}
|
||||
selectedTimezone={selectedTimezone}
|
||||
timezone={timezone}
|
||||
/>
|
||||
);
|
||||
}, [manualTimezone, onPressTimezone]);
|
||||
|
||||
const saveButton = useMemo(() => getSaveButton(SAVE_DISPLAY_TZ_BTN_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]);
|
||||
|
||||
const close = () => {
|
||||
onBack(manualTimezone);
|
||||
popTopScreen(componentId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -122,7 +130,7 @@ const SelectTimezones = ({selectedTimezone, onBack}: SelectTimezonesProps) => {
|
||||
const allTzs = await getAllSupportedTimezones(serverUrl);
|
||||
if (allTzs.length > 0) {
|
||||
setTimezones(allTzs);
|
||||
const timezoneIndex = allTzs.findIndex((timezone) => timezone === selectedTimezone);
|
||||
const timezoneIndex = allTzs.findIndex((timezone) => timezone === currentTimezone);
|
||||
if (timezoneIndex > 0) {
|
||||
setInitialScrollIndex(timezoneIndex);
|
||||
}
|
||||
@@ -131,29 +139,44 @@ const SelectTimezones = ({selectedTimezone, onBack}: SelectTimezonesProps) => {
|
||||
getSupportedTimezones();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const buttons = {
|
||||
rightButtons: [{
|
||||
...saveButton,
|
||||
enabled: initialTimezones !== manualTimezone,
|
||||
}],
|
||||
};
|
||||
setButtons(componentId, buttons);
|
||||
}, [componentId, saveButton, initialTimezones, manualTimezone]);
|
||||
|
||||
useNavButtonPressed(SAVE_DISPLAY_TZ_BTN_ID, componentId, close, [manualTimezone]);
|
||||
|
||||
useAndroidHardwareBackHandler(componentId, close);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={EDGES}
|
||||
style={styles.container}
|
||||
testID='settings.select_timezone.screen'
|
||||
>
|
||||
<View style={styles.searchBar}>
|
||||
<Search
|
||||
autoCapitalize='none'
|
||||
cancelButtonProps={cancelButtonProps}
|
||||
inputContainerStyle={styles.inputContainerStyle}
|
||||
inputStyle={styles.searchBarInput}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
onChangeText={setValue}
|
||||
placeholder={intl.formatMessage({id: 'search_bar.search.placeholder', defaultMessage: 'Search timezone'})}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
selectionColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
testID='settings.select_timezone.search_bar'
|
||||
value={value}
|
||||
/>
|
||||
</View>
|
||||
<Search
|
||||
autoCapitalize='none'
|
||||
cancelButtonProps={cancelButtonProps}
|
||||
inputContainerStyle={styles.searchBarInputContainerStyle}
|
||||
containerStyle={styles.searchBarContainerStyle}
|
||||
inputStyle={styles.searchBarInput}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
onChangeText={setSearchRegion}
|
||||
placeholder={intl.formatMessage({id: 'search_bar.search.placeholder', defaultMessage: 'Search timezone'})}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
selectionColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
testID='settings.select_timezone.search_bar'
|
||||
value={searchRegion}
|
||||
/>
|
||||
<FlatList
|
||||
data={filteredTimezones(value)}
|
||||
contentContainerStyle={styles.flexGrow}
|
||||
data={searchRegion?.length ? filteredTimezones() : timezones}
|
||||
extraData={manualTimezone}
|
||||
getItemLayout={getItemLayout}
|
||||
initialScrollIndex={initialScrollIndex}
|
||||
keyExtractor={keyExtractor}
|
||||
@@ -161,7 +184,6 @@ const SelectTimezones = ({selectedTimezone, onBack}: SelectTimezonesProps) => {
|
||||
keyboardShouldPersistTaps='always'
|
||||
removeClippedSubviews={true}
|
||||
renderItem={renderItem}
|
||||
contentContainerStyle={styles.flexGrow}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
@@ -38,11 +38,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
};
|
||||
});
|
||||
type TimezoneRowProps = {
|
||||
isSelected: boolean;
|
||||
onPressTimezone: (timezone: string) => void;
|
||||
selectedTimezone: string;
|
||||
timezone: string;
|
||||
}
|
||||
const TimezoneRow = ({onPressTimezone, selectedTimezone, timezone}: TimezoneRowProps) => {
|
||||
const TimezoneRow = ({onPressTimezone, isSelected, timezone}: TimezoneRowProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
@@ -64,7 +64,7 @@ const TimezoneRow = ({onPressTimezone, selectedTimezone, timezone}: TimezoneRowP
|
||||
{timezone}
|
||||
</Text>
|
||||
</View>
|
||||
{timezone === selectedTimezone && (
|
||||
{isSelected && (
|
||||
<CompassIcon
|
||||
color={theme.linkColor}
|
||||
name='check'
|
||||
|
||||
@@ -52,7 +52,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
footer: {
|
||||
paddingHorizontal: 20,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
textAlign: 'justify',
|
||||
...typography('Body', 75, 'Regular'),
|
||||
marginTop: 20,
|
||||
},
|
||||
|
||||
@@ -41,11 +41,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
containerStyle: {
|
||||
marginTop: 30,
|
||||
width: '90%',
|
||||
alignSelf: 'center',
|
||||
paddingHorizontal: 18.5,
|
||||
},
|
||||
keywordLabelStyle: {
|
||||
marginLeft: 20,
|
||||
paddingHorizontal: 18.5,
|
||||
marginTop: 4,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 75, 'Regular'),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import {useServerUrl} from '@context/server';
|
||||
@@ -10,6 +11,7 @@ import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import {getSaveButton} from '../config';
|
||||
@@ -88,12 +90,14 @@ const NotificationPush = ({componentId, currentUser, isCRTEnabled, sendPushNotif
|
||||
sendPushNotifications={sendPushNotifications}
|
||||
setMobilePushPref={setPushSend}
|
||||
/>
|
||||
{Platform.OS === 'android' && (<SettingSeparator isGroupSeparator={true}/>)}
|
||||
{isCRTEnabled && pushSend === 'mention' && (
|
||||
<MobilePushThread
|
||||
pushThread={pushThread}
|
||||
onMobilePushThreadChanged={onMobilePushThreadChanged}
|
||||
/>
|
||||
)}
|
||||
{Platform.OS === 'android' && (<SettingSeparator isGroupSeparator={true}/>)}
|
||||
{sendPushNotifications && pushSend !== 'none' && (
|
||||
<MobilePushStatus
|
||||
pushStatus={pushStatus}
|
||||
|
||||
@@ -56,7 +56,7 @@ const MobileSendPush = ({sendPushNotifications, pushStatus, setMobilePushPref}:
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={setMobilePushPref}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.mentions.only', defaultMessage: 'Mentions, direct messages only(default)'})}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.mentions_only', defaultMessage: 'Mentions, direct messages only (default)'})}
|
||||
selected={pushStatus === 'mention'}
|
||||
testID='notification_settings.pushNotification.onlyMentions'
|
||||
type='select'
|
||||
@@ -71,6 +71,7 @@ const MobileSendPush = ({sendPushNotifications, pushStatus, setMobilePushPref}:
|
||||
type='select'
|
||||
value='none'
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
</>
|
||||
}
|
||||
{!sendPushNotifications &&
|
||||
|
||||
@@ -49,6 +49,7 @@ const MobilePushStatus = ({pushStatus, setMobilePushStatus}: MobilePushStatusPro
|
||||
type='select'
|
||||
value='offline'
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
import {t} from '@i18n';
|
||||
|
||||
@@ -11,19 +10,9 @@ import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
area: {
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
});
|
||||
|
||||
const headerText = {
|
||||
id: t('notification_settings.push_threads'),
|
||||
defaultMessage: 'Thread reply notifications',
|
||||
};
|
||||
const footerText = {
|
||||
id: t('notification_settings.push_threads.info'),
|
||||
defaultMessage: 'When enabled, any reply to a thread you\'re following will send a mobile push notification',
|
||||
id: t('notification_settings.push_threads.replies'),
|
||||
defaultMessage: 'Thread replies',
|
||||
};
|
||||
|
||||
type MobilePushThreadProps = {
|
||||
@@ -37,12 +26,10 @@ const MobilePushThread = ({pushThread, onMobilePushThreadChanged}: MobilePushThr
|
||||
return (
|
||||
<SettingBlock
|
||||
headerText={headerText}
|
||||
footerText={footerText}
|
||||
containerStyles={styles.area}
|
||||
>
|
||||
<SettingOption
|
||||
action={onMobilePushThreadChanged}
|
||||
label={intl.formatMessage({id: 'notification_settings.push_threads.description', defaultMessage: 'Notify me about all replies to threads I\'m following'})}
|
||||
label={intl.formatMessage({id: 'notification_settings.push_threads.following', defaultMessage: 'Notify me about replies to threads I\'m following in this channel'})}
|
||||
selected={pushThread === 'all'}
|
||||
type='toggle'
|
||||
/>
|
||||
|
||||
@@ -12,8 +12,12 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
blockHeader: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 300, 'SemiBold'),
|
||||
marginBottom: 16,
|
||||
marginLeft: 18,
|
||||
marginBottom: 8,
|
||||
marginLeft: 20,
|
||||
marginTop: 12,
|
||||
},
|
||||
contentContainerStyle: {
|
||||
marginBottom: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -30,6 +34,7 @@ const SettingBlock = ({headerText, ...props}: SettingBlockProps) => {
|
||||
<Block
|
||||
headerText={headerText}
|
||||
headerStyles={styles.blockHeader}
|
||||
containerStyles={styles.contentContainerStyle}
|
||||
{...props}
|
||||
>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
contentContainerStyle: {
|
||||
marginTop: 20,
|
||||
marginTop: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem, {OptionItemProps} from '@components/option_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
@@ -29,12 +30,15 @@ const SettingOption = ({...props}: OptionItemProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const useRadioButton = props.type === 'select' && Platform.OS === 'android';
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
optionDescriptionTextStyle={styles.optionDescriptionTextStyle}
|
||||
optionLabelTextStyle={styles.optionLabelTextStyle}
|
||||
containerStyle={[styles.container, props.description && {marginTop: 16}]}
|
||||
containerStyle={[styles.container, props.description && {marginVertical: 12}]}
|
||||
{...props}
|
||||
type={useRadioButton ? 'radio' : props.type}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform, Text} from 'react-native';
|
||||
import {Platform, StyleProp, Text, TextStyle} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type SettingRowLabelProps = {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
rightLabel: {
|
||||
@@ -27,13 +23,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
};
|
||||
});
|
||||
|
||||
const SettingRowLabel = ({text}: SettingRowLabelProps) => {
|
||||
type SettingRowLabelProps = {
|
||||
text: string;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
}
|
||||
const SettingRowLabel = ({text, textStyle}: SettingRowLabelProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={styles.rightLabel}
|
||||
style={[styles.rightLabel, textStyle]}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
|
||||
@@ -25,14 +25,15 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
containerStyle: {
|
||||
paddingLeft: 8,
|
||||
marginTop: 20,
|
||||
marginTop: 12,
|
||||
},
|
||||
helpGroup: {
|
||||
width: '91%',
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
height: 1,
|
||||
alignSelf: 'center',
|
||||
marginTop: 20,
|
||||
|
||||
// marginTop: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -120,7 +121,7 @@ const Settings = ({componentId, helpLink, showHelp, siteName}: SettingsProps) =>
|
||||
});
|
||||
|
||||
return (
|
||||
<SettingContainer >
|
||||
<SettingContainer>
|
||||
<SettingItem
|
||||
onPress={goToNotifications}
|
||||
optionName='notification'
|
||||
|
||||
@@ -8,32 +8,39 @@ import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
const groupSeparator = {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.12),
|
||||
width: '91%',
|
||||
alignSelf: 'center',
|
||||
height: 1,
|
||||
};
|
||||
return {
|
||||
separator: {
|
||||
...Platform.select({
|
||||
ios: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
width: '91%',
|
||||
alignSelf: 'center',
|
||||
height: 1,
|
||||
marginTop: 12,
|
||||
...groupSeparator,
|
||||
},
|
||||
default: {
|
||||
display: 'none',
|
||||
},
|
||||
}),
|
||||
},
|
||||
groupSeparator: {
|
||||
...groupSeparator,
|
||||
marginBottom: 16,
|
||||
},
|
||||
};
|
||||
});
|
||||
type SettingSeparatorProps = {
|
||||
lineStyles?: StyleProp<ViewStyle>;
|
||||
isGroupSeparator?: boolean;
|
||||
}
|
||||
|
||||
const SettingSeparator = ({lineStyles}: SettingSeparatorProps) => {
|
||||
const SettingSeparator = ({lineStyles, isGroupSeparator = false}: SettingSeparatorProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (<View style={[styles.separator, lineStyles]}/>);
|
||||
return (<View style={[styles.separator, isGroupSeparator && styles.groupSeparator, lineStyles]}/>);
|
||||
};
|
||||
|
||||
export default SettingSeparator;
|
||||
|
||||
@@ -347,10 +347,6 @@
|
||||
"mentions.empty.paragraph": "You'll see messages here when someone mentions you or uses terms you're monitoring.",
|
||||
"mentions.empty.title": "No Mentions yet",
|
||||
"mobile.about.appVersion": "App Version: {version} (Build {number})",
|
||||
"mobile.about.copyright": "Copyright 2015-{currentYear} Mattermost, Inc. All rights reserved",
|
||||
"mobile.about.database": "Database: {type}",
|
||||
"mobile.about.licensed": "Licensed to: {company}",
|
||||
"mobile.about.powered_by": "{site} is powered by Mattermost",
|
||||
"mobile.about.serverVersion": "Server Version: {version} (Build {number})",
|
||||
"mobile.about.serverVersionNoBuild": "Server Version: {version}",
|
||||
"mobile.account.settings.save": "Save",
|
||||
@@ -454,9 +450,6 @@
|
||||
"mobile.no_results_with_term.files": "No files matching “{term}”",
|
||||
"mobile.no_results_with_term.messages": "No matches found for “{term}”",
|
||||
"mobile.no_results.spelling": "Check the spelling or try another search.",
|
||||
"mobile.notice_mobile_link": "mobile apps",
|
||||
"mobile.notice_platform_link": "server",
|
||||
"mobile.notice_text": "Mattermost is made possible by the open source software used in our {platform} and {mobile}.",
|
||||
"mobile.oauth.failed_to_login": "Your login attempt failed. Please try again.",
|
||||
"mobile.oauth.failed_to_open_link": "The link failed to open. Please try again.",
|
||||
"mobile.oauth.failed_to_open_link_no_browser": "The link failed to open. Please verify that a browser is installed on the device.",
|
||||
@@ -608,12 +601,11 @@
|
||||
"notification_settings.mobile.trigger_push": "Trigger push notifications when...",
|
||||
"notification_settings.ooo_auto_responder": "Automatic replies",
|
||||
"notification_settings.push_notification": "Push Notifications",
|
||||
"notification_settings.push_threads": "Thread reply notifications",
|
||||
"notification_settings.push_threads.description": "Notify me about all replies to threads I'm following",
|
||||
"notification_settings.push_threads.info": "When enabled, any reply to a thread you're following will send a mobile push notification",
|
||||
"notification_settings.push_threads.following": "Notify me about replies to threads I'm following in this channel",
|
||||
"notification_settings.push_threads.replies": "Thread replies",
|
||||
"notification_settings.pushNotification.all_new_messages": "All new messages",
|
||||
"notification_settings.pushNotification.disabled_long": "Push notifications for mobile devices have been disabled by your System Administrator.",
|
||||
"notification_settings.pushNotification.mentions.only": "Mentions, direct messages only(default)",
|
||||
"notification_settings.pushNotification.mentions_only": "Mentions, direct messages only (default)",
|
||||
"notification_settings.pushNotification.nothing": "Nothing",
|
||||
"notification_settings.send_notification.about": "Notify me about...",
|
||||
"notification_settings.threads_mentions": "Mentions in threads",
|
||||
@@ -722,16 +714,31 @@
|
||||
"servers.login": "Log in",
|
||||
"servers.logout": "Log out",
|
||||
"servers.remove": "Remove",
|
||||
"settings_display.clock.military": "24-hour clock (example: 16:00)",
|
||||
"settings_display.clock.normal": "12-hour clock (example: 4:00 PM)",
|
||||
"settings_display.clock.preferTime": "Select how you prefer time displayed.",
|
||||
"settings_display.clock.mz": "24-hour clock",
|
||||
"settings_display.clock.mz.desc": "Example: 16:00",
|
||||
"settings_display.clock.normal.desc": "Example: 4:00 PM",
|
||||
"settings_display.clock.standard": "12-hour clock",
|
||||
"settings_display.custom_theme": "Custom Theme",
|
||||
"settings_display.timezone.automatically": "Set automatically",
|
||||
"settings_display.timezone.manual": "Change timezone",
|
||||
"settings_display.timezone.off": "Off",
|
||||
"settings_display.timezone.select": "Select Timezone",
|
||||
"settings.about": "About {appTitle}",
|
||||
"settings.about.build": "{version} (Build {number})",
|
||||
"settings.about.copyright": "Copyright 2015-{currentYear} Mattermost, Inc. All rights reserved",
|
||||
"settings.about.database": "Database:",
|
||||
"settings.about.licensed": "Licensed to: {company}",
|
||||
"settings.about.powered_by": "{site} is powered by Mattermost",
|
||||
"settings.about.serverVersion": "{version} (Build {number})",
|
||||
"settings.about.serverVersionNoBuild": "{version}",
|
||||
"settings.about.version": "App Version:",
|
||||
"settings.advanced_settings": "Advanced Settings",
|
||||
"settings.display": "Display",
|
||||
"settings.link.error.text": "Unable to open the link.",
|
||||
"settings.link.error.title": "Error",
|
||||
"settings.notice_mobile_link": "mobile apps",
|
||||
"settings.notice_platform_link": "server",
|
||||
"settings.notice_text": "Mattermost is made possible by the open source software used in our {platform} and {mobile}.",
|
||||
"settings.notifications": "Notifications",
|
||||
"settings.save": "Save",
|
||||
"smobile.search.recent_title": "Recent searches in {teamName}",
|
||||
|
||||
Reference in New Issue
Block a user