MM-39711 - Gekidou Advanced Settings (#6412)

* fix display clock

* implemented help functionality

* skelton for adv settings

* added rightComponent to menuItem

* added advanced settings

* adv settings delete

* Update en.json

* clean up

* Update index.tsx

* fix for camera-roll on android > 30

* undo delete camera-roll images

* Update app/screens/settings/index.tsx

Co-authored-by: Daniel Espino García <larkox@gmail.com>

* refactoring

* removed menu item

* refactored imports and getAllFilesInCachesDirectory transferred into util/files

* corrections after review

* ts fix

Co-authored-by: Daniel Espino García <larkox@gmail.com>
This commit is contained in:
Avinash Lingaloo 2022-07-08 14:39:41 +04:00 committed by GitHub
parent 216b7058df
commit 39cfb297a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 208 additions and 57 deletions

View file

@ -33,3 +33,4 @@ export const updateLocalFilePath = async (serverUrl: string, fileId: string, loc
return {error};
}
};

View file

@ -10,27 +10,6 @@ import TouchableWithFeedback from '@components/touchable_with_feedback';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
export const ITEM_HEIGHT = 50;
export type MenuItemProps = {
centered?: boolean;
defaultMessage?: string;
i18nId?: string;
iconContainerStyle?: StyleProp<ViewStyle>;
iconName?: string;
containerStyle?: StyleProp<ViewStyle>;
isDestructor?: boolean;
labelComponent?: ReactNode;
leftComponent?: ReactNode;
messageValues?: Record<string, any>;
onPress: () => void;
separator?: boolean;
showArrow?: boolean;
testID: string;
theme: Theme;
labelStyle?: StyleProp<TextStyle>;
isLink?: boolean;
};
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
@ -83,25 +62,31 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
};
});
export type MenuItemProps = {
centered?: boolean;
containerStyle?: StyleProp<ViewStyle>;
defaultMessage?: string;
i18nId?: string;
iconContainerStyle?: StyleProp<ViewStyle>;
iconName?: string;
isDestructor?: boolean;
isLink?: boolean;
labelComponent?: ReactNode;
labelStyle?: StyleProp<TextStyle>;
leftComponent?: ReactNode;
messageValues?: Record<string, any>;
onPress: () => void;
rightComponent?: ReactNode;
separator?: boolean;
showArrow?: boolean;
testID: string;
theme: Theme;
};
const MenuItem = (props: MenuItemProps) => {
const {
centered,
defaultMessage = '',
i18nId,
iconContainerStyle,
iconName,
containerStyle,
isDestructor = false,
isLink = false,
labelComponent,
labelStyle,
leftComponent,
messageValues,
onPress,
separator = true,
showArrow = false,
testID,
theme,
centered, containerStyle, defaultMessage = '', i18nId, iconContainerStyle, iconName, isDestructor = false,
isLink = false, labelComponent, labelStyle, leftComponent, messageValues, onPress, rightComponent,
separator = true, showArrow = false, testID, theme,
} = props;
const style = getStyleSheet(theme);
@ -165,6 +150,7 @@ const MenuItem = (props: MenuItemProps) => {
style={style.chevron}
/>
)}
{rightComponent}
</View>
{Boolean(separator) && (<View style={style.divider}/>)}
</View>

View file

@ -10,7 +10,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
type Props = {
action: (value: string | boolean) => void;
action?: (value: string | boolean) => void;
description?: string;
inline?: boolean;
destructive?: boolean;
@ -29,6 +29,7 @@ const OptionType = {
DEFAULT: 'default',
TOGGLE: 'toggle',
SELECT: 'select',
NONE: 'none',
} as const;
type OptionType = typeof OptionType[keyof typeof OptionType];
@ -157,7 +158,7 @@ const OptionItem = ({
}
const onPress = useCallback(() => {
action(value || '');
action?.(value || '');
}, [value, action]);
const component = (

View file

@ -41,6 +41,7 @@ export const SEARCH = 'Search';
export const SELECT_TEAM = 'SelectTeam';
export const SERVER = 'Server';
export const SETTINGS = 'Settings';
export const SETTINGS_ADVANCED = 'SettingsAdvanced';
export const SETTINGS_DISPLAY = 'SettingsDisplay';
export const SETTINGS_DISPLAY_CLOCK = 'SettingsDisplayClock';
export const SETTINGS_DISPLAY_THEME = 'SettingsDisplayTheme';
@ -99,6 +100,7 @@ export default {
SELECT_TEAM,
SERVER,
SETTINGS,
SETTINGS_ADVANCED,
SETTINGS_DISPLAY,
SETTINGS_DISPLAY_CLOCK,
SETTINGS_DISPLAY_THEME,

View file

@ -151,6 +151,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
case Screens.SETTINGS:
screen = withServerDatabase(require('@screens/settings').default);
break;
case Screens.SETTINGS_ADVANCED:
screen = withServerDatabase(require('@screens/settings/advanced').default);
break;
case Screens.SETTINGS_DISPLAY:
screen = withServerDatabase(require('@screens/settings/display').default);
break;

View file

@ -0,0 +1,110 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
import {View, TouchableOpacity} from 'react-native';
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
import OptionItem from '@components/option_item';
import {useTheme} from '@context/theme';
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
import {popTopScreen} from '@screens/navigation';
import {deleteFileCache, getAllFilesInCachesDirectory, getFormattedFileSize} from '@utils/file';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import type {ReadDirItem} from 'react-native-fs';
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
screen: {
flex: 1,
backgroundColor: theme.centerChannelBg,
},
body: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
flex: 1,
paddingTop: 35,
},
itemStyle: {
backgroundColor: theme.centerChannelBg,
paddingHorizontal: 8,
},
};
});
const EMPTY_FILES: ReadDirItem[] = [];
const EMPTY_SERVERS: string[] = [];
const EDGES: Edge[] = ['left', 'right'];
type AdvancedSettingsProps = {
componentId: string;
}
const AdvancedSettings = ({componentId}: AdvancedSettingsProps) => {
const theme = useTheme();
const intl = useIntl();
const [dataSize, setDataSize] = useState<number|undefined>(0);
const [files, setFiles] = useState<ReadDirItem[]>(EMPTY_FILES);
const [serverUrls, setServerUrls] = useState<string[]>(EMPTY_SERVERS);
const styles = getStyleSheet(theme);
const getAllCachedFiles = async () => {
const {totalSize, files: cachedFiles, serverUrls: allServerUrls} = await getAllFilesInCachesDirectory();
setDataSize(totalSize);
setFiles(cachedFiles || EMPTY_FILES);
setServerUrls(allServerUrls || EMPTY_SERVERS);
};
const onPressDeleteData = preventDoubleTap(async () => {
try {
if (files.length > 0) {
const deletePromises = [];
for (const server of serverUrls) {
deletePromises.push(deleteFileCache(server));
}
await Promise.all(deletePromises);
}
await getAllCachedFiles();
} catch (e) {
//todo: show toast if error https://mattermost.atlassian.net/browse/MM-44926
}
});
useEffect(() => {
getAllCachedFiles();
}, []);
const close = () => popTopScreen(componentId);
useAndroidHardwareBackHandler(componentId, close);
const disabled = Boolean(dataSize && (dataSize > 0));
return (
<SafeAreaView
edges={EDGES}
style={styles.screen}
testID='settings_display.screen'
>
<View
style={styles.body}
>
<TouchableOpacity
onPress={onPressDeleteData}
disabled={disabled}
activeOpacity={disabled ? 0 : 1}
>
<OptionItem
containerStyle={styles.itemStyle}
destructive={true}
label={intl.formatMessage({id: 'advanced_settings.delete_data', defaultMessage: 'Delete Documents & Data'})}
info={getFormattedFileSize(dataSize || 0)}
type='none'
/>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
export default AdvancedSettings;

View file

@ -104,12 +104,6 @@ const DisplayClock = ({componentId, currentUserId, hasMilitaryTimeFormat}: Displ
setButtons(componentId, buttons);
}, [componentId, saveButton, isMilitaryTimeFormat]);
useEffect(() => {
setButtons(componentId, {
rightButtons: [saveButton],
});
}, []);
useAndroidHardwareBackHandler(componentId, close);
useNavButtonPressed(SAVE_CLOCK_BUTTON_ID, componentId, saveClockDisplayPreference, [isMilitaryTimeFormat]);

View file

@ -15,11 +15,13 @@ import type {WithDatabaseArgs} from '@typings/database/database';
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const siteName = observeConfigValue(database, 'SiteName');
const showHelp = observeConfigValue(database, 'HelpLink').pipe(switchMap((link) => of$(link ? isValidUrl(link) : false)));
const helpLink = observeConfigValue(database, 'HelpLink');
const showHelp = helpLink.pipe(switchMap((link) => of$(link ? isValidUrl(link) : false)));
return {
showHelp,
siteName,
helpLink,
};
});

View file

@ -16,6 +16,7 @@ import {dismissModal, goToScreen, setButtons} from '@screens/navigation';
import {preventDoubleTap} from '@utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import {tryOpenURL} from '@utils/url';
import SettingOption from './setting_option';
@ -62,13 +63,14 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
type SettingsProps = {
componentId: string;
siteName: string;
helpLink: string;
showHelp: boolean;
siteName: string;
}
//todo: handle display on tablet and Profile the whole feature - https://mattermost.atlassian.net/browse/MM-39711
const Settings = ({componentId, showHelp, siteName}: SettingsProps) => {
const Settings = ({componentId, helpLink, showHelp, siteName}: SettingsProps) => {
const theme = useTheme();
const intl = useIntl();
const styles = getStyleSheet(theme);
@ -97,12 +99,6 @@ const Settings = ({componentId, showHelp, siteName}: SettingsProps) => {
useNavButtonPressed(CLOSE_BUTTON_ID, componentId, close, []);
const onPressHandler = () => {
return Alert.alert(
'The functionality you are trying to use has not yet been implemented.',
);
};
const goToNotifications = preventDoubleTap(() => {
const screen = Screens.SETTINGS_NOTIFICATION;
const title = intl.formatMessage({id: 'settings.notifications', defaultMessage: 'Notifications'});
@ -124,6 +120,28 @@ const Settings = ({componentId, showHelp, siteName}: SettingsProps) => {
goToScreen(screen, title);
});
const goToAdvancedSettings = preventDoubleTap(() => {
const screen = Screens.SETTINGS_ADVANCED;
const title = intl.formatMessage({id: 'settings.advanced_settings', defaultMessage: 'Advanced Settings'});
goToScreen(screen, title);
});
const openHelp = preventDoubleTap(() => {
const link = helpLink ? helpLink.toLowerCase() : '';
if (link) {
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(link, onError);
}
});
let middleDividerStyle = styles.divider;
if (Platform.OS === 'ios') {
middleDividerStyle = styles.middleDivider;
@ -153,7 +171,7 @@ const Settings = ({componentId, showHelp, siteName}: SettingsProps) => {
/>
<SettingOption
optionName='advanced_settings'
onPress={onPressHandler}
onPress={goToAdvancedSettings}
/>
<SettingOption
optionName='about'
@ -169,7 +187,7 @@ const Settings = ({componentId, showHelp, siteName}: SettingsProps) => {
{showHelp &&
<SettingOption
optionName='help'
onPress={onPressHandler}
onPress={openHelp}
isLink={true}
containerStyle={styles.innerContainerStyle}
/>

View file

@ -14,6 +14,8 @@ import {Asset} from 'react-native-image-picker';
import Permissions, {PERMISSIONS} from 'react-native-permissions';
import {Files} from '@constants';
import DatabaseManager from '@database/manager';
import {queryAllServers} from '@queries/app/servers';
import {generateId} from '@utils/general';
import keyMirror from '@utils/key_mirror';
import {logError} from '@utils/log';
@ -499,3 +501,34 @@ export const hasWriteStoragePermission = async (intl: IntlShape) => {
default: return true;
}
};
export const getAllFilesInCachesDirectory = async () => {
try {
const appDatabase = DatabaseManager.appDatabase;
const servers = await queryAllServers(appDatabase!.database);
if (!servers.length) {
return {error: 'No servers'};
}
const serverUrls = [];
const files: FileSystem.ReadDirItem[][] = [];
for await (const server of servers) {
const directoryFiles = await FileSystem.readDir(`${FileSystem.CachesDirectoryPath}/${hashCode(server.url)}`);
files.push(directoryFiles);
serverUrls.push(server.url);
}
const flattenedFiles = files.flat();
const totalSize = flattenedFiles.reduce((acc, file) => acc + file.size, 0);
return {
files: flattenedFiles,
totalSize,
serverUrls,
};
} catch (error) {
logError('Failed getAllFilesInCachesDirectory', error);
return {error};
}
};

View file

@ -706,6 +706,7 @@
"settings_display.timezone.manual": "Change timezone",
"settings_display.timezone.select": "Select Timezone",
"settings.about": "About {appTitle}",
"settings.advanced_settings": "Advanced Settings",
"settings.display": "Display",
"settings.display.militaryClock.save": "Save",
"settings.notifications": "Notifications",