Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3497027
D6549.id21969.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
56 KB
Referenced Files
None
Subscribers
None
D6549.id21969.diff
View Options
diff --git a/native/chat/message-report-utils.js b/native/chat/message-report-utils.js
--- a/native/chat/message-report-utils.js
+++ b/native/chat/message-report-utils.js
@@ -1,52 +1,32 @@
// @flow
-import Alert from 'react-native/Libraries/Alert/Alert';
+import * as React from 'react';
+import { Alert } from 'react-native';
import {
sendMessageReport,
sendMessageReportActionTypes,
} from 'lib/actions/message-report-actions';
-import type { BindServerCall, DispatchFunctions } from 'lib/utils/action-utils';
+import {
+ useServerCall,
+ useDispatchActionPromise,
+} from 'lib/utils/action-utils';
import { displayActionResultModal } from '../navigation/action-result-modal';
-import type { TooltipRoute } from '../navigation/tooltip.react';
+import type { TooltipRoute } from '../tooltip/tooltip.react';
const confirmReport = () => displayActionResultModal('reported to admin');
-function onPressReport(
+function useOnPressReport(
route:
| TooltipRoute<'TextMessageTooltipModal'>
| TooltipRoute<'MultimediaMessageTooltipModal'>,
- dispatchFunctions: DispatchFunctions,
- bindServerCall: BindServerCall,
-) {
+): () => mixed {
const messageID = route.params.item.messageInfo.id;
- if (!messageID) {
- Alert.alert(
- 'Couldn’t send the report',
- 'Uhh... try again?',
- [{ text: 'OK' }],
- {
- cancelable: false,
- },
- );
- return;
- }
- reportMessage(messageID, dispatchFunctions, bindServerCall);
-}
-
-function reportMessage(
- messageID: string,
- dispatchFunctions: DispatchFunctions,
- bindServerCall: BindServerCall,
-) {
- const callSendMessageReport = bindServerCall(sendMessageReport);
- const messageReportPromise = (async () => {
- try {
- const result = await callSendMessageReport({ messageID });
- confirmReport();
- return result;
- } catch (e) {
+ const dispatchActionPromise = useDispatchActionPromise();
+ const callSendMessageReport = useServerCall(sendMessageReport);
+ return React.useCallback(() => {
+ if (!messageID) {
Alert.alert(
'Couldn’t send the report',
'Uhh... try again?',
@@ -55,14 +35,27 @@
cancelable: false,
},
);
- throw e;
+ return;
}
- })();
-
- dispatchFunctions.dispatchActionPromise(
- sendMessageReportActionTypes,
- messageReportPromise,
- );
+ const messageReportPromise = (async () => {
+ try {
+ const result = await callSendMessageReport({ messageID });
+ confirmReport();
+ return result;
+ } catch (e) {
+ Alert.alert(
+ 'Couldn’t send the report',
+ 'Uhh... try again?',
+ [{ text: 'OK' }],
+ {
+ cancelable: false,
+ },
+ );
+ throw e;
+ }
+ })();
+ dispatchActionPromise(sendMessageReportActionTypes, messageReportPromise);
+ }, [callSendMessageReport, messageID, dispatchActionPromise]);
}
-export { onPressReport };
+export { useOnPressReport };
diff --git a/native/chat/multimedia-message-tooltip-button.react.js b/native/chat/multimedia-message-tooltip-button.react.js
--- a/native/chat/multimedia-message-tooltip-button.react.js
+++ b/native/chat/multimedia-message-tooltip-button.react.js
@@ -9,8 +9,8 @@
import type { SetState } from 'lib/types/hook-types';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
-import type { TooltipRoute } from '../navigation/tooltip.react';
import { useSelector } from '../redux/redux-utils';
+import type { TooltipRoute } from '../tooltip/tooltip.react';
import { TooltipInlineEngagement } from './inline-engagement.react';
import { InnerMultimediaMessage } from './inner-multimedia-message.react';
import { MessageHeader } from './message-header.react';
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
@@ -2,42 +2,68 @@
import * as React from 'react';
+import SWMansionIcon from '../components/swmansion-icon.react';
import {
createTooltip,
type TooltipParams,
type BaseTooltipProps,
-} from '../navigation/tooltip.react';
+ type TooltipMenuProps,
+} from '../tooltip/tooltip.react';
import type { ChatMultimediaMessageInfoItem } from '../types/chat-types';
import type { VerticalBounds } from '../types/layout-types';
-import { onPressReport } from './message-report-utils';
+import { useOnPressReport } from './message-report-utils';
import MultimediaMessageTooltipButton from './multimedia-message-tooltip-button.react';
-import { navigateToSidebar } from './sidebar-navigation';
+import { useAnimatedNavigateToSidebar } from './sidebar-navigation';
export type MultimediaMessageTooltipModalParams = TooltipParams<{
+item: ChatMultimediaMessageInfoItem,
+verticalBounds: VerticalBounds,
}>;
-const spec = {
- entries: [
- {
- id: 'sidebar',
- text: 'Thread',
- onPress: navigateToSidebar,
- },
- {
- id: 'report',
- text: 'Report',
- onPress: onPressReport,
- },
- ],
-};
+function TooltipMenu(
+ props: TooltipMenuProps<'MultimediaMessageTooltipModal'>,
+): React.Node {
+ const { route, tooltipItem: TooltipItem } = props;
+
+ const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item);
+ const renderSidebarIcon = React.useCallback(
+ style => (
+ <SWMansionIcon name="message-circle-lines" style={style} size={16} />
+ ),
+ [],
+ );
+
+ const onPressReport = useOnPressReport(route);
+ const renderReportIcon = React.useCallback(
+ style => <SWMansionIcon name="warning-circle" style={style} size={16} />,
+ [],
+ );
+
+ return (
+ <>
+ <TooltipItem
+ id="sidebar"
+ text="Thread"
+ onPress={onPressSidebar}
+ renderIcon={renderSidebarIcon}
+ key="sidebar"
+ />
+ <TooltipItem
+ id="report"
+ text="Report"
+ onPress={onPressReport}
+ renderIcon={renderReportIcon}
+ key="report"
+ />
+ </>
+ );
+}
const MultimediaMessageTooltipModal: React.ComponentType<
BaseTooltipProps<'MultimediaMessageTooltipModal'>,
> = createTooltip<'MultimediaMessageTooltipModal'>(
MultimediaMessageTooltipButton,
- spec,
+ TooltipMenu,
);
export default MultimediaMessageTooltipModal;
diff --git a/native/chat/multimedia-message.react.js b/native/chat/multimedia-message.react.js
--- a/native/chat/multimedia-message.react.js
+++ b/native/chat/multimedia-message.react.js
@@ -21,7 +21,7 @@
MultimediaMessageTooltipModalRouteName,
VideoPlaybackModalRouteName,
} from '../navigation/route-names';
-import { fixedTooltipHeight } from '../navigation/tooltip.react';
+import { fixedTooltipHeight } from '../tooltip/tooltip.react';
import type { ChatMultimediaMessageInfoItem } from '../types/chat-types';
import { type VerticalBounds } from '../types/layout-types';
import type { LayoutCoordinates } from '../types/layout-types';
diff --git a/native/chat/robotext-message-tooltip-button.react.js b/native/chat/robotext-message-tooltip-button.react.js
--- a/native/chat/robotext-message-tooltip-button.react.js
+++ b/native/chat/robotext-message-tooltip-button.react.js
@@ -9,8 +9,8 @@
import type { SetState } from 'lib/types/hook-types';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
-import type { TooltipRoute } from '../navigation/tooltip.react';
import { useSelector } from '../redux/redux-utils';
+import type { TooltipRoute } from '../tooltip/tooltip.react';
import { TooltipInlineEngagement } from './inline-engagement.react';
import { InnerRobotextMessage } from './inner-robotext-message.react';
import {
diff --git a/native/chat/robotext-message-tooltip-modal.react.js b/native/chat/robotext-message-tooltip-modal.react.js
--- a/native/chat/robotext-message-tooltip-modal.react.js
+++ b/native/chat/robotext-message-tooltip-modal.react.js
@@ -2,34 +2,52 @@
import * as React from 'react';
+import SWMansionIcon from '../components/swmansion-icon.react';
import {
createTooltip,
type TooltipParams,
type BaseTooltipProps,
-} from '../navigation/tooltip.react';
+ type TooltipMenuProps,
+} from '../tooltip/tooltip.react';
import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types';
import RobotextMessageTooltipButton from './robotext-message-tooltip-button.react';
-import { navigateToSidebar } from './sidebar-navigation';
+import { useAnimatedNavigateToSidebar } from './sidebar-navigation';
export type RobotextMessageTooltipModalParams = TooltipParams<{
+item: ChatRobotextMessageInfoItemWithHeight,
}>;
-const spec = {
- entries: [
- {
- id: 'sidebar',
- text: 'Thread',
- onPress: navigateToSidebar,
- },
- ],
-};
+function TooltipMenu(
+ props: TooltipMenuProps<'RobotextMessageTooltipModal'>,
+): React.Node {
+ const { route, tooltipItem: TooltipItem } = props;
+
+ const onPress = useAnimatedNavigateToSidebar(route.params.item);
+ const renderIcon = React.useCallback(
+ style => (
+ <SWMansionIcon name="message-circle-lines" style={style} size={16} />
+ ),
+ [],
+ );
+
+ return (
+ <>
+ <TooltipItem
+ id="sidebar"
+ text="Thread"
+ onPress={onPress}
+ renderIcon={renderIcon}
+ key="sidebar"
+ />
+ </>
+ );
+}
const RobotextMessageTooltipModal: React.ComponentType<
BaseTooltipProps<'RobotextMessageTooltipModal'>,
> = createTooltip<'RobotextMessageTooltipModal'>(
RobotextMessageTooltipButton,
- spec,
+ TooltipMenu,
);
export default RobotextMessageTooltipModal;
diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js
--- a/native/chat/robotext-message.react.js
+++ b/native/chat/robotext-message.react.js
@@ -12,8 +12,8 @@
import { OverlayContext } from '../navigation/overlay-context';
import { RobotextMessageTooltipModalRouteName } from '../navigation/route-names';
import type { NavigationRoute } from '../navigation/route-names';
-import { fixedTooltipHeight } from '../navigation/tooltip.react';
import { useStyles } from '../themes/colors';
+import { fixedTooltipHeight } from '../tooltip/tooltip.react';
import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types';
import type { VerticalBounds } from '../types/layout-types';
import { AnimatedView } from '../types/styles';
diff --git a/native/chat/settings/thread-settings-member-tooltip-modal.react.js b/native/chat/settings/thread-settings-member-tooltip-modal.react.js
--- a/native/chat/settings/thread-settings-member-tooltip-modal.react.js
+++ b/native/chat/settings/thread-settings-member-tooltip-modal.react.js
@@ -14,14 +14,18 @@
} from 'lib/shared/thread-utils';
import { stringForUser } from 'lib/shared/user-utils';
import type { ThreadInfo, RelativeMemberInfo } from 'lib/types/thread-types';
-import type { DispatchFunctions, BindServerCall } from 'lib/utils/action-utils';
+import {
+ useDispatchActionPromise,
+ useServerCall,
+} from 'lib/utils/action-utils';
import {
createTooltip,
type TooltipParams,
type TooltipRoute,
type BaseTooltipProps,
-} from '../../navigation/tooltip.react';
+ type TooltipMenuProps,
+} from '../../tooltip/tooltip.react';
import ThreadSettingsMemberTooltipButton from './thread-settings-member-tooltip-button.react';
export type ThreadSettingsMemberTooltipModalParams = TooltipParams<{
@@ -29,78 +33,118 @@
+threadInfo: ThreadInfo,
}>;
-function onRemoveUser(
+function useOnRemoveUser(
route: TooltipRoute<'ThreadSettingsMemberTooltipModal'>,
- dispatchFunctions: DispatchFunctions,
- bindServerCall: BindServerCall,
) {
const { memberInfo, threadInfo } = route.params;
- const boundRemoveUsersFromThread = bindServerCall(removeUsersFromThread);
- const onConfirmRemoveUser = () =>
- removeMemberFromThread(
- threadInfo,
- memberInfo,
- dispatchFunctions.dispatchActionPromise,
- boundRemoveUsersFromThread,
- );
+ const boundRemoveUsersFromThread = useServerCall(removeUsersFromThread);
+ const dispatchActionPromise = useDispatchActionPromise();
- const userText = stringForUser(memberInfo);
- Alert.alert(
- 'Confirm removal',
- `Are you sure you want to remove ${userText} from this chat?`,
- [
- { text: 'Cancel', style: 'cancel' },
- { text: 'OK', onPress: onConfirmRemoveUser },
- ],
- { cancelable: true },
+ const onConfirmRemoveUser = React.useCallback(
+ () =>
+ removeMemberFromThread(
+ threadInfo,
+ memberInfo,
+ dispatchActionPromise,
+ boundRemoveUsersFromThread,
+ ),
+ [threadInfo, memberInfo, dispatchActionPromise, boundRemoveUsersFromThread],
);
+
+ const userText = stringForUser(memberInfo);
+ return React.useCallback(() => {
+ Alert.alert(
+ 'Confirm removal',
+ `Are you sure you want to remove ${userText} from this chat?`,
+ [
+ { text: 'Cancel', style: 'cancel' },
+ { text: 'OK', onPress: onConfirmRemoveUser },
+ ],
+ { cancelable: true },
+ );
+ }, [onConfirmRemoveUser, userText]);
}
-function onToggleAdmin(
+function useOnToggleAdmin(
route: TooltipRoute<'ThreadSettingsMemberTooltipModal'>,
- dispatchFunctions: DispatchFunctions,
- bindServerCall: BindServerCall,
) {
const { memberInfo, threadInfo } = route.params;
+ const boundChangeThreadMemberRoles = useServerCall(changeThreadMemberRoles);
+ const dispatchActionPromise = useDispatchActionPromise();
+
const isCurrentlyAdmin = memberIsAdmin(memberInfo, threadInfo);
- const boundChangeThreadMemberRoles = bindServerCall(changeThreadMemberRoles);
- const onConfirmMakeAdmin = () =>
- switchMemberAdminRoleInThread(
+ const onConfirmMakeAdmin = React.useCallback(
+ () =>
+ switchMemberAdminRoleInThread(
+ threadInfo,
+ memberInfo,
+ isCurrentlyAdmin,
+ dispatchActionPromise,
+ boundChangeThreadMemberRoles,
+ ),
+ [
threadInfo,
memberInfo,
isCurrentlyAdmin,
- dispatchFunctions.dispatchActionPromise,
+ dispatchActionPromise,
boundChangeThreadMemberRoles,
- );
+ ],
+ );
const userText = stringForUser(memberInfo);
const actionClause = isCurrentlyAdmin
? `remove ${userText} as an admin`
: `make ${userText} an admin`;
- Alert.alert(
- 'Confirm action',
- `Are you sure you want to ${actionClause} of this chat?`,
- [
- { text: 'Cancel', style: 'cancel' },
- { text: 'OK', onPress: onConfirmMakeAdmin },
- ],
- { cancelable: true },
- );
+ return React.useCallback(() => {
+ Alert.alert(
+ 'Confirm action',
+ `Are you sure you want to ${actionClause} of this chat?`,
+ [
+ { text: 'Cancel', style: 'cancel' },
+ { text: 'OK', onPress: onConfirmMakeAdmin },
+ ],
+ { cancelable: true },
+ );
+ }, [onConfirmMakeAdmin, actionClause]);
}
-const spec = {
- entries: [
- { id: 'remove_user', text: 'Remove user', onPress: onRemoveUser },
- { id: 'remove_admin', text: 'Remove admin', onPress: onToggleAdmin },
- { id: 'make_admin', text: 'Make admin', onPress: onToggleAdmin },
- ],
-};
+function TooltipMenu(
+ props: TooltipMenuProps<'ThreadSettingsMemberTooltipModal'>,
+): React.Node {
+ const { route, tooltipItem: TooltipItem } = props;
+
+ const onRemoveUser = useOnRemoveUser(route);
+ const onToggleAdmin = useOnToggleAdmin(route);
+
+ return (
+ <>
+ <TooltipItem
+ id="remove_user"
+ text="Remove user"
+ onPress={onRemoveUser}
+ key="remove_user"
+ />
+ <TooltipItem
+ id="remove_admin"
+ text="Remove admin"
+ onPress={onToggleAdmin}
+ key="remove_admin"
+ />
+ <TooltipItem
+ id="make_admin"
+ text="Make admin"
+ onPress={onToggleAdmin}
+ key="make_admin"
+ />
+ </>
+ );
+}
const ThreadSettingsMemberTooltipModal: React.ComponentType<
BaseTooltipProps<'ThreadSettingsMemberTooltipModal'>,
> = createTooltip<'ThreadSettingsMemberTooltipModal'>(
ThreadSettingsMemberTooltipButton,
- spec,
+ TooltipMenu,
);
export default ThreadSettingsMemberTooltipModal;
diff --git a/native/chat/sidebar-navigation.js b/native/chat/sidebar-navigation.js
--- a/native/chat/sidebar-navigation.js
+++ b/native/chat/sidebar-navigation.js
@@ -5,20 +5,12 @@
import { createPendingSidebar } from 'lib/shared/thread-utils';
import type { ThreadInfo } from 'lib/types/thread-types';
-import type { DispatchFunctions, BindServerCall } from 'lib/utils/action-utils';
-import type { InputState } from '../input/input-state';
import { getDefaultTextMessageRules } from '../markdown/rules.react';
-import type { AppNavigationProp } from '../navigation/app-navigator.react';
-import type { MessageTooltipRouteNames } from '../navigation/route-names';
-import type { TooltipRoute } from '../navigation/tooltip.react';
import { useSelector } from '../redux/redux-utils';
import type { ChatMessageInfoItemWithHeight } from '../types/chat-types';
-import type { ChatContextType } from './chat-context';
-import {
- createNavigateToThreadAction,
- useNavigateToThread,
-} from './message-list-types';
+import { ChatContext } from './chat-context';
+import { useNavigateToThread } from './message-list-types';
function getSidebarThreadInfo(
sourceMessage: ChatMessageInfoItemWithHeight,
@@ -42,27 +34,6 @@
);
}
-function navigateToSidebar<RouteName: MessageTooltipRouteNames>(
- route: TooltipRoute<RouteName>,
- dispatchFunctions: DispatchFunctions,
- bindServerCall: BindServerCall,
- inputState: ?InputState,
- navigation: AppNavigationProp<RouteName>,
- viewerID: ?string,
- chatContext: ?ChatContextType,
-) {
- invariant(viewerID, 'viewerID should be set');
- const threadInfo = getSidebarThreadInfo(route.params.item, viewerID);
- invariant(threadInfo, 'threadInfo should be set');
-
- chatContext?.setCurrentTransitionSidebarSourceID(
- route.params.item.messageInfo.id,
- );
- navigation.navigate<'MessageList'>(
- createNavigateToThreadAction({ threadInfo }),
- );
-}
-
function useNavigateToSidebar(item: ChatMessageInfoItemWithHeight): () => void {
const viewerID = useSelector(
state => state.currentUserInfo && state.currentUserInfo.id,
@@ -78,4 +49,21 @@
}, [navigateToThread, threadInfo]);
}
-export { navigateToSidebar, getSidebarThreadInfo, useNavigateToSidebar };
+function useAnimatedNavigateToSidebar(
+ item: ChatMessageInfoItemWithHeight,
+): () => void {
+ const chatContext = React.useContext(ChatContext);
+ const setSidebarSourceID = chatContext?.setCurrentTransitionSidebarSourceID;
+ const navigateToSidebar = useNavigateToSidebar(item);
+ const messageID = item.messageInfo.id;
+ return React.useCallback(() => {
+ setSidebarSourceID && setSidebarSourceID(messageID);
+ navigateToSidebar();
+ }, [setSidebarSourceID, messageID, navigateToSidebar]);
+}
+
+export {
+ getSidebarThreadInfo,
+ useNavigateToSidebar,
+ useAnimatedNavigateToSidebar,
+};
diff --git a/native/chat/text-message-tooltip-button.react.js b/native/chat/text-message-tooltip-button.react.js
--- a/native/chat/text-message-tooltip-button.react.js
+++ b/native/chat/text-message-tooltip-button.react.js
@@ -9,8 +9,8 @@
import type { SetState } from 'lib/types/hook-types';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
-import type { TooltipRoute } from '../navigation/tooltip.react';
import { useSelector } from '../redux/redux-utils';
+import type { TooltipRoute } from '../tooltip/tooltip.react';
import { TooltipInlineEngagement } from './inline-engagement.react';
import { InnerTextMessage } from './inner-text-message.react';
import { MessageHeader } from './message-header.react';
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
@@ -5,19 +5,20 @@
import * as React from 'react';
import { createMessageReply } from 'lib/shared/message-utils';
-import type { DispatchFunctions, BindServerCall } from 'lib/utils/action-utils';
-import type { InputState } from '../input/input-state';
+import CommIcon from '../components/comm-icon.react';
+import SWMansionIcon from '../components/swmansion-icon.react';
+import { InputStateContext } from '../input/input-state';
import { displayActionResultModal } from '../navigation/action-result-modal';
import {
createTooltip,
type TooltipParams,
- type TooltipRoute,
type BaseTooltipProps,
-} from '../navigation/tooltip.react';
+ type TooltipMenuProps,
+} from '../tooltip/tooltip.react';
import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types';
-import { onPressReport } from './message-report-utils';
-import { navigateToSidebar } from './sidebar-navigation';
+import { useOnPressReport } from './message-report-utils';
+import { useAnimatedNavigateToSidebar } from './sidebar-navigation';
import TextMessageTooltipButton from './text-message-tooltip-button.react';
export type TextMessageTooltipModalParams = TooltipParams<{
@@ -26,43 +27,87 @@
const confirmCopy = () => displayActionResultModal('copied!');
-function onPressCopy(route: TooltipRoute<'TextMessageTooltipModal'>) {
- Clipboard.setString(route.params.item.messageInfo.text);
- setTimeout(confirmCopy);
-}
+function TooltipMenu(
+ props: TooltipMenuProps<'TextMessageTooltipModal'>,
+): React.Node {
+ const { route, tooltipItem: TooltipItem } = props;
-function onPressReply(
- route: TooltipRoute<'TextMessageTooltipModal'>,
- dispatchFunctions: DispatchFunctions,
- bindServerCall: BindServerCall,
- inputState: ?InputState,
-) {
- invariant(
- inputState,
- 'inputState should be set in TextMessageTooltipModal.onPressReply',
+ 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.addReply(createMessageReply(text));
+ }, [inputState, text]);
+ const renderReplyIcon = React.useCallback(
+ style => <CommIcon name="reply" style={style} size={12} />,
+ [],
+ );
+
+ const onPressSidebar = useAnimatedNavigateToSidebar(route.params.item);
+ const renderSidebarIcon = React.useCallback(
+ style => (
+ <SWMansionIcon name="message-circle-lines" style={style} size={16} />
+ ),
+ [],
+ );
+
+ const onPressCopy = React.useCallback(() => {
+ Clipboard.setString(text);
+ setTimeout(confirmCopy);
+ }, [text]);
+ const renderCopyIcon = React.useCallback(
+ style => <SWMansionIcon name="copy" style={style} size={16} />,
+ [],
+ );
+
+ const onPressReport = useOnPressReport(route);
+ const renderReportIcon = React.useCallback(
+ style => <SWMansionIcon name="warning-circle" style={style} size={16} />,
+ [],
);
- inputState.addReply(createMessageReply(route.params.item.messageInfo.text));
-}
-const spec = {
- entries: [
- { id: 'reply', text: 'Reply', onPress: onPressReply },
- {
- id: 'sidebar',
- text: 'Thread',
- onPress: navigateToSidebar,
- },
- { id: 'copy', text: 'Copy', onPress: onPressCopy },
- {
- id: 'report',
- text: 'Report',
- onPress: onPressReport,
- },
- ],
-};
+ return (
+ <>
+ <TooltipItem
+ id="reply"
+ text="Reply"
+ onPress={onPressReply}
+ renderIcon={renderReplyIcon}
+ key="reply"
+ />
+ <TooltipItem
+ id="sidebar"
+ text="Thread"
+ onPress={onPressSidebar}
+ renderIcon={renderSidebarIcon}
+ key="sidebar"
+ />
+ <TooltipItem
+ id="copy"
+ text="Copy"
+ onPress={onPressCopy}
+ renderIcon={renderCopyIcon}
+ key="copy"
+ />
+ <TooltipItem
+ id="report"
+ text="Report"
+ onPress={onPressReport}
+ renderIcon={renderReportIcon}
+ key="report"
+ />
+ </>
+ );
+}
const TextMessageTooltipModal: React.ComponentType<
BaseTooltipProps<'TextMessageTooltipModal'>,
-> = createTooltip<'TextMessageTooltipModal'>(TextMessageTooltipButton, spec);
+> = createTooltip<'TextMessageTooltipModal'>(
+ TextMessageTooltipButton,
+ TooltipMenu,
+);
export default TextMessageTooltipModal;
diff --git a/native/chat/text-message.react.js b/native/chat/text-message.react.js
--- a/native/chat/text-message.react.js
+++ b/native/chat/text-message.react.js
@@ -19,7 +19,7 @@
} from '../navigation/overlay-context';
import type { NavigationRoute } from '../navigation/route-names';
import { TextMessageTooltipModalRouteName } from '../navigation/route-names';
-import { fixedTooltipHeight } from '../navigation/tooltip.react';
+import { fixedTooltipHeight } from '../tooltip/tooltip.react';
import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types';
import type { VerticalBounds } from '../types/layout-types';
import type { ChatNavigationProp } from './chat.react';
diff --git a/native/profile/relationship-list-item-tooltip-modal.react.js b/native/profile/relationship-list-item-tooltip-modal.react.js
--- a/native/profile/relationship-list-item-tooltip-modal.react.js
+++ b/native/profile/relationship-list-item-tooltip-modal.react.js
@@ -9,7 +9,10 @@
} from 'lib/actions/relationship-actions';
import { stringForUser } from 'lib/shared/user-utils';
import type { RelativeUserInfo } from 'lib/types/user-types';
-import type { DispatchFunctions, BindServerCall } from 'lib/utils/action-utils';
+import {
+ useDispatchActionPromise,
+ useServerCall,
+} from 'lib/utils/action-utils';
import PencilIcon from '../components/pencil-icon.react';
import type { AppNavigationProp } from '../navigation/app-navigator.react';
@@ -17,7 +20,8 @@
createTooltip,
type TooltipParams,
type BaseTooltipProps,
-} from '../navigation/tooltip.react';
+ type TooltipMenuProps,
+} from '../tooltip/tooltip.react';
type Action = 'unfriend' | 'unblock';
@@ -29,79 +33,85 @@
...RelationshipListItemTooltipModalParams,
+action: Action,
};
-function onRemoveUser(
- props: OnRemoveUserProps,
- dispatchFunctions: DispatchFunctions,
- bindServerCall: BindServerCall,
-) {
- const boundRemoveRelationships = bindServerCall(updateRelationships);
- const callRemoveRelationships = async () => {
- try {
- return await boundRemoveRelationships({
- action: props.action,
- userIDs: [props.relativeUserInfo.id],
- });
- } catch (e) {
- Alert.alert('Unknown error', 'Uhh... try again?', [{ text: 'OK' }], {
- cancelable: true,
- });
- throw e;
- }
- };
- const onConfirmRemoveUser = () => {
- const customKeyName = `${updateRelationshipsActionTypes.started}:${props.relativeUserInfo.id}`;
- dispatchFunctions.dispatchActionPromise(
- updateRelationshipsActionTypes,
- callRemoveRelationships(),
- { customKeyName },
+function useRelationshipAction(input: OnRemoveUserProps) {
+ const boundRemoveRelationships = useServerCall(updateRelationships);
+ const dispatchActionPromise = useDispatchActionPromise();
+ const userText = stringForUser(input.relativeUserInfo);
+
+ return React.useCallback(() => {
+ const callRemoveRelationships = async () => {
+ try {
+ return await boundRemoveRelationships({
+ action: input.action,
+ userIDs: [input.relativeUserInfo.id],
+ });
+ } catch (e) {
+ Alert.alert('Unknown error', 'Uhh... try again?', [{ text: 'OK' }], {
+ cancelable: true,
+ });
+ throw e;
+ }
+ };
+ const onConfirmRemoveUser = () => {
+ const customKeyName = `${updateRelationshipsActionTypes.started}:${input.relativeUserInfo.id}`;
+ dispatchActionPromise(
+ updateRelationshipsActionTypes,
+ callRemoveRelationships(),
+ { customKeyName },
+ );
+ };
+ const action = {
+ unfriend: 'removal',
+ unblock: 'unblock',
+ }[input.action];
+ const message = {
+ unfriend: `remove ${userText} from friends?`,
+ unblock: `unblock ${userText}?`,
+ }[input.action];
+ Alert.alert(
+ `Confirm ${action}`,
+ `Are you sure you want to ${message}`,
+ [
+ { text: 'Cancel', style: 'cancel' },
+ { text: 'OK', onPress: onConfirmRemoveUser },
+ ],
+ { cancelable: true },
);
- };
+ }, [boundRemoveRelationships, dispatchActionPromise, userText, input]);
+}
- const userText = stringForUser(props.relativeUserInfo);
- const action = {
- unfriend: 'removal',
- unblock: 'unblock',
- }[props.action];
- const message = {
- unfriend: `remove ${userText} from friends?`,
- unblock: `unblock ${userText}?`,
- }[props.action];
- Alert.alert(
- `Confirm ${action}`,
- `Are you sure you want to ${message}`,
- [
- { text: 'Cancel', style: 'cancel' },
- { text: 'OK', onPress: onConfirmRemoveUser },
- ],
- { cancelable: true },
+function TooltipMenu(
+ props: TooltipMenuProps<'RelationshipListItemTooltipModal'>,
+): React.Node {
+ const { route, tooltipItem: TooltipItem } = props;
+
+ const onRemoveUser = useRelationshipAction({
+ ...route.params,
+ action: 'unfriend',
+ });
+ const onUnblockUser = useRelationshipAction({
+ ...route.params,
+ action: 'unblock',
+ });
+
+ return (
+ <>
+ <TooltipItem
+ id="unfriend"
+ text="Unfriend"
+ onPress={onRemoveUser}
+ key="unfriend"
+ />
+ <TooltipItem
+ id="unblock"
+ text="Unblock"
+ onPress={onUnblockUser}
+ key="unblock"
+ />
+ </>
);
}
-const spec = {
- entries: [
- {
- id: 'unfriend',
- text: 'Unfriend',
- onPress: (route, dispatchFunctions, bindServerCall) =>
- onRemoveUser(
- { ...route.params, action: 'unfriend' },
- dispatchFunctions,
- bindServerCall,
- ),
- },
- {
- id: 'unblock',
- text: 'Unblock',
- onPress: (route, dispatchFunctions, bindServerCall) =>
- onRemoveUser(
- { ...route.params, action: 'unblock' },
- dispatchFunctions,
- bindServerCall,
- ),
- },
- ],
-};
-
type Props = {
+navigation: AppNavigationProp<'RelationshipListItemTooltipModal'>,
...
@@ -124,7 +134,7 @@
BaseTooltipProps<'RelationshipListItemTooltipModal'>,
> = createTooltip<'RelationshipListItemTooltipModal'>(
RelationshipListItemTooltipButton,
- spec,
+ TooltipMenu,
);
export default RelationshipListItemTooltipModal;
diff --git a/native/tooltip/tooltip-context.react.js b/native/tooltip/tooltip-context.react.js
new file mode 100644
--- /dev/null
+++ b/native/tooltip/tooltip-context.react.js
@@ -0,0 +1,186 @@
+// @flow
+
+import { useActionSheet } from '@expo/react-native-action-sheet';
+import * as React from 'react';
+import { Platform, StyleSheet } from 'react-native';
+
+import type { TooltipItemBaseProps } from './tooltip-item.react';
+
+type RegisterOptionInput = {
+ ...TooltipItemBaseProps,
+ +symbol: symbol,
+};
+type RegisterOptionOutput = { +shouldRender: boolean };
+export type TooltipContextType = {
+ +registerOption: (input: RegisterOptionInput) => RegisterOptionOutput,
+ +unregisterOption: (id: string) => void,
+ +showActionSheet: () => void,
+ +shouldShowMore: () => boolean,
+ +getNumVisibleEntries: () => number,
+};
+const TooltipContext: React.Context<?TooltipContextType> = React.createContext<?TooltipContextType>();
+
+type ProviderProps = {
+ +maxOptionsToDisplay: number,
+ +visibleEntryIDs: ?$ReadOnlyArray<string>,
+ +cancel: () => mixed,
+ +children: React.Node,
+};
+function TooltipContextProvider(props: ProviderProps): React.Node {
+ const optionsRef = React.useRef<RegisterOptionInput[]>([]);
+
+ const { visibleEntryIDs } = props;
+ const visibleEntryIDsSet = React.useMemo(
+ () => new Set(visibleEntryIDs ?? []),
+ [visibleEntryIDs],
+ );
+
+ const { maxOptionsToDisplay } = props;
+ const registerOption = React.useCallback(
+ (input: RegisterOptionInput) => {
+ const options = optionsRef.current;
+
+ const existingIndex = options.findIndex(option => option.id === input.id);
+ if (existingIndex === -1) {
+ options.push(input);
+ } else {
+ if (options[existingIndex].symbol !== input.symbol) {
+ console.warn(`multiple TooltipItems registered with ID ${input.id}`);
+ }
+ options[existingIndex] = input;
+ }
+
+ const optionsToDisplay = options.filter(option =>
+ visibleEntryIDsSet.has(option.id),
+ );
+ const displayIndex = optionsToDisplay.findIndex(
+ option => option.id === input.id,
+ );
+ const cutoff =
+ optionsToDisplay.length === maxOptionsToDisplay
+ ? maxOptionsToDisplay
+ : maxOptionsToDisplay - 1;
+
+ const shouldRender =
+ input.id === 'more' || (displayIndex >= 0 && displayIndex < cutoff);
+ return { shouldRender };
+ },
+ [maxOptionsToDisplay, visibleEntryIDsSet],
+ );
+
+ const unregisterOption = React.useCallback((id: string) => {
+ optionsRef.current = optionsRef.current.filter(option => option.id !== id);
+ }, []);
+
+ const { cancel } = props;
+ const { showActionSheetWithOptions } = useActionSheet();
+ const showActionSheet = React.useCallback(() => {
+ const options = optionsRef.current;
+
+ const optionsToDisplay = options.filter(option =>
+ visibleEntryIDsSet.has(option.id),
+ );
+
+ const filteredOptions = optionsToDisplay.slice(maxOptionsToDisplay - 1);
+
+ const cancelButtonExists = options.some(option => option.isCancel);
+ if (Platform.OS === 'ios' && !cancelButtonExists) {
+ filteredOptions.push({
+ id: 'cancel',
+ text: 'Cancel',
+ onPress: cancel,
+ isCancel: true,
+ symbol: Symbol(),
+ });
+ }
+
+ // We're reversing options to populate the action sheet from bottom to
+ // top instead of the default (top to bottom) ordering.
+ filteredOptions.reverse();
+
+ const texts = filteredOptions.map(option => option.text);
+
+ const destructiveButtonIndices = filteredOptions
+ .filter(option => option.isDestructive)
+ .map((_, i) => i);
+
+ const cancelButtonIndex = filteredOptions.findIndex(
+ option => option.isCancel,
+ );
+
+ const icons = filteredOptions.map(option =>
+ option.renderIcon ? option.renderIcon(styles.bottomSheetIcon) : undefined,
+ );
+
+ const onPressAction = (selectedIndex: ?number) => {
+ const index = selectedIndex ?? 0;
+ filteredOptions[index].onPress();
+ };
+
+ const containerStyle = {
+ paddingBottom: 24,
+ };
+ showActionSheetWithOptions(
+ {
+ options: texts,
+ cancelButtonIndex,
+ destructiveButtonIndex: destructiveButtonIndices,
+ containerStyle,
+ icons,
+ },
+ onPressAction,
+ );
+ }, [
+ maxOptionsToDisplay,
+ visibleEntryIDsSet,
+ cancel,
+ showActionSheetWithOptions,
+ ]);
+
+ const shouldShowMore = React.useCallback(() => {
+ const options = optionsRef.current;
+ const optionsToDisplay = options.filter(option =>
+ visibleEntryIDsSet.has(option.id),
+ );
+ return optionsToDisplay.length > maxOptionsToDisplay;
+ }, [maxOptionsToDisplay, visibleEntryIDsSet]);
+
+ const getNumVisibleEntries = React.useCallback(() => {
+ const options = optionsRef.current;
+ const optionsToDisplay = options.filter(option =>
+ visibleEntryIDsSet.has(option.id),
+ );
+ return Math.min(optionsToDisplay.length, maxOptionsToDisplay);
+ }, [maxOptionsToDisplay, visibleEntryIDsSet]);
+
+ const context = React.useMemo(
+ () => ({
+ registerOption,
+ unregisterOption,
+ showActionSheet,
+ shouldShowMore,
+ getNumVisibleEntries,
+ }),
+ [
+ registerOption,
+ unregisterOption,
+ showActionSheet,
+ shouldShowMore,
+ getNumVisibleEntries,
+ ],
+ );
+ const { children } = props;
+ return (
+ <TooltipContext.Provider value={context}>
+ {children}
+ </TooltipContext.Provider>
+ );
+}
+
+const styles = StyleSheet.create({
+ bottomSheetIcon: {
+ color: '#000000',
+ },
+});
+
+export { TooltipContext, TooltipContextProvider };
diff --git a/native/tooltip/tooltip-item.react.js b/native/tooltip/tooltip-item.react.js
new file mode 100644
--- /dev/null
+++ b/native/tooltip/tooltip-item.react.js
@@ -0,0 +1,77 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+import { TouchableOpacity } from 'react-native';
+
+import { SingleLine } from '../components/single-line.react';
+import { useStyles } from '../themes/colors';
+import type { ViewStyle, TextStyle } from '../types/styles';
+import { TooltipContext } from './tooltip-context.react';
+
+export type TooltipItemBaseProps = {
+ +id: string,
+ +text: string,
+ +onPress: () => mixed,
+ +renderIcon?: (iconStyle: TextStyle) => React.Node,
+ +isDestructive?: boolean,
+ +isCancel?: boolean,
+};
+type Props = {
+ ...TooltipItemBaseProps,
+ +containerStyle?: ViewStyle,
+ +closeTooltip: () => mixed,
+};
+function TooltipItem(props: Props): React.Node {
+ const tooltipContext = React.useContext(TooltipContext);
+ invariant(tooltipContext, 'TooltipContext should be set in TooltipItem');
+ const { registerOption, unregisterOption } = tooltipContext;
+
+ const symbolRef = React.useRef(Symbol());
+
+ const { containerStyle, closeTooltip, ...contextRegistrationInput } = props;
+ const { shouldRender } = registerOption({
+ ...contextRegistrationInput,
+ symbol: symbolRef.current,
+ });
+
+ const { id, text, renderIcon, onPress: onPressItem } = props;
+
+ React.useEffect(() => {
+ return () => unregisterOption(id);
+ }, [unregisterOption, id]);
+
+ const styles = useStyles(unboundStyles);
+
+ const onPress = React.useCallback(() => {
+ onPressItem();
+ closeTooltip();
+ }, [onPressItem, closeTooltip]);
+
+ if (!shouldRender) {
+ return null;
+ }
+
+ const icon = renderIcon ? renderIcon(styles.icon) : null;
+
+ return (
+ <TouchableOpacity onPress={onPress} style={containerStyle}>
+ {icon}
+ <SingleLine style={styles.label}>{text}</SingleLine>
+ </TouchableOpacity>
+ );
+}
+
+const unboundStyles = {
+ label: {
+ color: 'modalForegroundLabel',
+ fontSize: 14,
+ lineHeight: 17,
+ textAlign: 'center',
+ },
+ icon: {
+ color: 'modalForegroundLabel',
+ },
+};
+
+export default TooltipItem;
diff --git a/native/navigation/tooltip.react.js b/native/tooltip/tooltip.react.js
rename from native/navigation/tooltip.react.js
rename to native/tooltip/tooltip.react.js
--- a/native/navigation/tooltip.react.js
+++ b/native/tooltip/tooltip.react.js
@@ -1,9 +1,5 @@
// @flow
-import {
- useActionSheet,
- type ShowActionSheetWithOptions,
-} from '@expo/react-native-action-sheet';
import type { RouteProp } from '@react-navigation/native';
import * as Haptics from 'expo-haptics';
import invariant from 'invariant';
@@ -12,7 +8,6 @@
View,
TouchableWithoutFeedback,
Platform,
- TouchableOpacity,
Keyboard,
} from 'react-native';
import Animated, {
@@ -22,28 +17,17 @@
useSharedValue,
type SharedValue,
} from 'react-native-reanimated';
-import { useDispatch } from 'react-redux';
-import {
- type ServerCallState,
- serverCallStateSelector,
-} from 'lib/selectors/server-calls';
import type { SetState } from 'lib/types/hook-types';
-import type { Dispatch } from 'lib/types/redux-types';
-import {
- createBoundServerCallsSelector,
- useDispatchActionPromise,
- type DispatchActionPromise,
- type ActionFunc,
- type BindServerCall,
- type DispatchFunctions,
-} from 'lib/utils/action-utils';
import { ChatContext, type ChatContextType } from '../chat/chat-context';
-import CommIcon from '../components/comm-icon.react';
-import { SingleLine } from '../components/single-line.react';
import SWMansionIcon from '../components/swmansion-icon.react';
-import { type InputState, InputStateContext } from '../input/input-state';
+import type { AppNavigationProp } from '../navigation/app-navigator.react';
+import {
+ OverlayContext,
+ type OverlayContextType,
+} from '../navigation/overlay-context';
+import type { TooltipModalParamList } from '../navigation/route-names';
import { type DimensionsInfo } from '../redux/dimensions-updater.react';
import { useSelector } from '../redux/redux-utils';
import { useStyles } from '../themes/colors';
@@ -52,40 +36,20 @@
type LayoutCoordinates,
} from '../types/layout-types';
import type { LayoutEvent } from '../types/react-native';
-import { AnimatedView, type ViewStyle, type TextStyle } from '../types/styles';
-import type { AppNavigationProp } from './app-navigator.react';
-import { OverlayContext, type OverlayContextType } from './overlay-context';
-import type { TooltipModalParamList } from './route-names';
+import { AnimatedView } from '../types/styles';
+import {
+ TooltipContextProvider,
+ TooltipContext,
+ type TooltipContextType,
+} from './tooltip-context.react';
+import BaseTooltipItem, {
+ type TooltipItemBaseProps,
+} from './tooltip-item.react';
/* eslint-disable import/no-named-as-default-member */
const { Value, Node, Extrapolate, add, multiply, interpolateNode } = Animated;
/* eslint-enable import/no-named-as-default-member */
-export type TooltipEntry<RouteName: $Keys<TooltipModalParamList>> = {
- +id: string,
- +text: string,
- +onPress: (
- route: TooltipRoute<RouteName>,
- dispatchFunctions: DispatchFunctions,
- bindServerCall: BindServerCall,
- inputState: ?InputState,
- navigation: AppNavigationProp<RouteName>,
- viewerID: ?string,
- chatContext: ?ChatContextType,
- ) => mixed,
-};
-type TooltipItemProps<RouteName> = {
- +spec: TooltipEntry<RouteName>,
- +onPress: (entry: TooltipEntry<RouteName>) => void,
- +containerStyle?: ViewStyle,
- +labelStyle?: TextStyle,
- +styles: typeof unboundStyles,
-};
-type TooltipSpec<RouteName> = {
- +entries: $ReadOnlyArray<TooltipEntry<RouteName>>,
- +labelStyle?: ViewStyle,
-};
-
export type TooltipParams<CustomProps> = {
...CustomProps,
+presentedFrom: string,
@@ -116,24 +80,22 @@
...Base,
// Redux state
+dimensions: DimensionsInfo,
- +serverCallState: ServerCallState,
- +viewerID: ?string,
- +nextReactionMessageLocalID: number,
- // Redux dispatch functions
- +dispatch: Dispatch,
- +dispatchActionPromise: DispatchActionPromise,
- // withOverlayContext
+overlayContext: ?OverlayContextType,
- // withInputState
- +inputState: ?InputState,
+chatContext: ?ChatContextType,
- +showActionSheetWithOptions: ShowActionSheetWithOptions,
+actionSheetShown: SharedValue<boolean>,
+hideTooltip: boolean,
+setHideTooltip: SetState<boolean>,
+showEmojiKeyboard: SharedValue<boolean>,
+exitAnimationWorklet: (finished: boolean) => void,
+styles: typeof unboundStyles,
+ +tooltipContext: TooltipContextType,
+ +closeTooltip: () => mixed,
+ +boundTooltipItem: React.ComponentType<TooltipItemBaseProps>,
+};
+
+export type TooltipMenuProps<RouteName> = {
+ ...BaseTooltipProps<RouteName>,
+ +tooltipItem: React.ComponentType<TooltipItemBaseProps>,
};
function createTooltip<
@@ -141,51 +103,8 @@
BaseTooltipPropsType: BaseTooltipProps<RouteName> = BaseTooltipProps<RouteName>,
>(
ButtonComponent: React.ComponentType<ButtonProps<BaseTooltipPropsType>>,
- tooltipSpec: TooltipSpec<RouteName>,
+ MenuComponent: React.ComponentType<TooltipMenuProps<RouteName>>,
): React.ComponentType<BaseTooltipPropsType> {
- class TooltipItem extends React.PureComponent<TooltipItemProps<RouteName>> {
- render() {
- let icon;
- const { styles } = this.props;
- if (this.props.spec.id === 'copy') {
- icon = <SWMansionIcon name="copy" style={styles.icon} size={16} />;
- } else if (this.props.spec.id === 'reply') {
- icon = <CommIcon name="reply" style={styles.icon} size={12} />;
- } else if (this.props.spec.id === 'report') {
- icon = (
- <SWMansionIcon name="warning-circle" style={styles.icon} size={16} />
- );
- } else if (this.props.spec.id === 'sidebar') {
- icon = (
- <SWMansionIcon
- name="message-circle-lines"
- style={styles.icon}
- size={16}
- />
- );
- } else if (this.props.spec.id === 'more') {
- icon = (
- <SWMansionIcon name="menu-vertical" style={styles.icon} size={16} />
- );
- }
-
- return (
- <TouchableOpacity
- onPress={this.onPress}
- style={this.props.containerStyle}
- >
- {icon}
- <SingleLine style={[styles.label, this.props.labelStyle]}>
- {this.props.spec.text}
- </SingleLine>
- </TouchableOpacity>
- );
- }
-
- onPress = () => {
- this.props.onPress(this.props.spec);
- };
- }
class Tooltip extends React.PureComponent<
TooltipProps<BaseTooltipPropsType>,
> {
@@ -243,21 +162,11 @@
Haptics.impactAsync();
}
- get entries(): $ReadOnlyArray<TooltipEntry<RouteName>> {
- const { entries } = tooltipSpec;
- const { visibleEntryIDs } = this.props.route.params;
- if (!visibleEntryIDs) {
- return entries;
- }
- const visibleSet = new Set(visibleEntryIDs);
- return entries.filter(entry => visibleSet.has(entry.id));
- }
-
get tooltipHeight(): number {
if (this.props.route.params.tooltipLocation === 'fixed') {
return fixedTooltipHeight;
} else {
- return tooltipHeight(this.entries.length);
+ return tooltipHeight(this.props.tooltipContext.getNumVisibleEntries());
}
}
@@ -390,21 +299,17 @@
render() {
const {
dimensions,
- serverCallState,
- viewerID,
- nextReactionMessageLocalID,
- dispatch,
- dispatchActionPromise,
overlayContext,
- inputState,
chatContext,
- showActionSheetWithOptions,
actionSheetShown,
hideTooltip,
setHideTooltip,
showEmojiKeyboard,
exitAnimationWorklet,
styles,
+ tooltipContext,
+ closeTooltip,
+ boundTooltipItem,
...navAndRouteForFlow
} = this.props;
@@ -414,46 +319,26 @@
tooltipContainerStyle.push(styles.itemContainerFixed);
}
- const { entries } = this;
- const items = entries.map((entry, index) => {
- let style;
- if (this.tooltipLocation === 'fixed') {
- style = index !== entries.length - 1 ? styles.itemMarginFixed : null;
- } else {
- style = index !== entries.length - 1 ? styles.itemMargin : null;
- }
- return (
- <TooltipItem
- key={index}
- spec={entry}
- onPress={this.onPressEntry}
- containerStyle={[...tooltipContainerStyle, style]}
- labelStyle={tooltipSpec.labelStyle}
- styles={styles}
- />
- );
- });
-
- if (this.tooltipLocation === 'fixed' && entries.length > 3) {
- items.splice(3);
-
- const moreSpec = {
- id: 'more',
- text: 'More',
- onPress: this.onPressMore,
- };
+ const items = [
+ <MenuComponent
+ {...navAndRouteForFlow}
+ tooltipItem={this.getTooltipItem()}
+ key="menu"
+ />,
+ ];
- const moreTooltipItem = (
- <TooltipItem
- key={entries.length}
- spec={moreSpec}
- onPress={moreSpec.onPress}
+ if (this.props.tooltipContext.shouldShowMore()) {
+ items.push(
+ <BaseTooltipItem
+ id="more"
+ text="More"
+ onPress={this.onPressMore}
+ renderIcon={this.renderMoreIcon}
containerStyle={tooltipContainerStyle}
- styles={styles}
- />
+ closeTooltip={this.props.closeTooltip}
+ key="more"
+ />,
);
-
- items.push(moreTooltipItem);
}
let triangleStyle;
@@ -532,7 +417,7 @@
}
return (
- <TouchableWithoutFeedback onPress={this.onPressBackdrop}>
+ <TouchableWithoutFeedback onPress={this.props.closeTooltip}>
<View style={styles.container}>
<AnimatedView style={this.opacityStyle} />
<View style={this.contentContainerStyle}>
@@ -546,147 +431,25 @@
);
}
- onPressBackdrop = () => {
- if (this.tooltipLocation !== 'fixed') {
- this.props.navigation.goBackOnce();
- } else {
- this.props.setHideTooltip(true);
- }
- this.props.showEmojiKeyboard.value = false;
- };
-
- onPressEntry = (entry: TooltipEntry<RouteName>) => {
- if (
- this.tooltipLocation !== 'fixed' ||
- this.props.actionSheetShown.value
- ) {
- this.props.navigation.goBackOnce();
- } else {
- this.props.setHideTooltip(true);
- }
-
- const dispatchFunctions = {
- dispatch: this.props.dispatch,
- dispatchActionPromise: this.props.dispatchActionPromise,
- };
- entry.onPress(
- this.props.route,
- dispatchFunctions,
- this.bindServerCall,
- this.props.inputState,
- this.props.navigation,
- this.props.viewerID,
- this.props.chatContext,
- );
- };
+ getTooltipItem() {
+ const BoundTooltipItem = this.props.boundTooltipItem;
+ return BoundTooltipItem;
+ }
onPressMore = () => {
Keyboard.dismiss();
this.props.actionSheetShown.value = true;
this.props.setHideTooltip(true);
+ this.props.tooltipContext.showActionSheet();
+ };
- const { entries } = this;
- const options = entries.map(entry => entry.text);
-
- const {
- destructiveButtonIndex,
- cancelButtonIndex,
- } = this.getPlatformSpecificButtonIndices(options);
-
- // We're reversing options to populate the action sheet from bottom to
- // top instead of the default (top to bottom) ordering.
- options.reverse();
-
- const containerStyle = {
- paddingBottom: 24,
- };
-
+ renderMoreIcon = () => {
const { styles } = this.props;
- const icons = [
- <SWMansionIcon
- key="report"
- name="warning-circle"
- style={styles.bottomSheetIcon}
- size={16}
- />,
- <SWMansionIcon
- key="copy"
- name="copy"
- style={styles.bottomSheetIcon}
- size={16}
- />,
- <SWMansionIcon
- key="thread"
- name="message-circle-lines"
- style={styles.bottomSheetIcon}
- size={16}
- />,
- <CommIcon
- key="reply"
- name="reply"
- style={styles.bottomSheetIcon}
- size={12}
- />,
- ];
-
- const onPressAction = (selectedIndex?: number) => {
- if (selectedIndex === cancelButtonIndex) {
- this.props.navigation.goBackOnce();
- return;
- }
- const index = entries.length - (selectedIndex ?? 0);
- const entry = entries[Platform.OS === 'ios' ? index : index - 1];
- this.onPressEntry(entry);
- };
-
- this.props.showActionSheetWithOptions(
- {
- options,
- cancelButtonIndex,
- destructiveButtonIndex,
- containerStyle,
- icons,
- },
- onPressAction,
+ return (
+ <SWMansionIcon name="menu-vertical" style={styles.icon} size={16} />
);
};
- getPlatformSpecificButtonIndices = (options: Array<string>) => {
- let destructiveButtonIndex;
- if (Platform.OS === 'ios') {
- const reportIndex = options.findIndex(option => option === 'Report');
- destructiveButtonIndex =
- reportIndex !== -1 ? options.length - reportIndex : undefined;
- }
-
- const cancelButtonIndex = Platform.OS === 'ios' ? 0 : -1;
-
- // The "Cancel" action is iOS-specific
- if (Platform.OS === 'ios') {
- options.push('Cancel');
- }
-
- return { destructiveButtonIndex, cancelButtonIndex };
- };
-
- bindServerCall = <F>(serverCall: ActionFunc<F>): F => {
- const {
- cookie,
- urlPrefix,
- sessionID,
- currentUserInfo,
- connectionStatus,
- } = this.props.serverCallState;
- return createBoundServerCallsSelector(serverCall)({
- dispatch: this.props.dispatch,
- cookie,
- urlPrefix,
- sessionID,
- currentUserInfo,
- connectionStatus,
- });
- };
-
onTooltipContainerLayout = (event: LayoutEvent) => {
const { route, dimensions } = this.props;
const { x, width } = route.params.initialCoordinates;
@@ -704,21 +467,9 @@
}
};
}
- return React.memo<BaseTooltipPropsType>(function ConnectedTooltip(
- props: BaseTooltipPropsType,
- ) {
- const { showActionSheetWithOptions } = useActionSheet();
-
+ function ConnectedTooltip(props: BaseTooltipPropsType) {
const dimensions = useSelector(state => state.dimensions);
- const serverCallState = useSelector(serverCallStateSelector);
- const viewerID = useSelector(
- state => state.currentUserInfo && state.currentUserInfo.id,
- );
- const nextReactionMessageLocalID = useSelector(state => state.nextLocalID);
- const dispatch = useDispatch();
- const dispatchActionPromise = useDispatchActionPromise();
const overlayContext = React.useContext(OverlayContext);
- const inputState = React.useContext(InputStateContext);
const chatContext = React.useContext(ChatContext);
const actionSheetShown = useSharedValue(false);
@@ -726,11 +477,12 @@
const showEmojiKeyboard = useSharedValue(false);
+ const { goBackOnce } = props.navigation;
const goBackCallback = React.useCallback(() => {
- if (!actionSheetShown.value && !showEmojiKeyboard.value) {
- props.navigation.goBackOnce();
+ if (!actionSheetShown.value) {
+ goBackOnce();
}
- }, [actionSheetShown.value, props.navigation, showEmojiKeyboard.value]);
+ }, [actionSheetShown.value, goBackOnce]);
const exitAnimationWorklet = React.useCallback(
finished => {
@@ -742,30 +494,70 @@
[goBackCallback],
);
+ const { params } = props.route;
+ const { tooltipLocation } = params;
+ const isFixed = tooltipLocation === 'fixed';
+
+ const closeTooltip = React.useCallback(() => {
+ if (isFixed && !actionSheetShown.value) {
+ setHideTooltip(true);
+ } else {
+ goBackOnce();
+ }
+ showEmojiKeyboard.value = false;
+ }, [isFixed, actionSheetShown.value, goBackOnce, showEmojiKeyboard]);
+
const styles = useStyles(unboundStyles);
+ const boundTooltipItem = React.useCallback(
+ innerProps => {
+ const containerStyle = isFixed
+ ? [styles.itemContainer, styles.itemContainerFixed]
+ : styles.itemContainer;
+ return (
+ <BaseTooltipItem
+ {...innerProps}
+ containerStyle={containerStyle}
+ closeTooltip={closeTooltip}
+ />
+ );
+ },
+ [isFixed, styles, closeTooltip],
+ );
+ const tooltipContext = React.useContext(TooltipContext);
+ invariant(tooltipContext, 'TooltipContext should be set in Tooltip');
return (
<Tooltip
{...props}
dimensions={dimensions}
- serverCallState={serverCallState}
- viewerID={viewerID}
- nextReactionMessageLocalID={nextReactionMessageLocalID}
- dispatch={dispatch}
- dispatchActionPromise={dispatchActionPromise}
overlayContext={overlayContext}
- inputState={inputState}
chatContext={chatContext}
- showActionSheetWithOptions={showActionSheetWithOptions}
actionSheetShown={actionSheetShown}
hideTooltip={hideTooltip}
setHideTooltip={setHideTooltip}
showEmojiKeyboard={showEmojiKeyboard}
exitAnimationWorklet={exitAnimationWorklet}
styles={styles}
+ tooltipContext={tooltipContext}
+ closeTooltip={closeTooltip}
+ boundTooltipItem={boundTooltipItem}
/>
);
- });
+ }
+ function MemoizedTooltip(props: BaseTooltipPropsType) {
+ const { visibleEntryIDs } = props.route.params;
+ const { goBackOnce } = props.navigation;
+ return (
+ <TooltipContextProvider
+ maxOptionsToDisplay={4}
+ visibleEntryIDs={visibleEntryIDs}
+ cancel={goBackOnce}
+ >
+ <ConnectedTooltip {...props} />
+ </TooltipContextProvider>
+ );
+ }
+ return React.memo<BaseTooltipPropsType>(MemoizedTooltip);
}
const unboundStyles = {
@@ -777,9 +569,6 @@
right: 0,
top: 0,
},
- bottomSheetIcon: {
- color: '#000000',
- },
container: {
flex: 1,
},
@@ -800,14 +589,6 @@
itemContainerFixed: {
flexDirection: 'column',
},
- itemMargin: {
- borderBottomColor: '#404040',
- borderBottomWidth: 1,
- },
- itemMarginFixed: {
- borderRightColor: 'panelForegroundBorder',
- borderRightWidth: 1,
- },
items: {
backgroundColor: 'tooltipBackground',
borderRadius: 5,
@@ -817,12 +598,6 @@
flex: 1,
flexDirection: 'row',
},
- label: {
- color: 'modalForegroundLabel',
- fontSize: 14,
- lineHeight: 17,
- textAlign: 'center',
- },
triangleDown: {
borderBottomColor: 'transparent',
borderBottomWidth: 0,
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Dec 20, 5:34 PM (17 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2681498
Default Alt Text
D6549.id21969.diff (56 KB)
Attached To
Mode
D6549: [native] Clean up Tooltip component
Attached
Detach File
Event Timeline
Log In to Comment