diff --git a/native/chat/message-result.react.js b/native/chat/message-result.react.js
new file mode 100644
index 000000000..fa7b4bcd7
--- /dev/null
+++ b/native/chat/message-result.react.js
@@ -0,0 +1,20 @@
+// @flow
+
+import * as React from 'react';
+import { View } from 'react-native';
+
+import { type ThreadInfo } from 'lib/types/thread-types.js';
+
+import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
+
+type MessageResultProps = {
+ +item: ChatMessageInfoItemWithHeight,
+ +threadInfo: ThreadInfo,
+};
+
+/* eslint-disable no-unused-vars */
+function MessageResult(props: MessageResultProps): React.Node {
+ return ;
+}
+
+export default MessageResult;
diff --git a/native/chat/multimedia-message-tooltip-modal.react.js b/native/chat/multimedia-message-tooltip-modal.react.js
index 3d9c86631..d300b99f0 100644
--- a/native/chat/multimedia-message-tooltip-modal.react.js
+++ b/native/chat/multimedia-message-tooltip-modal.react.js
@@ -1,94 +1,99 @@
// @flow
import * as React from 'react';
import { useOnPressReport } from './message-report-utils.js';
import MultimediaMessageTooltipButton from './multimedia-message-tooltip-button.react.js';
import { useAnimatedNavigateToSidebar } from './sidebar-navigation.js';
import CommIcon from '../components/comm-icon.react.js';
import SWMansionIcon from '../components/swmansion-icon.react.js';
+import { OverlayContext } from '../navigation/overlay-context.js';
import {
createTooltip,
type TooltipParams,
type BaseTooltipProps,
type TooltipMenuProps,
} from '../tooltip/tooltip.react.js';
import type { ChatMultimediaMessageInfoItem } from '../types/chat-types.js';
import type { VerticalBounds } from '../types/layout-types.js';
+import { useNavigateToPinModal } from '../utils/toggle-pin-utils.js';
export type MultimediaMessageTooltipModalParams = TooltipParams<{
+item: ChatMultimediaMessageInfoItem,
+verticalBounds: VerticalBounds,
}>;
function TooltipMenu(
props: TooltipMenuProps<'MultimediaMessageTooltipModal'>,
): React.Node {
const { route, tooltipItem: TooltipItem } = props;
- const onPressTogglePin = React.useCallback(() => {}, []);
+ const overlayContext = React.useContext(OverlayContext);
+
+ const onPressTogglePin = useNavigateToPinModal(overlayContext, route);
+
const renderPinIcon = React.useCallback(
style => ,
[],
);
const renderUnpinIcon = React.useCallback(
style => ,
[],
);
const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item);
const renderSidebarIcon = React.useCallback(
style => (
),
[],
);
const onPressReport = useOnPressReport(route);
const renderReportIcon = React.useCallback(
style => ,
[],
);
return (
<>
>
);
}
const MultimediaMessageTooltipModal: React.ComponentType<
BaseTooltipProps<'MultimediaMessageTooltipModal'>,
> = createTooltip<'MultimediaMessageTooltipModal'>(
MultimediaMessageTooltipButton,
TooltipMenu,
);
export default MultimediaMessageTooltipModal;
diff --git a/native/chat/text-message-tooltip-modal.react.js b/native/chat/text-message-tooltip-modal.react.js
index 40675a48b..977c20114 100644
--- a/native/chat/text-message-tooltip-modal.react.js
+++ b/native/chat/text-message-tooltip-modal.react.js
@@ -1,174 +1,178 @@
// @flow
import Clipboard from '@react-native-clipboard/clipboard';
import invariant from 'invariant';
import * as React from 'react';
import { createMessageReply } from 'lib/shared/message-utils.js';
import { useOnPressReport } from './message-report-utils.js';
import { useAnimatedNavigateToSidebar } from './sidebar-navigation.js';
import TextMessageTooltipButton from './text-message-tooltip-button.react.js';
import CommIcon from '../components/comm-icon.react.js';
import SWMansionIcon from '../components/swmansion-icon.react.js';
import { InputStateContext } from '../input/input-state.js';
import { displayActionResultModal } from '../navigation/action-result-modal.js';
+import { OverlayContext } from '../navigation/overlay-context.js';
import {
createTooltip,
type TooltipParams,
type BaseTooltipProps,
type TooltipMenuProps,
} from '../tooltip/tooltip.react.js';
import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types.js';
import { exitEditAlert } from '../utils/edit-messages-utils.js';
+import { useNavigateToPinModal } from '../utils/toggle-pin-utils.js';
export type TextMessageTooltipModalParams = TooltipParams<{
+item: ChatTextMessageInfoItemWithHeight,
}>;
const confirmCopy = () => displayActionResultModal('copied!');
function TooltipMenu(
props: TooltipMenuProps<'TextMessageTooltipModal'>,
): React.Node {
const { route, tooltipItem: TooltipItem } = props;
+ const overlayContext = React.useContext(OverlayContext);
const inputState = React.useContext(InputStateContext);
const { text } = route.params.item.messageInfo;
const onPressReply = React.useCallback(() => {
invariant(
inputState,
'inputState should be set in TextMessageTooltipModal.onPressReply',
);
inputState.editInputMessage({
message: createMessageReply(text),
mode: 'prepend',
});
}, [inputState, text]);
const renderReplyIcon = React.useCallback(
style => ,
[],
);
const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item);
const renderSidebarIcon = React.useCallback(
style => (
),
[],
);
const { messageInfo } = route.params.item;
const onPressEdit = React.useCallback(() => {
invariant(
inputState,
'inputState should be set in TextMessageTooltipModal.onPressEdit',
);
const updateInputBar = () => {
inputState.editInputMessage({
message: text,
mode: 'replace',
});
};
const enterEditMode = () => {
inputState.setEditedMessage(messageInfo, updateInputBar);
};
if (inputState.editState.editedMessage) {
exitEditAlert(enterEditMode);
} else {
enterEditMode();
}
}, [inputState, messageInfo, text]);
const renderEditIcon = React.useCallback(
style => ,
[],
);
- const onPressTogglePin = React.useCallback(() => {}, []);
+ const onPressTogglePin = useNavigateToPinModal(overlayContext, route);
+
const renderPinIcon = React.useCallback(
style => ,
[],
);
const renderUnpinIcon = React.useCallback(
style => ,
[],
);
const onPressCopy = React.useCallback(() => {
Clipboard.setString(text);
setTimeout(confirmCopy);
}, [text]);
const renderCopyIcon = React.useCallback(
style => ,
[],
);
const onPressReport = useOnPressReport(route);
const renderReportIcon = React.useCallback(
style => ,
[],
);
return (
<>
>
);
}
const TextMessageTooltipModal: React.ComponentType<
BaseTooltipProps<'TextMessageTooltipModal'>,
> = createTooltip<'TextMessageTooltipModal'>(
TextMessageTooltipButton,
TooltipMenu,
);
export default TextMessageTooltipModal;
diff --git a/native/chat/toggle-pin-modal.react.js b/native/chat/toggle-pin-modal.react.js
new file mode 100644
index 000000000..1311bef2b
--- /dev/null
+++ b/native/chat/toggle-pin-modal.react.js
@@ -0,0 +1,198 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+import { Text, View } from 'react-native';
+
+import {
+ toggleMessagePin,
+ toggleMessagePinActionTypes,
+} from 'lib/actions/thread-actions.js';
+import { type ThreadInfo } from 'lib/types/thread-types.js';
+import {
+ useServerCall,
+ useDispatchActionPromise,
+} from 'lib/utils/action-utils.js';
+
+import MessageResult from './message-result.react.js';
+import Button from '../components/button.react.js';
+import Modal from '../components/modal.react.js';
+import type { AppNavigationProp } from '../navigation/app-navigator.react.js';
+import type { NavigationRoute } from '../navigation/route-names.js';
+import { useStyles } from '../themes/colors.js';
+import type { ChatMessageInfoItemWithHeight } from '../types/chat-types';
+
+export type TogglePinModalParams = {
+ +item: ChatMessageInfoItemWithHeight,
+ +threadInfo: ThreadInfo,
+};
+
+type TogglePinModalProps = {
+ +navigation: AppNavigationProp<'TogglePinModal'>,
+ +route: NavigationRoute<'TogglePinModal'>,
+};
+
+function TogglePinModal(props: TogglePinModalProps): React.Node {
+ const { navigation, route } = props;
+ const { item, threadInfo } = route.params;
+ const { messageInfo, isPinned } = item;
+ const styles = useStyles(unboundStyles);
+
+ const callToggleMessagePin = useServerCall(toggleMessagePin);
+ const dispatchActionPromise = useDispatchActionPromise();
+
+ const modalInfo = React.useMemo(() => {
+ if (isPinned) {
+ return {
+ name: 'Remove Pinned Message',
+ action: 'unpin',
+ confirmationText:
+ 'Are you sure you want to remove this pinned message?',
+ buttonText: 'Remove Pinned Message',
+ buttonStyle: styles.removePinButton,
+ };
+ }
+
+ return {
+ name: 'Pin Message',
+ action: 'pin',
+ confirmationText:
+ 'You may pin this message to the channel you are ' +
+ 'currently viewing. To unpin a message, select the pinned messages ' +
+ 'icon in the channel.',
+ buttonText: 'Pin Message',
+ buttonStyle: styles.pinButton,
+ };
+ }, [isPinned, styles.pinButton, styles.removePinButton]);
+
+ const modifiedItem = React.useMemo(() => {
+ // The if / else if / else conditional is for Flow
+ if (item.messageShapeType === 'robotext') {
+ return item;
+ } else if (item.messageShapeType === 'multimedia') {
+ return {
+ ...item,
+ threadCreatedFromMessage: undefined,
+ reactions: {},
+ startsConversation: false,
+ startsCluster: true,
+ endsCluster: true,
+ messageInfo: {
+ ...item.messageInfo,
+ creator: {
+ ...item.messageInfo.creator,
+ isViewer: false,
+ },
+ },
+ };
+ } else {
+ return {
+ ...item,
+ threadCreatedFromMessage: undefined,
+ reactions: {},
+ startsConversation: false,
+ startsCluster: true,
+ endsCluster: true,
+ messageInfo: {
+ ...item.messageInfo,
+ creator: {
+ ...item.messageInfo.creator,
+ isViewer: false,
+ },
+ },
+ };
+ }
+ }, [item]);
+
+ const createToggleMessagePinPromise = React.useCallback(async () => {
+ invariant(messageInfo.id, 'messageInfo.id should be defined');
+ const result = await callToggleMessagePin({
+ messageID: messageInfo.id,
+ action: modalInfo.action,
+ });
+ return {
+ newMessageInfos: result.newMessageInfos,
+ threadID: result.threadID,
+ };
+ }, [callToggleMessagePin, messageInfo.id, modalInfo.action]);
+
+ const onPress = React.useCallback(() => {
+ dispatchActionPromise(
+ toggleMessagePinActionTypes,
+ createToggleMessagePinPromise(),
+ );
+
+ navigation.goBack();
+ }, [createToggleMessagePinPromise, dispatchActionPromise, navigation]);
+
+ const onCancel = React.useCallback(() => {
+ navigation.goBack();
+ }, [navigation]);
+
+ return (
+
+ {modalInfo.name}
+
+ {modalInfo.confirmationText}
+
+
+
+
+
+
+
+ );
+}
+
+const unboundStyles = {
+ modal: {
+ backgroundColor: 'modalForeground',
+ borderColor: 'modalForegroundBorder',
+ },
+ modalHeader: {
+ fontSize: 18,
+ color: 'modalForegroundLabel',
+ },
+ modalConfirmationText: {
+ fontSize: 12,
+ color: 'modalBackgroundLabel',
+ marginTop: 4,
+ },
+ buttonsContainer: {
+ flexDirection: 'column',
+ flex: 1,
+ justifyContent: 'flex-end',
+ marginBottom: 0,
+ height: 72,
+ paddingHorizontal: 16,
+ },
+ removePinButton: {
+ borderRadius: 5,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'vibrantRedButton',
+ },
+ pinButton: {
+ borderRadius: 5,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'purpleButton',
+ },
+ cancelButton: {
+ borderRadius: 5,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ textColor: {
+ color: 'modalButtonLabel',
+ },
+};
+
+export default TogglePinModal;
diff --git a/native/navigation/app-navigator.react.js b/native/navigation/app-navigator.react.js
index 3aab1dbe0..c934263a9 100644
--- a/native/navigation/app-navigator.react.js
+++ b/native/navigation/app-navigator.react.js
@@ -1,160 +1,163 @@
// @flow
import * as SplashScreen from 'expo-splash-screen';
import * as React from 'react';
import { useSelector } from 'react-redux';
import { PersistGate } from 'redux-persist/es/integration/react.js';
import ActionResultModal from './action-result-modal.react.js';
import { CommunityDrawerNavigator } from './community-drawer-navigator.react.js';
import { createOverlayNavigator } from './overlay-navigator.react.js';
import type {
OverlayNavigationProp,
OverlayNavigationHelpers,
} from './overlay-navigator.react.js';
import type { RootNavigationProp } from './root-navigator.react.js';
import {
UserAvatarCameraModalRouteName,
ThreadAvatarCameraModalRouteName,
ImageModalRouteName,
MultimediaMessageTooltipModalRouteName,
ActionResultModalRouteName,
TextMessageTooltipModalRouteName,
ThreadSettingsMemberTooltipModalRouteName,
RelationshipListItemTooltipModalRouteName,
RobotextMessageTooltipModalRouteName,
ChatCameraModalRouteName,
VideoPlaybackModalRouteName,
CommunityDrawerNavigatorRouteName,
type ScreenParamList,
type OverlayParamList,
+ TogglePinModalRouteName,
} from './route-names.js';
import MultimediaMessageTooltipModal from '../chat/multimedia-message-tooltip-modal.react.js';
import RobotextMessageTooltipModal from '../chat/robotext-message-tooltip-modal.react.js';
import ThreadSettingsMemberTooltipModal from '../chat/settings/thread-settings-member-tooltip-modal.react.js';
import TextMessageTooltipModal from '../chat/text-message-tooltip-modal.react.js';
+import TogglePinModal from '../chat/toggle-pin-modal.react.js';
import KeyboardStateContainer from '../keyboard/keyboard-state-container.react.js';
import ChatCameraModal from '../media/chat-camera-modal.react.js';
import ImageModal from '../media/image-modal.react.js';
import ThreadAvatarCameraModal from '../media/thread-avatar-camera-modal.react.js';
import UserAvatarCameraModal from '../media/user-avatar-camera-modal.react.js';
import VideoPlaybackModal from '../media/video-playback-modal.react.js';
import RelationshipListItemTooltipModal from '../profile/relationship-list-item-tooltip-modal.react.js';
import PushHandler from '../push/push-handler.react.js';
import { getPersistor } from '../redux/persist.js';
import { RootContext } from '../root-context.js';
import { useLoadCommFonts } from '../themes/fonts.js';
import { waitForInteractions } from '../utils/timers.js';
let splashScreenHasHidden = false;
export type AppNavigationProp<
RouteName: $Keys = $Keys,
> = OverlayNavigationProp;
const App = createOverlayNavigator<
ScreenParamList,
OverlayParamList,
OverlayNavigationHelpers,
>();
type AppNavigatorProps = {
navigation: RootNavigationProp<'App'>,
...
};
function AppNavigator(props: AppNavigatorProps): React.Node {
const { navigation } = props;
const fontsLoaded = useLoadCommFonts();
const rootContext = React.useContext(RootContext);
const storeLoadedFromLocalDatabase = useSelector(state => state.storeLoaded);
const setNavStateInitialized =
rootContext && rootContext.setNavStateInitialized;
React.useEffect(() => {
setNavStateInitialized && setNavStateInitialized();
}, [setNavStateInitialized]);
const [localSplashScreenHasHidden, setLocalSplashScreenHasHidden] =
React.useState(splashScreenHasHidden);
React.useEffect(() => {
if (localSplashScreenHasHidden || !fontsLoaded) {
return;
}
splashScreenHasHidden = true;
(async () => {
await waitForInteractions();
try {
await SplashScreen.hideAsync();
} finally {
setLocalSplashScreenHasHidden(true);
}
})();
}, [localSplashScreenHasHidden, fontsLoaded]);
let pushHandler;
if (localSplashScreenHasHidden) {
pushHandler = (
);
}
if (!storeLoadedFromLocalDatabase) {
return null;
}
return (
+
{pushHandler}
);
}
export default AppNavigator;
diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js
index 5d5228e3a..46b002e61 100644
--- a/native/navigation/route-names.js
+++ b/native/navigation/route-names.js
@@ -1,218 +1,226 @@
// @flow
import type { RouteProp } from '@react-navigation/native';
import type { ActionResultModalParams } from './action-result-modal.react.js';
import type { InviteLinkModalParams } from './invite-link-modal.react';
import type { ConnectEthereumParams } from '../account/registration/connect-ethereum.react.js';
import type { KeyserverSelectionParams } from '../account/registration/keyserver-selection.react.js';
import type { TermsAndPrivacyModalParams } from '../account/terms-and-privacy-modal.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 { TogglePinModalParams } from '../chat/toggle-pin-modal.react.js';
import type { ChatCameraModalParams } from '../media/chat-camera-modal.react.js';
import type { ImageModalParams } from '../media/image-modal.react.js';
import type { ThreadAvatarCameraModalParams } from '../media/thread-avatar-camera-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 { 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 ChatCameraModalRouteName = 'ChatCameraModal';
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 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 InviteLinkModalRouteName = 'InviteLinkModal';
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 ThreadAvatarCameraModalRouteName = 'ThreadAvatarCameraModal';
export const ThreadPickerModalRouteName = 'ThreadPickerModal';
export const ThreadSettingsMemberTooltipModalRouteName =
'ThreadSettingsMemberTooltipModal';
export const ThreadSettingsRouteName = 'ThreadSettings';
export const UserAvatarCameraModalRouteName = 'UserAvatarCameraModal';
+export const TogglePinModalRouteName = 'TogglePinModal';
export const VideoPlaybackModalRouteName = 'VideoPlaybackModal';
export const TermsAndPrivacyRouteName = 'TermsAndPrivacyModal';
export const RegistrationRouteName = 'Registration';
export const KeyserverSelectionRouteName = 'KeyserverSelection';
export const CoolOrNerdModeSelectionRouteName = 'CoolOrNerdModeSelection';
export const ConnectEthereumRouteName = 'ConnectEthereum';
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,
+Registration: void,
+InviteLinkModal: InviteLinkModalParams,
};
export type MessageTooltipRouteNames =
| typeof RobotextMessageTooltipModalRouteName
| typeof MultimediaMessageTooltipModalRouteName
| typeof TextMessageTooltipModalRouteName;
+export const PinnableMessageTooltipRouteNames = [
+ TextMessageTooltipModalRouteName,
+ MultimediaMessageTooltipModalRouteName,
+];
+
export type TooltipModalParamList = {
+MultimediaMessageTooltipModal: MultimediaMessageTooltipModalParams,
+TextMessageTooltipModal: TextMessageTooltipModalParams,
+ThreadSettingsMemberTooltipModal: ThreadSettingsMemberTooltipModalParams,
+RelationshipListItemTooltipModal: RelationshipListItemTooltipModalParams,
+RobotextMessageTooltipModal: RobotextMessageTooltipModalParams,
};
export type OverlayParamList = {
+CommunityDrawerNavigator: void,
+ImageModal: ImageModalParams,
+ActionResultModal: ActionResultModalParams,
+ChatCameraModal: ChatCameraModalParams,
+UserAvatarCameraModal: void,
+ThreadAvatarCameraModal: ThreadAvatarCameraModalParams,
+VideoPlaybackModal: VideoPlaybackModalParams,
+ +TogglePinModal: TogglePinModalParams,
...TooltipModalParamList,
};
export type TabParamList = {
+Calendar: void,
+Chat: void,
+Profile: void,
+Apps: void,
};
export type ChatParamList = {
+ChatThreadList: void,
+MessageList: MessageListParams,
+ComposeSubchannel: ComposeSubchannelParams,
+ThreadSettings: ThreadSettingsParams,
+EmojiThreadAvatarCreation: EmojiThreadAvatarCreationParams,
+DeleteThread: DeleteThreadParams,
+FullScreenThreadMediaGallery: FullScreenThreadMediaGalleryParams,
};
export type ChatTopTabsParamList = {
+HomeChatThreadList: void,
+BackgroundChatThreadList: void,
};
export type ProfileParamList = {
+ProfileScreen: void,
+EmojiUserAvatarCreation: void,
+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 RegistrationParamList = {
+CoolOrNerdModeSelection: void,
+KeyserverSelection: KeyserverSelectionParams,
+ConnectEthereum: ConnectEthereumParams,
};
export type ScreenParamList = {
...RootParamList,
...OverlayParamList,
...TabParamList,
...ChatParamList,
...ChatTopTabsParamList,
...ProfileParamList,
...CommunityDrawerParamList,
...RegistrationParamList,
};
export type NavigationRoute> =
RouteProp;
export const accountModals = [LoggedOutModalRouteName, RegistrationRouteName];
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/utils/toggle-pin-utils.js b/native/utils/toggle-pin-utils.js
new file mode 100644
index 000000000..e2cd3b6f6
--- /dev/null
+++ b/native/utils/toggle-pin-utils.js
@@ -0,0 +1,59 @@
+// @flow
+
+import { StackActions, useNavigation } from '@react-navigation/native';
+import * as React from 'react';
+
+import type { OverlayContextType } from '../navigation/overlay-context.js';
+import {
+ type NavigationRoute,
+ PinnableMessageTooltipRouteNames,
+ TogglePinModalRouteName,
+} from '../navigation/route-names.js';
+
+function useNavigateToPinModal(
+ overlayContext: ?OverlayContextType,
+ route:
+ | NavigationRoute<'TextMessageTooltipModal'>
+ | NavigationRoute<'MultimediaMessageTooltipModal'>,
+): () => mixed {
+ const navigation = useNavigation();
+
+ const { params } = route;
+ const { item } = params;
+ const { threadInfo } = item;
+
+ return React.useCallback(() => {
+ // Since the most recent overlay is the tooltip modal, prior to opening the
+ // toggle pin modal, we want to dismiss it so the overlay is not visible
+ // once the toggle pin modal is closed. This is also necessary with the
+ // TextMessageTooltipModal, since otherwise the toggle pin modal fails to
+ // render the message since we 'hide' the original message and
+ // show another message on top when the tooltip is active, and this
+ // state carries through into the modal.
+ const mostRecentOverlay = overlayContext?.visibleOverlays?.slice(-1)[0];
+ const routeName = mostRecentOverlay?.routeName;
+ const routeKey = mostRecentOverlay?.routeKey;
+
+ // If there is not a valid routeKey or the most recent routeName is
+ // not included in PinnableMessageTooltipRouteNames, we want to
+ // just navigate to the toggle pin modal as normal.
+ if (!routeKey || !PinnableMessageTooltipRouteNames.includes(routeName)) {
+ navigation.navigate(TogglePinModalRouteName, {
+ threadInfo,
+ item,
+ });
+ return;
+ }
+
+ // Otherwise, we want to replace the tooltip overlay with the pin modal.
+ navigation.dispatch({
+ ...StackActions.replace(TogglePinModalRouteName, {
+ threadInfo,
+ item,
+ }),
+ source: routeKey,
+ });
+ }, [navigation, overlayContext, threadInfo, item]);
+}
+
+export { useNavigateToPinModal };