Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3380766
D7607.id26231.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D7607.id26231.diff
View Options
diff --git a/native/chat/message-result.react.js b/native/chat/message-result.react.js
new file mode 100644
--- /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 <View />;
+}
+
+export default MessageResult;
diff --git a/native/chat/multimedia-message-tooltip-modal.react.js b/native/chat/multimedia-message-tooltip-modal.react.js
--- a/native/chat/multimedia-message-tooltip-modal.react.js
+++ b/native/chat/multimedia-message-tooltip-modal.react.js
@@ -7,6 +7,7 @@
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,
@@ -15,6 +16,7 @@
} 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,
@@ -26,7 +28,10 @@
): 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 => <CommIcon name="pin-outline" style={style} size={16} />,
[],
diff --git a/native/chat/text-message-tooltip-modal.react.js b/native/chat/text-message-tooltip-modal.react.js
--- a/native/chat/text-message-tooltip-modal.react.js
+++ b/native/chat/text-message-tooltip-modal.react.js
@@ -13,6 +13,7 @@
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,
@@ -21,6 +22,7 @@
} 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,
@@ -33,6 +35,7 @@
): 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(() => {
@@ -84,7 +87,8 @@
[],
);
- const onPressTogglePin = React.useCallback(() => {}, []);
+ const onPressTogglePin = useNavigateToPinModal(overlayContext, route);
+
const renderPinIcon = React.useCallback(
style => <CommIcon name="pin-outline" style={style} size={16} />,
[],
diff --git a/native/chat/toggle-pin-modal.react.js b/native/chat/toggle-pin-modal.react.js
new file mode 100644
--- /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 (
+ <Modal modalStyle={styles.modal}>
+ <Text style={styles.modalHeader}>{modalInfo.name}</Text>
+ <Text style={styles.modalConfirmationText}>
+ {modalInfo.confirmationText}
+ </Text>
+ <MessageResult item={modifiedItem} threadInfo={threadInfo} />
+ <View style={styles.buttonsContainer}>
+ <Button style={modalInfo.buttonStyle} onPress={onPress}>
+ <Text style={styles.textColor}>{modalInfo.buttonText}</Text>
+ </Button>
+ <Button style={styles.cancelButton} onPress={onCancel}>
+ <Text style={styles.textColor}>Cancel</Text>
+ </Button>
+ </View>
+ </Modal>
+ );
+}
+
+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
--- a/native/navigation/app-navigator.react.js
+++ b/native/navigation/app-navigator.react.js
@@ -28,11 +28,13 @@
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';
@@ -151,6 +153,7 @@
name={VideoPlaybackModalRouteName}
component={VideoPlaybackModal}
/>
+ <App.Screen name={TogglePinModalRouteName} component={TogglePinModal} />
</App.Navigator>
{pushHandler}
</KeyboardStateContainer>
diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js
--- a/native/navigation/route-names.js
+++ b/native/navigation/route-names.js
@@ -25,6 +25,7 @@
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';
@@ -85,6 +86,7 @@
'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';
@@ -114,6 +116,11 @@
| typeof MultimediaMessageTooltipModalRouteName
| typeof TextMessageTooltipModalRouteName;
+export const PinnableMessageTooltipRouteNames = [
+ TextMessageTooltipModalRouteName,
+ MultimediaMessageTooltipModalRouteName,
+];
+
export type TooltipModalParamList = {
+MultimediaMessageTooltipModal: MultimediaMessageTooltipModalParams,
+TextMessageTooltipModal: TextMessageTooltipModalParams,
@@ -130,6 +137,7 @@
+UserAvatarCameraModal: void,
+ThreadAvatarCameraModal: ThreadAvatarCameraModalParams,
+VideoPlaybackModal: VideoPlaybackModalParams,
+ +TogglePinModal: TogglePinModalParams,
...TooltipModalParamList,
};
diff --git a/native/utils/toggle-pin-utils.js b/native/utils/toggle-pin-utils.js
new file mode 100644
--- /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 };
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Nov 29, 1:58 AM (21 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2595432
Default Alt Text
D7607.id26231.diff (14 KB)
Attached To
Mode
D7607: [native] Create the toggle pin modal
Attached
Detach File
Event Timeline
Log In to Comment