diff --git a/native/avatars/emoji-avatar-creation.react.js b/native/avatars/emoji-avatar-creation.react.js index ea97abd11..6e3910e9e 100644 --- a/native/avatars/emoji-avatar-creation.react.js +++ b/native/avatars/emoji-avatar-creation.react.js @@ -1,256 +1,254 @@ // @flow import * as React from 'react'; import { View, Text, TouchableWithoutFeedback, ActivityIndicator, } from 'react-native'; import EmojiPicker from 'rn-emoji-keyboard'; import { changeThreadSettingsActionTypes } from 'lib/actions/thread-actions.js'; import { updateUserAvatarActionTypes } from 'lib/actions/user-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { savedEmojiAvatarSelectorForThread } from 'lib/selectors/thread-selectors.js'; import { savedEmojiAvatarSelectorForCurrentUser } from 'lib/selectors/user-selectors.js'; import type { ClientEmojiAvatar } from 'lib/types/avatar-types.js'; import Avatar from './avatar.react.js'; import Button from '../components/button.react.js'; import ColorRows from '../components/color-rows.react.js'; -import type { NavigationRoute } from '../navigation/route-names.js'; -import type { ProfileNavigationProp } from '../profile/profile.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; import { useSaveUserAvatar, useSaveThreadAvatar, } from '../utils/avatar-utils.js'; const userAvatarLoadingStatusSelector = createLoadingStatusSelector( updateUserAvatarActionTypes, ); const threadAvatarLoadingStatusSelector = createLoadingStatusSelector( changeThreadSettingsActionTypes, `${changeThreadSettingsActionTypes.started}:avatar`, ); export type EmojiAvatarCreationParams = { +threadID?: string, +containingThreadID?: ?string, }; type Props = { - +navigation: ProfileNavigationProp<'EmojiAvatarCreation'>, - +route: NavigationRoute<'EmojiAvatarCreation'>, + +threadID?: string, + +containingThreadID?: ?string, }; function EmojiAvatarCreation(props: Props): React.Node { - const { threadID, containingThreadID } = props.route.params; + const { threadID, containingThreadID } = props; const selector = threadID ? savedEmojiAvatarSelectorForThread(threadID, containingThreadID) : savedEmojiAvatarSelectorForCurrentUser; const savedEmojiAvatarFunc = useSelector(selector); const [pendingEmoji, setPendingEmoji] = React.useState( () => savedEmojiAvatarFunc().emoji, ); const [pendingColor, setPendingColor] = React.useState( () => savedEmojiAvatarFunc().color, ); const [emojiKeyboardOpen, setEmojiKeyboardOpen] = React.useState(false); const styles = useStyles(unboundStyles); const saveUserAvatar = useSaveUserAvatar(); const saveThreadAvatar = useSaveThreadAvatar(); const saveUserAvatarCallLoading = useSelector( state => userAvatarLoadingStatusSelector(state) === 'loading', ); const saveThreadAvatarCallLoading = useSelector( state => threadAvatarLoadingStatusSelector(state) === 'loading', ); const onPressEditEmoji = React.useCallback(() => { setEmojiKeyboardOpen(true); }, []); const onPressSetAvatar = React.useCallback(() => { const newEmojiAvatarRequest = { type: 'emoji', emoji: pendingEmoji, color: pendingColor, }; if (!threadID) { saveUserAvatar(newEmojiAvatarRequest); } else { saveThreadAvatar(newEmojiAvatarRequest, threadID); } }, [pendingColor, pendingEmoji, saveThreadAvatar, saveUserAvatar, threadID]); const onPressReset = React.useCallback(() => { const resetEmojiAvatar = savedEmojiAvatarFunc(); setPendingEmoji(resetEmojiAvatar.emoji); setPendingColor(resetEmojiAvatar.color); }, [savedEmojiAvatarFunc]); const onEmojiSelected = React.useCallback(emoji => { setPendingEmoji(emoji.emoji); }, []); const onEmojiKeyboardClose = React.useCallback( () => setEmojiKeyboardOpen(false), [], ); const stagedAvatarInfo: ClientEmojiAvatar = React.useMemo( () => ({ type: 'emoji', emoji: pendingEmoji, color: pendingColor, }), [pendingColor, pendingEmoji], ); const loadingContainer = React.useMemo(() => { if (!saveUserAvatarCallLoading && !saveThreadAvatarCallLoading) { return null; } return ( ); }, [ saveThreadAvatarCallLoading, saveUserAvatarCallLoading, styles.loadingContainer, ]); return ( {loadingContainer} Edit Emoji ); } const unboundStyles = { container: { flex: 1, justifyContent: 'space-between', }, emojiAvatarCreationContainer: { paddingTop: 16, }, stagedAvatarSection: { backgroundColor: 'panelForeground', paddingVertical: 24, alignItems: 'center', }, editEmojiText: { color: 'purpleLink', marginTop: 16, fontWeight: '500', fontSize: 16, lineHeight: 24, textAlign: 'center', }, colorRowsSection: { paddingVertical: 24, marginTop: 24, backgroundColor: 'panelForeground', alignItems: 'center', }, selectedColorOuterRing: { backgroundColor: 'modalSubtext', }, buttonsContainer: { paddingHorizontal: 16, paddingBottom: 8, }, saveButton: { backgroundColor: 'purpleButton', paddingVertical: 12, borderRadius: 8, }, saveButtonText: { color: 'whiteText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, resetButton: { padding: 12, borderRadius: 8, marginTop: 8, alignSelf: 'center', }, resetButtonText: { color: 'redText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, loadingContainer: { position: 'absolute', backgroundColor: 'black', width: 112, height: 112, borderRadius: 56, opacity: 0.6, justifyContent: 'center', }, }; export default EmojiAvatarCreation; diff --git a/native/chat/chat.react.js b/native/chat/chat.react.js index e165b4c70..6881cdbf5 100644 --- a/native/chat/chat.react.js +++ b/native/chat/chat.react.js @@ -1,384 +1,384 @@ // @flow import { createMaterialTopTabNavigator, type MaterialTopTabNavigationProp, } from '@react-navigation/material-top-tabs'; import { createNavigatorFactory, useNavigationBuilder, type StackNavigationState, type StackOptions, type StackNavigationEventMap, type StackNavigatorProps, type ExtraStackNavigatorProps, type StackHeaderProps as CoreStackHeaderProps, type StackNavigationProp, type StackNavigationHelpers, type ParamListBase, } from '@react-navigation/native'; import { StackView, type StackHeaderProps } from '@react-navigation/stack'; import invariant from 'invariant'; import * as React from 'react'; import { Platform, View, useWindowDimensions } from 'react-native'; import { useSelector } from 'react-redux'; import ThreadDraftUpdater from 'lib/components/thread-draft-updater.react.js'; import { isLoggedIn } from 'lib/selectors/user-selectors.js'; import { threadIsPending, threadMembersWithoutAddedAshoat, } from 'lib/shared/thread-utils.js'; import BackgroundChatThreadList from './background-chat-thread-list.react.js'; import ChatHeader from './chat-header.react.js'; import ChatRouter, { type ChatRouterNavigationHelpers } from './chat-router.js'; import ComposeSubchannel from './compose-subchannel.react.js'; import ComposeThreadButton from './compose-thread-button.react.js'; import FullScreenThreadMediaGallery from './fullscreen-thread-media-gallery.react.js'; import HomeChatThreadList from './home-chat-thread-list.react.js'; import MessageListContainer from './message-list-container.react.js'; import MessageListHeaderTitle from './message-list-header-title.react.js'; import MessageStorePruner from './message-store-pruner.react.js'; import DeleteThread from './settings/delete-thread.react.js'; +import EmojiThreadAvatarCreation from './settings/emoji-thread-avatar-creation.react.js'; import ThreadSettings from './settings/thread-settings.react.js'; import ThreadScreenPruner from './thread-screen-pruner.react.js'; import ThreadSettingsButton from './thread-settings-button.react.js'; import ThreadSettingsHeaderTitle from './thread-settings-header-title.react.js'; -import EmojiAvatarCreation from '../avatars/emoji-avatar-creation.react.js'; import KeyboardAvoidingView from '../components/keyboard-avoiding-view.react.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import { InputStateContext } from '../input/input-state.js'; import CommunityDrawerButton from '../navigation/community-drawer-button.react.js'; import type { CommunityDrawerNavigationProp } from '../navigation/community-drawer-navigator.react.js'; import HeaderBackButton from '../navigation/header-back-button.react.js'; import { defaultStackScreenOptions } from '../navigation/options.js'; import { ComposeSubchannelRouteName, DeleteThreadRouteName, ThreadSettingsRouteName, - EmojiAvatarCreationRouteName, + EmojiThreadAvatarCreationRouteName, FullScreenThreadMediaGalleryRouteName, MessageListRouteName, ChatThreadListRouteName, HomeChatThreadListRouteName, BackgroundChatThreadListRouteName, type ScreenParamList, type ChatParamList, type ChatTopTabsParamList, } from '../navigation/route-names.js'; import { useColors, useStyles } from '../themes/colors.js'; const unboundStyles = { keyboardAvoidingView: { flex: 1, }, view: { flex: 1, backgroundColor: 'listBackground', }, threadListHeaderStyle: { elevation: 0, shadowOffset: { width: 0, height: 0 }, borderBottomWidth: 0, backgroundColor: 'tabBarBackground', }, }; export type ChatTopTabsNavigationProp< RouteName: $Keys = $Keys, > = MaterialTopTabNavigationProp; const homeChatThreadListOptions = { title: 'Focused', // eslint-disable-next-line react/display-name tabBarIcon: ({ color }) => ( ), }; const backgroundChatThreadListOptions = { title: 'Background', // eslint-disable-next-line react/display-name tabBarIcon: ({ color }) => ( ), }; const ChatThreadsTopTab = createMaterialTopTabNavigator(); function ChatThreadsComponent(): React.Node { const colors = useColors(); const { tabBarBackground, tabBarAccent } = colors; const screenOptions = React.useMemo( () => ({ tabBarShowIcon: true, tabBarStyle: { backgroundColor: tabBarBackground, }, tabBarItemStyle: { flexDirection: 'row', }, tabBarIndicatorStyle: { borderColor: tabBarAccent, borderBottomWidth: 2, }, }), [tabBarAccent, tabBarBackground], ); return ( ); } export type ChatNavigationHelpers = { ...$Exact>, ...ChatRouterNavigationHelpers, }; type ChatNavigatorProps = StackNavigatorProps>; function ChatNavigator({ initialRouteName, children, screenOptions, defaultScreenOptions, screenListeners, id, ...rest }: ChatNavigatorProps) { const { state, descriptors, navigation } = useNavigationBuilder(ChatRouter, { id, initialRouteName, children, screenOptions, defaultScreenOptions, screenListeners, }); // Clear ComposeSubchannel screens after each message is sent. If a user goes // to ComposeSubchannel to create a new thread, but finds an existing one and // uses it instead, we can assume the intent behind opening ComposeSubchannel // is resolved const inputState = React.useContext(InputStateContext); invariant(inputState, 'InputState should be set in ChatNavigator'); const clearComposeScreensAfterMessageSend = React.useCallback(() => { navigation.clearScreens([ComposeSubchannelRouteName]); }, [navigation]); React.useEffect(() => { inputState.registerSendCallback(clearComposeScreensAfterMessageSend); return () => { inputState.unregisterSendCallback(clearComposeScreensAfterMessageSend); }; }, [inputState, clearComposeScreensAfterMessageSend]); return ( ); } const createChatNavigator = createNavigatorFactory< StackNavigationState, StackOptions, StackNavigationEventMap, ChatNavigationHelpers<>, ExtraStackNavigatorProps, >(ChatNavigator); const header = (props: CoreStackHeaderProps) => { // Flow has trouble reconciling identical types between different libdefs, // and flow-typed has no way for one libdef to depend on another const castProps: StackHeaderProps = (props: any); return ; }; const messageListOptions = ({ navigation, route }) => { const isSearchEmpty = !!route.params.searching && threadMembersWithoutAddedAshoat(route.params.threadInfo).length === 1; const areSettingsEnabled = !threadIsPending(route.params.threadInfo.id) && !isSearchEmpty; return { // This is a render prop, not a component // eslint-disable-next-line react/display-name headerTitle: props => ( ), headerRight: areSettingsEnabled ? // This is a render prop, not a component // eslint-disable-next-line react/display-name () => ( ) : undefined, headerBackTitleVisible: false, headerTitleAlign: isSearchEmpty ? 'center' : 'left', headerLeftContainerStyle: { width: Platform.OS === 'ios' ? 32 : 40 }, }; }; const composeThreadOptions = { headerTitle: 'Compose chat', headerBackTitleVisible: false, }; const threadSettingsOptions = ({ route }) => ({ // eslint-disable-next-line react/display-name headerTitle: props => ( ), headerBackTitleVisible: false, }); const emojiAvatarCreationOptions = { headerTitle: 'Emoji avatar selection', headerBackTitleVisible: false, }; const fullScreenThreadMediaGalleryOptions = { headerTitle: 'All Media', headerBackTitleVisible: false, }; const deleteThreadOptions = { headerTitle: 'Delete chat', headerBackTitleVisible: false, }; export type ChatNavigationProp< RouteName: $Keys = $Keys, > = { ...StackNavigationProp, ...ChatRouterNavigationHelpers, }; const Chat = createChatNavigator< ScreenParamList, ChatParamList, ChatNavigationHelpers, >(); type Props = { +navigation: CommunityDrawerNavigationProp<'TabNavigator'>, ... }; export default function ChatComponent(props: Props): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); const loggedIn = useSelector(isLoggedIn); let draftUpdater = null; if (loggedIn) { draftUpdater = ; } const headerLeftButton = React.useCallback( headerProps => { if (headerProps.canGoBack) { return ; } return ; }, [props.navigation], ); const { width: screenWidth } = useWindowDimensions(); const screenOptions = React.useMemo( () => ({ ...defaultStackScreenOptions, header, headerLeft: headerLeftButton, headerStyle: { backgroundColor: colors.tabBarBackground, borderBottomWidth: 1, }, gestureEnabled: true, gestureResponseDistance: screenWidth, }), [colors.tabBarBackground, headerLeftButton, screenWidth], ); const chatThreadListOptions = React.useCallback( ({ navigation }) => ({ headerTitle: 'Inbox', headerRight: Platform.OS === 'ios' ? () => : undefined, headerBackTitleVisible: false, headerStyle: styles.threadListHeaderStyle, }), [styles.threadListHeaderStyle], ); return ( {draftUpdater} ); } diff --git a/native/avatars/emoji-avatar-creation.react.js b/native/chat/settings/emoji-thread-avatar-creation.react.js similarity index 89% copy from native/avatars/emoji-avatar-creation.react.js copy to native/chat/settings/emoji-thread-avatar-creation.react.js index ea97abd11..3efff6c5c 100644 --- a/native/avatars/emoji-avatar-creation.react.js +++ b/native/chat/settings/emoji-thread-avatar-creation.react.js @@ -1,256 +1,256 @@ // @flow import * as React from 'react'; import { View, Text, TouchableWithoutFeedback, ActivityIndicator, } from 'react-native'; import EmojiPicker from 'rn-emoji-keyboard'; import { changeThreadSettingsActionTypes } from 'lib/actions/thread-actions.js'; import { updateUserAvatarActionTypes } from 'lib/actions/user-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { savedEmojiAvatarSelectorForThread } from 'lib/selectors/thread-selectors.js'; import { savedEmojiAvatarSelectorForCurrentUser } from 'lib/selectors/user-selectors.js'; import type { ClientEmojiAvatar } from 'lib/types/avatar-types.js'; -import Avatar from './avatar.react.js'; -import Button from '../components/button.react.js'; -import ColorRows from '../components/color-rows.react.js'; -import type { NavigationRoute } from '../navigation/route-names.js'; -import type { ProfileNavigationProp } from '../profile/profile.react.js'; -import { useSelector } from '../redux/redux-utils.js'; -import { useStyles } from '../themes/colors.js'; +import Avatar from '../../avatars/avatar.react.js'; +import type { ChatNavigationProp } from '../../chat/chat.react.js'; +import Button from '../../components/button.react.js'; +import ColorRows from '../../components/color-rows.react.js'; +import type { NavigationRoute } from '../../navigation/route-names.js'; +import { useSelector } from '../../redux/redux-utils.js'; +import { useStyles } from '../../themes/colors.js'; import { useSaveUserAvatar, useSaveThreadAvatar, -} from '../utils/avatar-utils.js'; +} from '../../utils/avatar-utils.js'; const userAvatarLoadingStatusSelector = createLoadingStatusSelector( updateUserAvatarActionTypes, ); const threadAvatarLoadingStatusSelector = createLoadingStatusSelector( changeThreadSettingsActionTypes, `${changeThreadSettingsActionTypes.started}:avatar`, ); -export type EmojiAvatarCreationParams = { +export type EmojiThreadAvatarCreationParams = { +threadID?: string, +containingThreadID?: ?string, }; type Props = { - +navigation: ProfileNavigationProp<'EmojiAvatarCreation'>, - +route: NavigationRoute<'EmojiAvatarCreation'>, + +navigation: ChatNavigationProp<'EmojiThreadAvatarCreation'>, + +route: NavigationRoute<'EmojiThreadAvatarCreation'>, }; -function EmojiAvatarCreation(props: Props): React.Node { +function EmojiThreadAvatarCreation(props: Props): React.Node { const { threadID, containingThreadID } = props.route.params; const selector = threadID ? savedEmojiAvatarSelectorForThread(threadID, containingThreadID) : savedEmojiAvatarSelectorForCurrentUser; const savedEmojiAvatarFunc = useSelector(selector); const [pendingEmoji, setPendingEmoji] = React.useState( () => savedEmojiAvatarFunc().emoji, ); const [pendingColor, setPendingColor] = React.useState( () => savedEmojiAvatarFunc().color, ); const [emojiKeyboardOpen, setEmojiKeyboardOpen] = React.useState(false); const styles = useStyles(unboundStyles); const saveUserAvatar = useSaveUserAvatar(); const saveThreadAvatar = useSaveThreadAvatar(); const saveUserAvatarCallLoading = useSelector( state => userAvatarLoadingStatusSelector(state) === 'loading', ); const saveThreadAvatarCallLoading = useSelector( state => threadAvatarLoadingStatusSelector(state) === 'loading', ); const onPressEditEmoji = React.useCallback(() => { setEmojiKeyboardOpen(true); }, []); const onPressSetAvatar = React.useCallback(() => { const newEmojiAvatarRequest = { type: 'emoji', emoji: pendingEmoji, color: pendingColor, }; if (!threadID) { saveUserAvatar(newEmojiAvatarRequest); } else { saveThreadAvatar(newEmojiAvatarRequest, threadID); } }, [pendingColor, pendingEmoji, saveThreadAvatar, saveUserAvatar, threadID]); const onPressReset = React.useCallback(() => { const resetEmojiAvatar = savedEmojiAvatarFunc(); setPendingEmoji(resetEmojiAvatar.emoji); setPendingColor(resetEmojiAvatar.color); }, [savedEmojiAvatarFunc]); const onEmojiSelected = React.useCallback(emoji => { setPendingEmoji(emoji.emoji); }, []); const onEmojiKeyboardClose = React.useCallback( () => setEmojiKeyboardOpen(false), [], ); const stagedAvatarInfo: ClientEmojiAvatar = React.useMemo( () => ({ type: 'emoji', emoji: pendingEmoji, color: pendingColor, }), [pendingColor, pendingEmoji], ); const loadingContainer = React.useMemo(() => { if (!saveUserAvatarCallLoading && !saveThreadAvatarCallLoading) { return null; } return ( ); }, [ saveThreadAvatarCallLoading, saveUserAvatarCallLoading, styles.loadingContainer, ]); return ( {loadingContainer} Edit Emoji ); } const unboundStyles = { container: { flex: 1, justifyContent: 'space-between', }, emojiAvatarCreationContainer: { paddingTop: 16, }, stagedAvatarSection: { backgroundColor: 'panelForeground', paddingVertical: 24, alignItems: 'center', }, editEmojiText: { color: 'purpleLink', marginTop: 16, fontWeight: '500', fontSize: 16, lineHeight: 24, textAlign: 'center', }, colorRowsSection: { paddingVertical: 24, marginTop: 24, backgroundColor: 'panelForeground', alignItems: 'center', }, selectedColorOuterRing: { backgroundColor: 'modalSubtext', }, buttonsContainer: { paddingHorizontal: 16, paddingBottom: 8, }, saveButton: { backgroundColor: 'purpleButton', paddingVertical: 12, borderRadius: 8, }, saveButtonText: { color: 'whiteText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, resetButton: { padding: 12, borderRadius: 8, marginTop: 8, alignSelf: 'center', }, resetButtonText: { color: 'redText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, loadingContainer: { position: 'absolute', backgroundColor: 'black', width: 112, height: 112, borderRadius: 56, opacity: 0.6, justifyContent: 'center', }, }; -export default EmojiAvatarCreation; +export default EmojiThreadAvatarCreation; diff --git a/native/chat/settings/thread-settings-avatar.react.js b/native/chat/settings/thread-settings-avatar.react.js index 4a3a60923..fbcfc85c9 100644 --- a/native/chat/settings/thread-settings-avatar.react.js +++ b/native/chat/settings/thread-settings-avatar.react.js @@ -1,57 +1,57 @@ // @flow import { useNavigation } from '@react-navigation/native'; import * as React from 'react'; import { View } from 'react-native'; import { type ResolvedThreadInfo } from 'lib/types/thread-types.js'; import EditThreadAvatar from '../../avatars/edit-thread-avatar.react.js'; -import { EmojiAvatarCreationRouteName } from '../../navigation/route-names.js'; +import { EmojiThreadAvatarCreationRouteName } from '../../navigation/route-names.js'; import { useStyles } from '../../themes/colors.js'; type Props = { +threadInfo: ResolvedThreadInfo, +canChangeSettings: boolean, }; function ThreadSettingsAvatar(props: Props): React.Node { const { threadInfo, canChangeSettings } = props; const { navigate } = useNavigation(); const styles = useStyles(unboundStyles); const onPressEmojiAvatarFlow = React.useCallback(() => { - navigate<'EmojiAvatarCreation'>({ - name: EmojiAvatarCreationRouteName, + navigate<'EmojiThreadAvatarCreation'>({ + name: EmojiThreadAvatarCreationRouteName, params: { threadID: threadInfo.id, containingThreadID: threadInfo.containingThreadID, }, }); }, [navigate, threadInfo.containingThreadID, threadInfo.id]); return ( ); } const unboundStyles = { container: { alignItems: 'center', backgroundColor: 'panelForeground', flex: 1, paddingVertical: 16, }, }; const MemoizedThreadSettingsAvatar: React.ComponentType = React.memo(ThreadSettingsAvatar); export default MemoizedThreadSettingsAvatar; diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js index 14a131e4a..d85576e2b 100644 --- a/native/navigation/route-names.js +++ b/native/navigation/route-names.js @@ -1,195 +1,197 @@ // @flow import type { RouteProp } from '@react-navigation/native'; import type { ActionResultModalParams } from './action-result-modal.react.js'; import type { TermsAndPrivacyModalParams } from '../account/terms-and-privacy-modal.react.js'; -import type { EmojiAvatarCreationParams } from '../avatars/emoji-avatar-creation.react.js'; import type { ThreadPickerModalParams } from '../calendar/thread-picker-modal.react.js'; import type { ComposeSubchannelParams } from '../chat/compose-subchannel.react.js'; import type { FullScreenThreadMediaGalleryParams } from '../chat/fullscreen-thread-media-gallery.react.js'; import type { ImagePasteModalParams } from '../chat/image-paste-modal.react.js'; import type { MessageListParams } from '../chat/message-list-types.js'; import type { MessageReactionsModalParams } from '../chat/message-reactions-modal.react.js'; import type { MultimediaMessageTooltipModalParams } from '../chat/multimedia-message-tooltip-modal.react.js'; import type { RobotextMessageTooltipModalParams } from '../chat/robotext-message-tooltip-modal.react.js'; import type { AddUsersModalParams } from '../chat/settings/add-users-modal.react.js'; import type { ColorSelectorModalParams } from '../chat/settings/color-selector-modal.react.js'; import type { ComposeSubchannelModalParams } from '../chat/settings/compose-subchannel-modal.react.js'; import type { DeleteThreadParams } from '../chat/settings/delete-thread.react.js'; +import type { EmojiThreadAvatarCreationParams } from '../chat/settings/emoji-thread-avatar-creation.react.js'; import type { ThreadSettingsMemberTooltipModalParams } from '../chat/settings/thread-settings-member-tooltip-modal.react.js'; import type { ThreadSettingsParams } from '../chat/settings/thread-settings.react.js'; import type { SidebarListModalParams } from '../chat/sidebar-list-modal.react.js'; import type { SubchannelListModalParams } from '../chat/subchannels-list-modal.react.js'; import type { TextMessageTooltipModalParams } from '../chat/text-message-tooltip-modal.react.js'; import type { CameraModalParams } from '../media/camera-modal.react.js'; import type { ImageModalParams } from '../media/image-modal.react.js'; import type { VideoPlaybackModalParams } from '../media/video-playback-modal.react.js'; import type { CustomServerModalParams } from '../profile/custom-server-modal.react.js'; +import type { EmojiUserAvatarCreationParams } from '../profile/emoji-user-avatar-creation.react.js'; import type { RelationshipListItemTooltipModalParams } from '../profile/relationship-list-item-tooltip-modal.react.js'; export const ActionResultModalRouteName = 'ActionResultModal'; export const AddUsersModalRouteName = 'AddUsersModal'; export const AppearancePreferencesRouteName = 'AppearancePreferences'; export const AppRouteName = 'App'; export const AppsRouteName = 'Apps'; export const BackgroundChatThreadListRouteName = 'BackgroundChatThreadList'; export const BlockListRouteName = 'BlockList'; export const BuildInfoRouteName = 'BuildInfo'; export const CalendarRouteName = 'Calendar'; export const CameraModalRouteName = 'CameraModal'; export const ChatRouteName = 'Chat'; export const ChatThreadListRouteName = 'ChatThreadList'; export const ColorSelectorModalRouteName = 'ColorSelectorModal'; export const ComposeSubchannelModalRouteName = 'ComposeSubchannelModal'; export const ComposeSubchannelRouteName = 'ComposeSubchannel'; export const CommunityDrawerNavigatorRouteName = 'CommunityDrawerNavigator'; export const CustomServerModalRouteName = 'CustomServerModal'; export const DefaultNotificationsPreferencesRouteName = 'DefaultNotifications'; export const DeleteAccountRouteName = 'DeleteAccount'; export const DeleteThreadRouteName = 'DeleteThread'; export const DevToolsRouteName = 'DevTools'; export const EditPasswordRouteName = 'EditPassword'; -export const EmojiAvatarCreationRouteName = 'EmojiAvatarCreation'; +export const EmojiThreadAvatarCreationRouteName = 'EmojiThreadAvatarCreation'; +export const EmojiUserAvatarCreationRouteName = 'EmojiUserAvatarCreation'; export const FriendListRouteName = 'FriendList'; export const FullScreenThreadMediaGalleryRouteName = 'FullScreenThreadMediaGallery'; export const HomeChatThreadListRouteName = 'HomeChatThreadList'; export const ImageModalRouteName = 'ImageModal'; export const ImagePasteModalRouteName = 'ImagePasteModal'; export const LoggedOutModalRouteName = 'LoggedOutModal'; export const MessageListRouteName = 'MessageList'; export const MessageReactionsModalRouteName = 'MessageReactionsModal'; export const MultimediaMessageTooltipModalRouteName = 'MultimediaMessageTooltipModal'; export const PrivacyPreferencesRouteName = 'PrivacyPreferences'; export const ProfileRouteName = 'Profile'; export const ProfileScreenRouteName = 'ProfileScreen'; export const RelationshipListItemTooltipModalRouteName = 'RelationshipListItemTooltipModal'; export const RobotextMessageTooltipModalRouteName = 'RobotextMessageTooltipModal'; export const SidebarListModalRouteName = 'SidebarListModal'; export const SubchannelsListModalRouteName = 'SubchannelsListModal'; export const TabNavigatorRouteName = 'TabNavigator'; export const TextMessageTooltipModalRouteName = 'TextMessageTooltipModal'; export const ThreadPickerModalRouteName = 'ThreadPickerModal'; export const ThreadSettingsMemberTooltipModalRouteName = 'ThreadSettingsMemberTooltipModal'; export const ThreadSettingsRouteName = 'ThreadSettings'; export const VideoPlaybackModalRouteName = 'VideoPlaybackModal'; export const TermsAndPrivacyRouteName = 'TermsAndPrivacyModal'; export type RootParamList = { +LoggedOutModal: void, +App: void, +ThreadPickerModal: ThreadPickerModalParams, +AddUsersModal: AddUsersModalParams, +CustomServerModal: CustomServerModalParams, +ColorSelectorModal: ColorSelectorModalParams, +ComposeSubchannelModal: ComposeSubchannelModalParams, +SidebarListModal: SidebarListModalParams, +ImagePasteModal: ImagePasteModalParams, +TermsAndPrivacyModal: TermsAndPrivacyModalParams, +SubchannelsListModal: SubchannelListModalParams, +MessageReactionsModal: MessageReactionsModalParams, }; export type MessageTooltipRouteNames = | typeof RobotextMessageTooltipModalRouteName | typeof MultimediaMessageTooltipModalRouteName | typeof TextMessageTooltipModalRouteName; export type TooltipModalParamList = { +MultimediaMessageTooltipModal: MultimediaMessageTooltipModalParams, +TextMessageTooltipModal: TextMessageTooltipModalParams, +ThreadSettingsMemberTooltipModal: ThreadSettingsMemberTooltipModalParams, +RelationshipListItemTooltipModal: RelationshipListItemTooltipModalParams, +RobotextMessageTooltipModal: RobotextMessageTooltipModalParams, }; export type OverlayParamList = { +CommunityDrawerNavigator: void, +ImageModal: ImageModalParams, +ActionResultModal: ActionResultModalParams, +CameraModal: CameraModalParams, +VideoPlaybackModal: VideoPlaybackModalParams, ...TooltipModalParamList, }; export type TabParamList = { +Calendar: void, +Chat: void, +Profile: void, +Apps: void, }; export type ChatParamList = { +ChatThreadList: void, +MessageList: MessageListParams, +ComposeSubchannel: ComposeSubchannelParams, +ThreadSettings: ThreadSettingsParams, - +EmojiAvatarCreation: EmojiAvatarCreationParams, + +EmojiThreadAvatarCreation: EmojiThreadAvatarCreationParams, +DeleteThread: DeleteThreadParams, +FullScreenThreadMediaGallery: FullScreenThreadMediaGalleryParams, }; export type ChatTopTabsParamList = { +HomeChatThreadList: void, +BackgroundChatThreadList: void, }; export type ProfileParamList = { +ProfileScreen: void, - +EmojiAvatarCreation: EmojiAvatarCreationParams, + +EmojiUserAvatarCreation: EmojiUserAvatarCreationParams, +EditPassword: void, +DeleteAccount: void, +BuildInfo: void, +DevTools: void, +AppearancePreferences: void, +PrivacyPreferences: void, +DefaultNotifications: void, +FriendList: void, +BlockList: void, }; export type CommunityDrawerParamList = { +TabNavigator: void }; export type ScreenParamList = { ...RootParamList, ...OverlayParamList, ...TabParamList, ...ChatParamList, ...ChatTopTabsParamList, ...ProfileParamList, ...CommunityDrawerParamList, }; export type NavigationRoute> = RouteProp; export const accountModals = [LoggedOutModalRouteName]; export const scrollBlockingModals = [ ImageModalRouteName, MultimediaMessageTooltipModalRouteName, TextMessageTooltipModalRouteName, ThreadSettingsMemberTooltipModalRouteName, RelationshipListItemTooltipModalRouteName, RobotextMessageTooltipModalRouteName, VideoPlaybackModalRouteName, ]; export const chatRootModals = [ AddUsersModalRouteName, ColorSelectorModalRouteName, ComposeSubchannelModalRouteName, ]; export const threadRoutes = [ MessageListRouteName, ThreadSettingsRouteName, DeleteThreadRouteName, ComposeSubchannelRouteName, FullScreenThreadMediaGalleryRouteName, ]; diff --git a/native/avatars/emoji-avatar-creation.react.js b/native/profile/emoji-user-avatar-creation.react.js similarity index 95% copy from native/avatars/emoji-avatar-creation.react.js copy to native/profile/emoji-user-avatar-creation.react.js index ea97abd11..0633bb9d6 100644 --- a/native/avatars/emoji-avatar-creation.react.js +++ b/native/profile/emoji-user-avatar-creation.react.js @@ -1,256 +1,256 @@ // @flow import * as React from 'react'; import { View, Text, TouchableWithoutFeedback, ActivityIndicator, } from 'react-native'; import EmojiPicker from 'rn-emoji-keyboard'; import { changeThreadSettingsActionTypes } from 'lib/actions/thread-actions.js'; import { updateUserAvatarActionTypes } from 'lib/actions/user-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { savedEmojiAvatarSelectorForThread } from 'lib/selectors/thread-selectors.js'; import { savedEmojiAvatarSelectorForCurrentUser } from 'lib/selectors/user-selectors.js'; import type { ClientEmojiAvatar } from 'lib/types/avatar-types.js'; -import Avatar from './avatar.react.js'; +import Avatar from '../avatars/avatar.react.js'; import Button from '../components/button.react.js'; import ColorRows from '../components/color-rows.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import type { ProfileNavigationProp } from '../profile/profile.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; import { useSaveUserAvatar, useSaveThreadAvatar, } from '../utils/avatar-utils.js'; const userAvatarLoadingStatusSelector = createLoadingStatusSelector( updateUserAvatarActionTypes, ); const threadAvatarLoadingStatusSelector = createLoadingStatusSelector( changeThreadSettingsActionTypes, `${changeThreadSettingsActionTypes.started}:avatar`, ); -export type EmojiAvatarCreationParams = { +export type EmojiUserAvatarCreationParams = { +threadID?: string, +containingThreadID?: ?string, }; type Props = { - +navigation: ProfileNavigationProp<'EmojiAvatarCreation'>, - +route: NavigationRoute<'EmojiAvatarCreation'>, + +navigation: ProfileNavigationProp<'EmojiUserAvatarCreation'>, + +route: NavigationRoute<'EmojiUserAvatarCreation'>, }; -function EmojiAvatarCreation(props: Props): React.Node { +function EmojiUserAvatarCreation(props: Props): React.Node { const { threadID, containingThreadID } = props.route.params; const selector = threadID ? savedEmojiAvatarSelectorForThread(threadID, containingThreadID) : savedEmojiAvatarSelectorForCurrentUser; const savedEmojiAvatarFunc = useSelector(selector); const [pendingEmoji, setPendingEmoji] = React.useState( () => savedEmojiAvatarFunc().emoji, ); const [pendingColor, setPendingColor] = React.useState( () => savedEmojiAvatarFunc().color, ); const [emojiKeyboardOpen, setEmojiKeyboardOpen] = React.useState(false); const styles = useStyles(unboundStyles); const saveUserAvatar = useSaveUserAvatar(); const saveThreadAvatar = useSaveThreadAvatar(); const saveUserAvatarCallLoading = useSelector( state => userAvatarLoadingStatusSelector(state) === 'loading', ); const saveThreadAvatarCallLoading = useSelector( state => threadAvatarLoadingStatusSelector(state) === 'loading', ); const onPressEditEmoji = React.useCallback(() => { setEmojiKeyboardOpen(true); }, []); const onPressSetAvatar = React.useCallback(() => { const newEmojiAvatarRequest = { type: 'emoji', emoji: pendingEmoji, color: pendingColor, }; if (!threadID) { saveUserAvatar(newEmojiAvatarRequest); } else { saveThreadAvatar(newEmojiAvatarRequest, threadID); } }, [pendingColor, pendingEmoji, saveThreadAvatar, saveUserAvatar, threadID]); const onPressReset = React.useCallback(() => { const resetEmojiAvatar = savedEmojiAvatarFunc(); setPendingEmoji(resetEmojiAvatar.emoji); setPendingColor(resetEmojiAvatar.color); }, [savedEmojiAvatarFunc]); const onEmojiSelected = React.useCallback(emoji => { setPendingEmoji(emoji.emoji); }, []); const onEmojiKeyboardClose = React.useCallback( () => setEmojiKeyboardOpen(false), [], ); const stagedAvatarInfo: ClientEmojiAvatar = React.useMemo( () => ({ type: 'emoji', emoji: pendingEmoji, color: pendingColor, }), [pendingColor, pendingEmoji], ); const loadingContainer = React.useMemo(() => { if (!saveUserAvatarCallLoading && !saveThreadAvatarCallLoading) { return null; } return ( ); }, [ saveThreadAvatarCallLoading, saveUserAvatarCallLoading, styles.loadingContainer, ]); return ( {loadingContainer} Edit Emoji ); } const unboundStyles = { container: { flex: 1, justifyContent: 'space-between', }, emojiAvatarCreationContainer: { paddingTop: 16, }, stagedAvatarSection: { backgroundColor: 'panelForeground', paddingVertical: 24, alignItems: 'center', }, editEmojiText: { color: 'purpleLink', marginTop: 16, fontWeight: '500', fontSize: 16, lineHeight: 24, textAlign: 'center', }, colorRowsSection: { paddingVertical: 24, marginTop: 24, backgroundColor: 'panelForeground', alignItems: 'center', }, selectedColorOuterRing: { backgroundColor: 'modalSubtext', }, buttonsContainer: { paddingHorizontal: 16, paddingBottom: 8, }, saveButton: { backgroundColor: 'purpleButton', paddingVertical: 12, borderRadius: 8, }, saveButtonText: { color: 'whiteText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, resetButton: { padding: 12, borderRadius: 8, marginTop: 8, alignSelf: 'center', }, resetButtonText: { color: 'redText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, loadingContainer: { position: 'absolute', backgroundColor: 'black', width: 112, height: 112, borderRadius: 56, opacity: 0.6, justifyContent: 'center', }, }; -export default EmojiAvatarCreation; +export default EmojiUserAvatarCreation; diff --git a/native/profile/profile-screen.react.js b/native/profile/profile-screen.react.js index d76b0b93b..c026b4c92 100644 --- a/native/profile/profile-screen.react.js +++ b/native/profile/profile-screen.react.js @@ -1,435 +1,435 @@ // @flow import * as React from 'react'; import { View, Text, Alert, Platform, ScrollView } from 'react-native'; import { logOutActionTypes, logOut } from 'lib/actions/user-actions.js'; import { useStringForUser } from 'lib/hooks/ens-cache.js'; import { preRequestUserStateSelector } from 'lib/selectors/account-selectors.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { accountHasPassword } from 'lib/shared/account-utils.js'; import type { LogOutResult } from 'lib/types/account-types.js'; import { type PreRequestUserState } from 'lib/types/session-types.js'; import { type CurrentUserInfo } from 'lib/types/user-types.js'; import { type DispatchActionPromise, useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; import { deleteNativeCredentialsFor } from '../account/native-credentials.js'; import EditUserAvatar from '../avatars/edit-user-avatar.react.js'; import Action from '../components/action-row.react.js'; import Button from '../components/button.react.js'; import EditSettingButton from '../components/edit-setting-button.react.js'; import { SingleLine } from '../components/single-line.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { EditPasswordRouteName, - EmojiAvatarCreationRouteName, + EmojiUserAvatarCreationRouteName, DeleteAccountRouteName, BuildInfoRouteName, DevToolsRouteName, AppearancePreferencesRouteName, FriendListRouteName, BlockListRouteName, PrivacyPreferencesRouteName, DefaultNotificationsPreferencesRouteName, } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { type Colors, useColors, useStyles } from '../themes/colors.js'; import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; import { useStaffCanSee } from '../utils/staff-utils.js'; type ProfileRowProps = { +content: string, +onPress: () => void, +danger?: boolean, }; function ProfileRow(props: ProfileRowProps): React.Node { const { content, onPress, danger } = props; return ( ); } type BaseProps = { +navigation: ProfileNavigationProp<'ProfileScreen'>, +route: NavigationRoute<'ProfileScreen'>, }; type Props = { ...BaseProps, +currentUserInfo: ?CurrentUserInfo, +preRequestUserState: PreRequestUserState, +logOutLoading: boolean, +colors: Colors, +styles: typeof unboundStyles, +dispatchActionPromise: DispatchActionPromise, +logOut: (preRequestUserState: PreRequestUserState) => Promise, +staffCanSee: boolean, +stringForUser: ?string, +isAccountWithPassword: boolean, +shouldRenderAvatars: boolean, }; class ProfileScreen extends React.PureComponent { get loggedOutOrLoggingOut() { return ( !this.props.currentUserInfo || this.props.currentUserInfo.anonymous || this.props.logOutLoading ); } render() { let developerTools, defaultNotifications; const { staffCanSee } = this.props; if (staffCanSee) { developerTools = ( ); defaultNotifications = ( ); } let passwordEditionUI; if (accountHasPassword(this.props.currentUserInfo)) { passwordEditionUI = ( Password •••••••••••••••• ); } let avatarSection; if (this.props.shouldRenderAvatars) { avatarSection = ( <> USER AVATAR ); } return ( {avatarSection} ACCOUNT Logged in as {this.props.stringForUser} {passwordEditionUI} PREFERENCES {defaultNotifications} {developerTools} ); } onPressEmojiAvatarFlow = () => { - this.props.navigation.navigate<'EmojiAvatarCreation'>({ - name: EmojiAvatarCreationRouteName, + this.props.navigation.navigate<'EmojiUserAvatarCreation'>({ + name: EmojiUserAvatarCreationRouteName, params: {}, }); }; onPressLogOut = () => { if (this.loggedOutOrLoggingOut) { return; } if (!this.props.isAccountWithPassword) { Alert.alert( 'Log out', 'Are you sure you want to log out?', [ { text: 'No', style: 'cancel' }, { text: 'Yes', onPress: this.logOutWithoutDeletingNativeCredentialsWrapper, style: 'destructive', }, ], { cancelable: true }, ); return; } const alertTitle = Platform.OS === 'ios' ? 'Keep Login Info in Keychain' : 'Keep Login Info'; const alertDescription = 'We will automatically fill out log-in forms with your credentials ' + 'in the app.'; Alert.alert( alertTitle, alertDescription, [ { text: 'Cancel', style: 'cancel' }, { text: 'Keep', onPress: this.logOutWithoutDeletingNativeCredentialsWrapper, }, { text: 'Remove', onPress: this.logOutAndDeleteNativeCredentialsWrapper, style: 'destructive', }, ], { cancelable: true }, ); }; logOutWithoutDeletingNativeCredentialsWrapper = () => { if (this.loggedOutOrLoggingOut) { return; } this.logOut(); }; logOutAndDeleteNativeCredentialsWrapper = async () => { if (this.loggedOutOrLoggingOut) { return; } await this.deleteNativeCredentials(); this.logOut(); }; logOut() { this.props.dispatchActionPromise( logOutActionTypes, this.props.logOut(this.props.preRequestUserState), ); } async deleteNativeCredentials() { await deleteNativeCredentialsFor(); } navigateIfActive(name) { this.props.navigation.navigate({ name }); } onPressEditPassword = () => { this.navigateIfActive(EditPasswordRouteName); }; onPressDeleteAccount = () => { this.navigateIfActive(DeleteAccountRouteName); }; onPressBuildInfo = () => { this.navigateIfActive(BuildInfoRouteName); }; onPressDevTools = () => { this.navigateIfActive(DevToolsRouteName); }; onPressAppearance = () => { this.navigateIfActive(AppearancePreferencesRouteName); }; onPressPrivacy = () => { this.navigateIfActive(PrivacyPreferencesRouteName); }; onPressDefaultNotifications = () => { this.navigateIfActive(DefaultNotificationsPreferencesRouteName); }; onPressFriendList = () => { this.navigateIfActive(FriendListRouteName); }; onPressBlockList = () => { this.navigateIfActive(BlockListRouteName); }; } const unboundStyles = { avatarSection: { alignItems: 'center', paddingVertical: 16, }, container: { flex: 1, }, content: { flex: 1, }, deleteAccountButton: { paddingHorizontal: 24, paddingVertical: 12, }, editPasswordButton: { paddingTop: Platform.OS === 'android' ? 3 : 2, }, header: { color: 'panelBackgroundLabel', fontSize: 12, fontWeight: '400', paddingBottom: 3, paddingHorizontal: 24, }, label: { color: 'panelForegroundTertiaryLabel', fontSize: 16, paddingRight: 12, }, loggedInLabel: { color: 'panelForegroundTertiaryLabel', fontSize: 16, }, logOutText: { color: 'link', fontSize: 16, paddingLeft: 6, }, row: { flex: 1, flexDirection: 'row', justifyContent: 'space-between', }, scrollView: { backgroundColor: 'panelBackground', }, scrollViewContentContainer: { paddingTop: 24, }, paddedRow: { flex: 1, flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 10, }, section: { backgroundColor: 'panelForeground', borderBottomWidth: 1, borderColor: 'panelForegroundBorder', borderTopWidth: 1, marginBottom: 24, paddingVertical: 1, }, unpaddedSection: { backgroundColor: 'panelForeground', borderBottomWidth: 1, borderColor: 'panelForegroundBorder', borderTopWidth: 1, marginBottom: 24, }, username: { color: 'panelForegroundLabel', flex: 1, }, value: { color: 'panelForegroundLabel', fontSize: 16, textAlign: 'right', }, }; const logOutLoadingStatusSelector = createLoadingStatusSelector(logOutActionTypes); const ConnectedProfileScreen: React.ComponentType = React.memo(function ConnectedProfileScreen(props: BaseProps) { const currentUserInfo = useSelector(state => state.currentUserInfo); const preRequestUserState = useSelector(preRequestUserStateSelector); const logOutLoading = useSelector(logOutLoadingStatusSelector) === 'loading'; const colors = useColors(); const styles = useStyles(unboundStyles); const callLogOut = useServerCall(logOut); const dispatchActionPromise = useDispatchActionPromise(); const staffCanSee = useStaffCanSee(); const stringForUser = useStringForUser(currentUserInfo); const isAccountWithPassword = useSelector(state => accountHasPassword(state.currentUserInfo), ); const shouldRenderAvatars = useShouldRenderAvatars(); return ( ); }); export default ConnectedProfileScreen; diff --git a/native/profile/profile.react.js b/native/profile/profile.react.js index 6d750afc8..97dd70525 100644 --- a/native/profile/profile.react.js +++ b/native/profile/profile.react.js @@ -1,183 +1,183 @@ // @flow import { createStackNavigator, type StackNavigationProp, type StackNavigationHelpers, type StackHeaderProps, } from '@react-navigation/stack'; import * as React from 'react'; import { View, useWindowDimensions } from 'react-native'; import AppearancePreferences from './appearance-preferences.react.js'; import BuildInfo from './build-info.react.js'; import DefaultNotificationsPreferences from './default-notifications-preferences.react.js'; import DeleteAccount from './delete-account.react.js'; import DevTools from './dev-tools.react.js'; import EditPassword from './edit-password.react.js'; +import EmojiUserAvatarCreation from './emoji-user-avatar-creation.react.js'; import PrivacyPreferences from './privacy-preferences.react.js'; import ProfileHeader from './profile-header.react.js'; import ProfileScreen from './profile-screen.react.js'; import RelationshipList from './relationship-list.react.js'; -import EmojiAvatarCreation from '../avatars/emoji-avatar-creation.react.js'; import KeyboardAvoidingView from '../components/keyboard-avoiding-view.react.js'; import CommunityDrawerButton from '../navigation/community-drawer-button.react.js'; import type { CommunityDrawerNavigationProp } from '../navigation/community-drawer-navigator.react.js'; import HeaderBackButton from '../navigation/header-back-button.react.js'; import { ProfileScreenRouteName, EditPasswordRouteName, DeleteAccountRouteName, - EmojiAvatarCreationRouteName, + EmojiUserAvatarCreationRouteName, BuildInfoRouteName, DevToolsRouteName, AppearancePreferencesRouteName, PrivacyPreferencesRouteName, FriendListRouteName, DefaultNotificationsPreferencesRouteName, BlockListRouteName, type ScreenParamList, type ProfileParamList, } from '../navigation/route-names.js'; import { useStyles, useColors } from '../themes/colors.js'; const header = (props: StackHeaderProps) => ; const profileScreenOptions = { headerTitle: 'Profile' }; const emojiAvatarCreationOptions = { headerTitle: 'Emoji avatar selection', headerBackTitleVisible: false, }; const editPasswordOptions = { headerTitle: 'Change password' }; const deleteAccountOptions = { headerTitle: 'Delete account' }; const buildInfoOptions = { headerTitle: 'Build info' }; const devToolsOptions = { headerTitle: 'Developer tools' }; const appearanceOptions = { headerTitle: 'Appearance' }; const privacyOptions = { headerTitle: 'Privacy' }; const friendListOptions = { headerTitle: 'Friend list' }; const blockListOptions = { headerTitle: 'Block list' }; const defaultNotificationsOptions = { headerTitle: 'Default Notifications' }; export type ProfileNavigationProp< RouteName: $Keys = $Keys, > = StackNavigationProp; const Profile = createStackNavigator< ScreenParamList, ProfileParamList, StackNavigationHelpers, >(); type Props = { +navigation: CommunityDrawerNavigationProp<'TabNavigator'>, ... }; function ProfileComponent(props: Props): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); const headerLeftButton = React.useCallback( headerProps => headerProps.canGoBack ? ( ) : ( ), [props.navigation], ); const { width: screenWidth } = useWindowDimensions(); const screenOptions = React.useMemo( () => ({ header, headerLeft: headerLeftButton, headerStyle: { backgroundColor: colors.tabBarBackground, shadowOpacity: 0, }, gestureEnabled: true, gestureResponseDistance: screenWidth, }), [colors.tabBarBackground, headerLeftButton, screenWidth], ); return ( ); } const unboundStyles = { keyboardAvoidingView: { flex: 1, }, view: { flex: 1, backgroundColor: 'panelBackground', }, }; export default ProfileComponent;