diff --git a/native/chat/settings/thread-settings-description.react.js b/native/chat/settings/thread-settings-description.react.js index 7d9b1f338..78c29c4d3 100644 --- a/native/chat/settings/thread-settings-description.react.js +++ b/native/chat/settings/thread-settings-description.react.js @@ -1,304 +1,300 @@ // @flow import invariant from 'invariant'; import { changeThreadSettingsActionTypes, changeThreadSettings, } from 'lib/actions/thread-actions'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors'; import { threadHasPermission } from 'lib/shared/thread-utils'; import type { LoadingStatus } from 'lib/types/loading-types'; -import { loadingStatusPropType } from 'lib/types/loading-types'; import { type ThreadInfo, - threadInfoPropType, threadPermissions, type ChangeThreadSettingsPayload, type UpdateThreadRequest, } from 'lib/types/thread-types'; -import type { DispatchActionPromise } from 'lib/utils/action-utils'; -import { connect } from 'lib/utils/redux-utils'; -import PropTypes from 'prop-types'; +import { + type DispatchActionPromise, + useServerCall, + useDispatchActionPromise, +} from 'lib/utils/action-utils'; import * as React from 'react'; import { Text, Alert, ActivityIndicator, TextInput, View } from 'react-native'; import Icon from 'react-native-vector-icons/FontAwesome'; import Button from '../../components/button.react'; import EditSettingButton from '../../components/edit-setting-button.react'; -import type { AppState } from '../../redux/redux-setup'; -import { - type Colors, - colorsPropType, - colorsSelector, - styleSelector, -} from '../../themes/colors'; +import { useSelector } from '../../redux/redux-utils'; +import { type Colors, useStyles, useColors } from '../../themes/colors'; import type { LayoutEvent, ContentSizeChangeEvent, } from '../../types/react-native'; import SaveSettingButton from './save-setting-button.react'; import { ThreadSettingsCategoryHeader, ThreadSettingsCategoryFooter, } from './thread-settings-category.react'; +type BaseProps = {| + +threadInfo: ThreadInfo, + +descriptionEditValue: ?string, + +setDescriptionEditValue: (value: ?string, callback?: () => void) => void, + +descriptionTextHeight: ?number, + +setDescriptionTextHeight: (number: number) => void, + +canChangeSettings: boolean, +|}; type Props = {| - threadInfo: ThreadInfo, - descriptionEditValue: ?string, - setDescriptionEditValue: (value: ?string, callback?: () => void) => void, - descriptionTextHeight: ?number, - setDescriptionTextHeight: (number: number) => void, - canChangeSettings: boolean, + ...BaseProps, // Redux state - loadingStatus: LoadingStatus, - colors: Colors, - styles: typeof styles, + +loadingStatus: LoadingStatus, + +colors: Colors, + +styles: typeof unboundStyles, // Redux dispatch functions - dispatchActionPromise: DispatchActionPromise, + +dispatchActionPromise: DispatchActionPromise, // async functions that hit server APIs - changeThreadSettings: ( + +changeThreadSettings: ( update: UpdateThreadRequest, ) => Promise, |}; class ThreadSettingsDescription extends React.PureComponent { - static propTypes = { - threadInfo: threadInfoPropType.isRequired, - descriptionEditValue: PropTypes.string, - setDescriptionEditValue: PropTypes.func.isRequired, - descriptionTextHeight: PropTypes.number, - setDescriptionTextHeight: PropTypes.func.isRequired, - canChangeSettings: PropTypes.bool.isRequired, - loadingStatus: loadingStatusPropType.isRequired, - colors: colorsPropType.isRequired, - styles: PropTypes.objectOf(PropTypes.object).isRequired, - dispatchActionPromise: PropTypes.func.isRequired, - changeThreadSettings: PropTypes.func.isRequired, - }; textInput: ?React.ElementRef; render() { if ( this.props.descriptionEditValue !== null && this.props.descriptionEditValue !== undefined ) { let button; if (this.props.loadingStatus !== 'loading') { button = ; } else { button = ( ); } const textInputStyle = {}; if ( this.props.descriptionTextHeight !== undefined && this.props.descriptionTextHeight !== null ) { textInputStyle.height = this.props.descriptionTextHeight; } return ( {button} ); } if (this.props.threadInfo.description) { return ( {this.props.threadInfo.description} ); } const canEditThread = threadHasPermission( this.props.threadInfo, threadPermissions.EDIT_THREAD, ); const { panelIosHighlightUnderlay } = this.props.colors; if (canEditThread) { return ( ); } return null; } textInputRef = (textInput: ?React.ElementRef) => { this.textInput = textInput; }; onLayoutText = (event: LayoutEvent) => { this.props.setDescriptionTextHeight(event.nativeEvent.layout.height); }; onTextInputContentSizeChange = (event: ContentSizeChangeEvent) => { this.props.setDescriptionTextHeight(event.nativeEvent.contentSize.height); }; onPressEdit = () => { this.props.setDescriptionEditValue(this.props.threadInfo.description); }; onSubmit = () => { invariant( this.props.descriptionEditValue !== null && this.props.descriptionEditValue !== undefined, 'should be set', ); const description = this.props.descriptionEditValue.trim(); if (description === this.props.threadInfo.description) { this.props.setDescriptionEditValue(null); return; } const editDescriptionPromise = this.editDescription(description); this.props.dispatchActionPromise( changeThreadSettingsActionTypes, editDescriptionPromise, { customKeyName: `${changeThreadSettingsActionTypes.started}:description`, }, ); editDescriptionPromise.then(() => { this.props.setDescriptionEditValue(null); }); }; async editDescription(newDescription: string) { try { return await this.props.changeThreadSettings({ threadID: this.props.threadInfo.id, changes: { description: newDescription }, }); } catch (e) { Alert.alert( 'Unknown error', 'Uhh... try again?', [{ text: 'OK', onPress: this.onErrorAcknowledged }], { cancelable: false }, ); throw e; } } onErrorAcknowledged = () => { this.props.setDescriptionEditValue( this.props.threadInfo.description, () => { invariant(this.textInput, 'textInput should be set'); this.textInput.focus(); }, ); }; } -const styles = { +const unboundStyles = { addDescriptionButton: { flexDirection: 'row', paddingHorizontal: 24, paddingVertical: 10, }, addDescriptionText: { color: 'panelForegroundTertiaryLabel', flex: 1, fontSize: 16, }, editIcon: { color: 'panelForegroundTertiaryLabel', paddingLeft: 10, textAlign: 'right', }, outlineCategory: { backgroundColor: 'panelSecondaryForeground', borderColor: 'panelSecondaryForegroundBorder', borderRadius: 1, borderStyle: 'dashed', borderWidth: 1, marginLeft: -1, marginRight: -1, }, row: { backgroundColor: 'panelForeground', flexDirection: 'row', paddingHorizontal: 24, paddingVertical: 4, }, text: { color: 'panelForegroundSecondaryLabel', flex: 1, fontFamily: 'Arial', fontSize: 16, margin: 0, padding: 0, borderBottomColor: 'transparent', }, }; -const stylesSelector = styleSelector(styles); const loadingStatusSelector = createLoadingStatusSelector( changeThreadSettingsActionTypes, `${changeThreadSettingsActionTypes.started}:description`, ); -export default connect( - (state: AppState) => ({ - loadingStatus: loadingStatusSelector(state), - colors: colorsSelector(state), - styles: stylesSelector(state), - }), - { changeThreadSettings }, -)(ThreadSettingsDescription); +export default React.memo( + function ConnectedThreadSettingsDescription(props: BaseProps) { + const loadingStatus = useSelector(loadingStatusSelector); + const colors = useColors(); + const styles = useStyles(unboundStyles); + + const dispatchActionPromise = useDispatchActionPromise(); + const callChangeThreadSettings = useServerCall(changeThreadSettings); + return ( + + ); + }, +);