diff --git a/app/containers/ActionSheet/Item.tsx b/app/containers/ActionSheet/Item.tsx index 2b0b5008..7b48dcef 100644 --- a/app/containers/ActionSheet/Item.tsx +++ b/app/containers/ActionSheet/Item.tsx @@ -26,11 +26,16 @@ export const Item = React.memo(({ item, hide }: IActionSheetItem) => { style={[styles.item, { backgroundColor: themes[theme].focusedBackground }]} theme={theme} testID={item.testID}> - + {item.icon ? ( + + ) : null} + style={[ + styles.title, + { color: item.danger ? themes[theme].dangerColor : themes[theme].bodyText, marginLeft: item.icon ? 16 : 0 } + ]}> {item.title} diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx index 945bb9af..b7caf641 100644 --- a/app/containers/ActionSheet/Provider.tsx +++ b/app/containers/ActionSheet/Provider.tsx @@ -6,7 +6,7 @@ import ActionSheet from './ActionSheet'; export type TActionSheetOptionsItem = { title: string; - icon: TIconsName; + icon?: TIconsName; danger?: boolean; testID?: string; onPress: () => void; diff --git a/app/containers/ActionSheet/styles.ts b/app/containers/ActionSheet/styles.ts index 3ffb9024..4c099c71 100644 --- a/app/containers/ActionSheet/styles.ts +++ b/app/containers/ActionSheet/styles.ts @@ -27,7 +27,6 @@ export default StyleSheet.create({ }, title: { fontSize: 16, - marginLeft: 16, ...sharedStyles.textRegular }, handle: { diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts index 1444d4fe..d49eccf6 100644 --- a/app/definitions/IRoom.ts +++ b/app/definitions/IRoom.ts @@ -237,6 +237,7 @@ export interface IRoomNotifications { desktopNotifications?: TNotifications; mobilePushNotifications?: TNotifications; emailNotifications?: TNotifications; + hideMentionStatus?: boolean; } export type TRoomNotificationsModel = IRoomNotifications & Model; diff --git a/app/lib/methods/helpers/info.ts b/app/lib/methods/helpers/info.ts index 4971f743..2fc903d0 100644 --- a/app/lib/methods/helpers/info.ts +++ b/app/lib/methods/helpers/info.ts @@ -5,6 +5,14 @@ import I18n from '../../../i18n'; export const showErrorAlert = (message: string, title?: string, onPress = () => {}): void => Alert.alert(title || '', message, [{ text: 'OK', onPress }], { cancelable: true }); +export const showErrorAlertWithEMessage = (e: any): void => { + const messageError = + e.data && e.data.error.includes('[error-too-many-requests]') + ? I18n.t('error-too-many-requests', { seconds: e.data.error.replace(/\D/g, '') }) + : e.data.errorType; + showErrorAlert(messageError); +}; + interface IShowConfirmationAlert { title?: string; message: string; diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx index ea17fee2..10889aac 100644 --- a/app/stacks/InsideStack.tsx +++ b/app/stacks/InsideStack.tsx @@ -111,11 +111,7 @@ const ChatsStackNavigator = () => { - + diff --git a/app/stacks/MasterDetailStack/index.tsx b/app/stacks/MasterDetailStack/index.tsx index bf9a1425..890e52c5 100644 --- a/app/stacks/MasterDetailStack/index.tsx +++ b/app/stacks/MasterDetailStack/index.tsx @@ -151,11 +151,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => { options={props => DirectoryView.navigationOptions!({ ...props, isMasterDetail: true })} /> - + diff --git a/app/views/NotificationPreferencesView/index.tsx b/app/views/NotificationPreferencesView/index.tsx index aa2931f0..7029e5a7 100644 --- a/app/views/NotificationPreferencesView/index.tsx +++ b/app/views/NotificationPreferencesView/index.tsx @@ -1,282 +1,222 @@ -import React from 'react'; -import { StyleSheet, Switch, Text } from 'react-native'; -import { RouteProp } from '@react-navigation/core'; -import { StackNavigationProp } from '@react-navigation/stack'; -import { Observable, Subscription } from 'rxjs'; -import { connect } from 'react-redux'; +import { RouteProp, useNavigation, useRoute } from '@react-navigation/core'; +import React, { useEffect, useState } from 'react'; +import { Switch, Text } from 'react-native'; -import database from '../../lib/database'; -import { SWITCH_TRACK_COLOR, themes } from '../../lib/constants'; -import StatusBar from '../../containers/StatusBar'; +import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet'; +import { CustomIcon } from '../../containers/CustomIcon'; import * as List from '../../containers/List'; -import I18n from '../../i18n'; -import { TSupportedThemes, withTheme } from '../../theme'; -import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import SafeAreaView from '../../containers/SafeAreaView'; -import log, { events, logEvent } from '../../lib/methods/helpers/log'; -import sharedStyles from '../Styles'; -import { IOptionsField, OPTIONS } from './options'; -import { ChatsStackParamList } from '../../stacks/types'; -import { IApplicationState, IRoomNotifications, TRoomNotificationsModel } from '../../definitions'; -import { Services } from '../../lib/services'; +import StatusBar from '../../containers/StatusBar'; +import { IRoomNotifications, TRoomNotificationsModel } from '../../definitions'; +import I18n from '../../i18n'; +import { SWITCH_TRACK_COLOR } from '../../lib/constants'; +import { useAppSelector } from '../../lib/hooks'; +import { showErrorAlertWithEMessage } from '../../lib/methods/helpers'; import { compareServerVersion } from '../../lib/methods/helpers/compareServerVersion'; +import log, { events, logEvent } from '../../lib/methods/helpers/log'; +import { Services } from '../../lib/services'; +import { ChatsStackParamList } from '../../stacks/types'; +import { useTheme } from '../../theme'; +import sharedStyles from '../Styles'; +import { OPTIONS } from './options'; -const styles = StyleSheet.create({ - pickerText: { - ...sharedStyles.textRegular, - fontSize: 16 - } -}); +type TOptions = keyof typeof OPTIONS; +type TRoomNotifications = keyof IRoomNotifications; +type TUnionOptionsRoomNotifications = TOptions | TRoomNotifications; -interface INotificationPreferencesViewProps { - navigation: StackNavigationProp; - route: RouteProp; - theme: TSupportedThemes; - serverVersion: string | null; -} - -interface INotificationPreferencesViewState { +interface IBaseParams { + preference: TUnionOptionsRoomNotifications; room: TRoomNotificationsModel; + onChangeValue: (pref: TUnionOptionsRoomNotifications, param: { [key: string]: string }, onError: () => void) => void; } -class NotificationPreferencesView extends React.Component { - static navigationOptions = () => ({ - title: I18n.t('Notification_Preferences') - }); +const RenderListPicker = ({ + preference, + room, + title, + testID, + onChangeValue +}: { + title: string; + testID: string; +} & IBaseParams) => { + const { showActionSheet, hideActionSheet } = useActionSheet(); + const { colors } = useTheme(); - private mounted: boolean; - private rid: string; - private roomObservable?: Observable; - private subscription?: Subscription; + const pref = room[preference] + ? OPTIONS[preference as TOptions].find(option => option.value === room[preference]) + : OPTIONS[preference as TOptions][0]; - constructor(props: INotificationPreferencesViewProps) { - super(props); - this.mounted = false; - this.rid = props.route.params?.rid ?? ''; - const room = props.route.params?.room; - this.state = { - room: room || {} - }; - if (room && room.observe) { - this.roomObservable = room.observe(); - this.subscription = this.roomObservable.subscribe(changes => { - if (this.mounted) { - this.setState({ room: changes }); - } else { - // @ts-ignore - this.state.room = changes; - } - }); - } - } + const [option, setOption] = useState(pref); - componentDidMount() { - this.mounted = true; - } + const options: TActionSheetOptionsItem[] = OPTIONS[preference as TOptions].map(i => ({ + title: I18n.t(i.label, { defaultValue: i.label, second: i.second }), + onPress: () => { + hideActionSheet(); + onChangeValue(preference, { [preference]: i.value.toString() }, () => setOption(option)); + setOption(i); + }, + right: option?.value === i.value ? () => : undefined + })); - componentWillUnmount() { - if (this.subscription && this.subscription.unsubscribe) { - this.subscription.unsubscribe(); - } - } + return ( + showActionSheet({ options })} + right={() => ( + + {option?.label ? I18n.t(option?.label, { defaultValue: option?.label, second: option?.second }) : option?.label} + + )} + /> + ); +}; - saveNotificationSettings = async (key: string, value: string | boolean, params: IRoomNotifications) => { - // @ts-ignore - logEvent(events[`NP_${key.toUpperCase()}`]); - const { room } = this.state; - const db = database.active; +const RenderSwitch = ({ preference, room, onChangeValue }: IBaseParams) => { + const [switchValue, setSwitchValue] = useState(!room[preference]); + return ( + { + onChangeValue(preference, { [preference]: switchValue ? '1' : '0' }, () => setSwitchValue(switchValue)); + setSwitchValue(value); + }} + /> + ); +}; +const NotificationPreferencesView = (): React.ReactElement => { + const route = useRoute>(); + const { rid, room } = route.params; + const navigation = useNavigation(); + const serverVersion = useAppSelector(state => state.server.version); + const [hideUnreadStatus, setHideUnreadStatus] = useState(room.hideUnreadStatus); + + useEffect(() => { + navigation.setOptions({ + title: I18n.t('Notification_Preferences') + }); + }, []); + + useEffect(() => { + const observe = room.observe(); + observe.subscribe(data => { + setHideUnreadStatus(data.hideUnreadStatus); + }); + }, []); + + const saveNotificationSettings = async (key: TUnionOptionsRoomNotifications, params: IRoomNotifications, onError: Function) => { try { - await db.write(async () => { - await room.update( - protectedFunction((r: IRoomNotifications) => { - r[key] = value; - }) - ); - }); - - try { - const result = await Services.saveNotificationSettings(this.rid, params); - if (result.success) { - return; - } - } catch { - // do nothing - } - - await db.write(async () => { - await room.update( - protectedFunction((r: IRoomNotifications) => { - r[key] = room[key]; - }) - ); - }); + // @ts-ignore + logEvent(events[`NP_${key.toUpperCase()}`]); + await Services.saveNotificationSettings(rid, params); } catch (e) { // @ts-ignore logEvent(events[`NP_${key.toUpperCase()}_F`]); log(e); + onError(); + showErrorAlertWithEMessage(e); } }; - onValueChangeSwitch = (key: string, value: string | boolean) => - this.saveNotificationSettings(key, value, { [key]: value ? '1' : '0' }); + return ( + + + + + + } + /> + + + - onValueChangePicker = (key: string, value: string) => this.saveNotificationSettings(key, value, { [key]: value.toString() }); + + + } + /> + + + - pickerSelection = (title: string, key: string) => { - const { room } = this.state; - const { navigation } = this.props; - navigation.navigate('PickerView', { - title, - data: OPTIONS[key], - value: room[key], - onChangeValue: (value: string) => this.onValueChangePicker(key, value) - }); - }; + + + } + /> + + + - renderPickerOption = (key: string) => { - const { room } = this.state; - const { theme } = this.props; - const text = room[key] ? OPTIONS[key].find(option => option.value === room[key]) : (OPTIONS[key][0] as IOptionsField); - return ( - - {text?.label ? I18n.t(text?.label, { defaultValue: text?.label, second: text?.second }) : text?.label} - - ); - }; - - renderSwitch = (key: string) => { - const { room } = this.state; - return ( - this.onValueChangeSwitch(key, !value)} - /> - ); - }; - - render() { - const { serverVersion } = this.props; - const { room } = this.state; - return ( - - - + {hideUnreadStatus && compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.8.0') ? ( this.renderSwitch('disableNotifications')} + title='Show_badge_for_mentions' + testID='notification-preference-view-badge-for-mentions' + right={() => } /> - + + ) : null} - - - this.renderSwitch('muteGroupMentions')} - /> - - - + + + + + + + + + + + + + + + + + + + + + ); +}; - - - this.renderSwitch('hideUnreadStatus')} - /> - - - - - {room.hideUnreadStatus && compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.8.0') ? ( - - - this.renderSwitch('hideMentionStatus')} - /> - - - - ) : null} - - - - this.pickerSelection(title, 'desktopNotifications')} - right={() => this.renderPickerOption('desktopNotifications')} - /> - - - - - - - this.pickerSelection(title, 'mobilePushNotifications')} - right={() => this.renderPickerOption('mobilePushNotifications')} - /> - - - - - - - this.pickerSelection(title, 'audioNotifications')} - right={() => this.renderPickerOption('audioNotifications')} - /> - - this.pickerSelection(title, 'audioNotificationValue')} - right={() => this.renderPickerOption('audioNotificationValue')} - /> - - this.pickerSelection(title, 'desktopNotificationDuration')} - right={() => this.renderPickerOption('desktopNotificationDuration')} - /> - - - - - - this.pickerSelection(title, 'emailNotifications')} - right={() => this.renderPickerOption('emailNotifications')} - /> - - - - - ); - } -} - -const mapStateToProps = (state: IApplicationState) => ({ - serverVersion: state.server.version -}); - -export default connect(mapStateToProps)(withTheme(NotificationPreferencesView)); +export default NotificationPreferencesView; diff --git a/app/views/NotificationPreferencesView/options.ts b/app/views/NotificationPreferencesView/options.ts index a2b3251c..ee98aebb 100644 --- a/app/views/NotificationPreferencesView/options.ts +++ b/app/views/NotificationPreferencesView/options.ts @@ -4,11 +4,9 @@ export interface IOptionsField { second?: number; } export interface INotificationOptions { - [desktopNotifications: string]: IOptionsField[]; - audioNotifications: IOptionsField[]; + desktopNotifications: IOptionsField[]; mobilePushNotifications: IOptionsField[]; emailNotifications: IOptionsField[]; - desktopNotificationDuration: IOptionsField[]; audioNotificationValue: IOptionsField[]; } @@ -31,24 +29,6 @@ export const OPTIONS: INotificationOptions = { value: 'nothing' } ], - audioNotifications: [ - { - label: 'Default', - value: 'default' - }, - { - label: 'All_Messages', - value: 'all' - }, - { - label: 'Mentions', - value: 'mentions' - }, - { - label: 'Nothing', - value: 'nothing' - } - ], mobilePushNotifications: [ { label: 'Default', @@ -85,37 +65,6 @@ export const OPTIONS: INotificationOptions = { value: 'nothing' } ], - desktopNotificationDuration: [ - { - label: 'Default', - value: 0 - }, - { - label: 'Seconds', - second: 1, - value: 1 - }, - { - label: 'Seconds', - second: 2, - value: 2 - }, - { - label: 'Seconds', - second: 3, - value: 3 - }, - { - label: 'Seconds', - second: 4, - value: 4 - }, - { - label: 'Seconds', - second: 5, - value: 5 - } - ], audioNotificationValue: [ { label: 'None',