diff --git a/app/lib/methods/helpers/showToast.ts b/app/lib/methods/helpers/showToast.ts new file mode 100644 index 000000000..77400b165 --- /dev/null +++ b/app/lib/methods/helpers/showToast.ts @@ -0,0 +1,4 @@ +import { LISTENER } from '../../../containers/Toast'; +import EventEmitter from '../../../utils/events'; + +export const showToast = (message: string): void => EventEmitter.emit(LISTENER, { message }); diff --git a/app/views/StatusView.tsx b/app/views/StatusView.tsx deleted file mode 100644 index ca44c2a15..000000000 --- a/app/views/StatusView.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import React from 'react'; -import { FlatList, StyleSheet } from 'react-native'; -import { connect } from 'react-redux'; - -import { setUser } from '../actions/login'; -import * as HeaderButton from '../containers/HeaderButton'; -import * as List from '../containers/List'; -import Loading from '../containers/Loading'; -import SafeAreaView from '../containers/SafeAreaView'; -import Status from '../containers/Status/Status'; -import TextInput from '../containers/TextInput'; -import { LISTENER } from '../containers/Toast'; -import { IApplicationState, IBaseScreen, IUser, TUserStatus } from '../definitions'; -import I18n from '../i18n'; -import { Services } from '../lib/services'; -import { getUserSelector } from '../selectors/login'; -import { withTheme } from '../theme'; -import EventEmitter from '../utils/events'; -import { showErrorAlert } from '../utils/info'; -import log, { events, logEvent } from '../utils/log'; - -interface IStatus { - id: TUserStatus; - name: string; -} - -const STATUS: IStatus[] = [ - { - id: 'online', - name: 'Online' - }, - { - id: 'busy', - name: 'Busy' - }, - { - id: 'away', - name: 'Away' - }, - { - id: 'offline', - name: 'Invisible' - } -]; - -const styles = StyleSheet.create({ - inputContainer: { - marginTop: 32, - marginBottom: 32 - }, - inputLeft: { - position: 'absolute', - top: 12, - left: 12 - }, - inputStyle: { - paddingLeft: 48 - } -}); - -interface IStatusViewState { - statusText: string; - loading: boolean; -} - -interface IStatusViewProps extends IBaseScreen { - user: IUser; - isMasterDetail: boolean; - Accounts_AllowInvisibleStatusOption: boolean; -} - -class StatusView extends React.Component { - constructor(props: IStatusViewProps) { - super(props); - const { statusText } = props.user; - this.state = { statusText: statusText || '', loading: false }; - this.setHeader(); - } - - setHeader = () => { - const { navigation, isMasterDetail } = this.props; - navigation.setOptions({ - title: I18n.t('Edit_Status'), - headerLeft: isMasterDetail ? undefined : () => , - headerRight: () => ( - - - - ) - }); - }; - - submit = async () => { - logEvent(events.STATUS_DONE); - const { statusText } = this.state; - const { user } = this.props; - if (statusText !== user.statusText) { - await this.setCustomStatus(statusText); - } - this.close(); - }; - - close = () => { - const { navigation } = this.props; - navigation.goBack(); - }; - - setCustomStatus = async (statusText: string) => { - const { user, dispatch } = this.props; - - this.setState({ loading: true }); - - try { - const result = await Services.setUserStatus(user.status, statusText); - if (result.success) { - logEvent(events.STATUS_CUSTOM); - dispatch(setUser({ statusText })); - EventEmitter.emit(LISTENER, { message: I18n.t('Status_saved_successfully') }); - } else { - logEvent(events.STATUS_CUSTOM_F); - EventEmitter.emit(LISTENER, { message: I18n.t('error-could-not-change-status') }); - } - } catch { - logEvent(events.STATUS_CUSTOM_F); - EventEmitter.emit(LISTENER, { message: I18n.t('error-could-not-change-status') }); - } - - this.setState({ loading: false }); - }; - - renderHeader = () => { - const { statusText } = this.state; - const { user, theme } = this.props; - - return ( - <> - this.setState({ statusText: text })} - left={} - inputStyle={styles.inputStyle} - placeholder={I18n.t('What_are_you_doing_right_now')} - testID='status-view-input' - /> - - - ); - }; - - renderItem = ({ item }: { item: IStatus }) => { - const { statusText } = this.state; - const { user, dispatch } = this.props; - const { id, name } = item; - return ( - { - // @ts-ignore - logEvent(events[`STATUS_${item.id.toUpperCase()}`]); - if (user.status !== item.id) { - try { - const result = await Services.setUserStatus(item.id, statusText); - if (result.success) { - dispatch(setUser({ status: item.id })); - } - } catch (e: any) { - 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); - logEvent(events.SET_STATUS_FAIL); - log(e); - } - } - }} - testID={`status-view-${id}`} - left={() => } - /> - ); - }; - - render() { - const { loading } = this.state; - const { Accounts_AllowInvisibleStatusOption } = this.props; - - const status = Accounts_AllowInvisibleStatusOption ? STATUS : STATUS.filter(s => s.id !== 'offline'); - - return ( - - item.id} - renderItem={this.renderItem} - ListHeaderComponent={this.renderHeader} - ListFooterComponent={List.Separator} - ItemSeparatorComponent={List.Separator} - /> - - - ); - } -} - -const mapStateToProps = (state: IApplicationState) => ({ - user: getUserSelector(state), - isMasterDetail: state.app.isMasterDetail, - Accounts_AllowInvisibleStatusOption: (state.settings.Accounts_AllowInvisibleStatusOption as boolean) ?? true -}); - -export default connect(mapStateToProps)(withTheme(StatusView)); diff --git a/app/views/StatusView/index.tsx b/app/views/StatusView/index.tsx new file mode 100644 index 000000000..5f02d7c18 --- /dev/null +++ b/app/views/StatusView/index.tsx @@ -0,0 +1,193 @@ +import { useNavigation } from '@react-navigation/native'; +import React, { useEffect, useState } from 'react'; +import { FlatList, StyleSheet } from 'react-native'; +import { useDispatch, useSelector } from 'react-redux'; + +import { setUser } from '../../actions/login'; +import * as HeaderButton from '../../containers/HeaderButton'; +import * as List from '../../containers/List'; +import Loading from '../../containers/Loading'; +import SafeAreaView from '../../containers/SafeAreaView'; +import StatusIcon from '../../containers/Status/Status'; +import TextInput from '../../containers/TextInput'; +import { IApplicationState, TUserStatus } from '../../definitions'; +import I18n from '../../i18n'; +import { showToast } from '../../lib/methods/helpers/showToast'; +import { Services } from '../../lib/services'; +import { getUserSelector } from '../../selectors/login'; +import { useTheme } from '../../theme'; +import { showErrorAlert } from '../../utils/info'; +import log, { events, logEvent } from '../../utils/log'; + +interface IStatus { + id: TUserStatus; + name: string; +} + +const STATUS: IStatus[] = [ + { + id: 'online', + name: 'Online' + }, + { + id: 'busy', + name: 'Busy' + }, + { + id: 'away', + name: 'Away' + }, + { + id: 'offline', + name: 'Invisible' + } +]; + +const styles = StyleSheet.create({ + inputContainer: { + marginTop: 32, + marginBottom: 32 + }, + inputLeft: { + position: 'absolute', + top: 12, + left: 12 + }, + inputStyle: { + paddingLeft: 48 + } +}); + +const Status = ({ status, statusText }: { status: IStatus; statusText: string }) => { + const user = useSelector((state: IApplicationState) => getUserSelector(state)); + const dispatch = useDispatch(); + + const { id, name } = status; + return ( + { + const key = `STATUS_${status.id.toUpperCase()}` as keyof typeof events; + logEvent(events[key]); + if (user.status !== status.id) { + try { + const result = await Services.setUserStatus(status.id, statusText); + if (result.success) { + dispatch(setUser({ status: status.id })); + } + } catch (e: any) { + 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); + logEvent(events.SET_STATUS_FAIL); + log(e); + } + } + }} + testID={`status-view-${id}`} + left={() => } + /> + ); +}; + +const StatusView = (): React.ReactElement => { + const user = useSelector((state: IApplicationState) => getUserSelector(state)); + const isMasterDetail = useSelector((state: IApplicationState) => state.app.isMasterDetail); + const Accounts_AllowInvisibleStatusOption = useSelector( + (state: IApplicationState) => state.settings.Accounts_AllowInvisibleStatusOption + ); + + const [statusText, setStatusText] = useState(user.statusText || ''); + const [loading, setLoading] = useState(false); + + const dispatch = useDispatch(); + const { setOptions, goBack } = useNavigation(); + + const { theme } = useTheme(); + + useEffect(() => { + const submit = async () => { + logEvent(events.STATUS_DONE); + if (statusText !== user.statusText) { + await setCustomStatus(statusText); + } + goBack(); + }; + const setHeader = () => { + setOptions({ + title: I18n.t('Edit_Status'), + headerLeft: isMasterDetail ? undefined : () => , + headerRight: () => ( + + + + ) + }); + }; + setHeader(); + }, [statusText, user.status]); + + const setCustomStatus = async (statusText: string) => { + setLoading(true); + try { + const result = await Services.setUserStatus(user.status, statusText); + if (result.success) { + dispatch(setUser({ statusText })); + logEvent(events.STATUS_CUSTOM); + showToast(I18n.t('Status_saved_successfully')); + } else { + logEvent(events.STATUS_CUSTOM_F); + showToast(I18n.t('error-could-not-change-status')); + } + } catch (e: any) { + 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; + logEvent(events.STATUS_CUSTOM_F); + showErrorAlert(messageError); + } + setLoading(false); + }; + + const status = Accounts_AllowInvisibleStatusOption ? STATUS : STATUS.filter(s => s.id !== 'offline'); + + return ( + + item.id} + renderItem={({ item }) => } + ListHeaderComponent={ + <> + setStatusText(text)} + left={ + + } + inputStyle={styles.inputStyle} + placeholder={I18n.t('What_are_you_doing_right_now')} + testID='status-view-input' + /> + + + } + ListFooterComponent={List.Separator} + ItemSeparatorComponent={List.Separator} + /> + + + ); +}; + +export default StatusView;