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:
parent
216b7058df
commit
39cfb297a3
11 changed files with 208 additions and 57 deletions
|
|
@ -33,3 +33,4 @@ export const updateLocalFilePath = async (serverUrl: string, fileId: string, loc
|
|||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
110
app/screens/settings/advanced/index.tsx
Normal file
110
app/screens/settings/advanced/index.tsx
Normal 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;
|
||||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue