From beb2a522919db1325db807955d5c5f64c7d5c024 Mon Sep 17 00:00:00 2001 From: SilentCruzer Date: Tue, 3 May 2022 14:14:52 +0530 Subject: [PATCH 1/2] api.updateMessage: Add params stream_id and propagate_mode --- src/api/messages/updateMessage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/messages/updateMessage.js b/src/api/messages/updateMessage.js index af3f92c8c7d..984ac99fb4d 100644 --- a/src/api/messages/updateMessage.js +++ b/src/api/messages/updateMessage.js @@ -22,6 +22,7 @@ export default async ( propagate_mode?: PropagateMode, content?: string, + stream_id?: number, send_notification_to_old_thread?: boolean, send_notification_to_new_thread?: boolean, From d2177682ea0d6d8664733c2e72d179adea4e22cd Mon Sep 17 00:00:00 2001 From: SilentCruzer Date: Tue, 3 May 2022 14:22:10 +0530 Subject: [PATCH 2/2] message_actions: Add move message option --- src/action-sheets/index.js | 11 ++ src/message/MoveMessage.js | 211 +++++++++++++++++++++++++++ src/nav/AppNavigator.js | 3 + src/nav/navActions.js | 11 +- static/translations/messages_en.json | 4 + 5 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 src/message/MoveMessage.js diff --git a/src/action-sheets/index.js b/src/action-sheets/index.js index e0199d8d57a..6073c124b1b 100644 --- a/src/action-sheets/index.js +++ b/src/action-sheets/index.js @@ -42,6 +42,7 @@ import { import { navigateToMessageReactionScreen, navigateToPmConversationDetails, + navigateToMoveMessage, } from '../nav/navActions'; import { deleteMessagesForTopic } from '../topics/topicActions'; import * as logging from '../utils/logging'; @@ -91,6 +92,7 @@ type MessageArgs = { dispatch: Dispatch, _: GetText, startEditMessage: (editMessage: EditMessage) => void, + narrow: Narrow, ... }; @@ -248,6 +250,14 @@ const resolveTopic = { action: toggleResolveTopic, }; +const moveMessage = { + title: 'Move message', + errorMessage: 'Failed to move message', + action: async ({ message, narrow }) => { + NavigationService.dispatch(navigateToMoveMessage(message, narrow)); + }, +}; + const unresolveTopic = { title: 'Unresolve topic', errorMessage: 'Failed to unresolve topic', @@ -574,6 +584,7 @@ export const constructMessageActionButtons = (args: {| && (isStreamOrTopicNarrow(narrow) || isPmNarrow(narrow)) ) { buttons.push(editMessage); + buttons.push(moveMessage); } if (message.sender_id === ownUser.user_id && messageNotDeleted(message)) { // TODO(#2793): Don't show if message isn't deletable. diff --git a/src/message/MoveMessage.js b/src/message/MoveMessage.js new file mode 100644 index 00000000000..ad3e2998626 --- /dev/null +++ b/src/message/MoveMessage.js @@ -0,0 +1,211 @@ +/* @flow strict-local */ +import React, { useState, useContext } from 'react'; +import { Text, View, Platform, Picker, TouchableOpacity, ScrollView } from 'react-native'; +import type { Node } from 'react'; +import { ThemeContext, BRAND_COLOR } from '../styles'; +import type { RouteProp } from '../react-navigation'; +import * as api from '../api'; +import Input from '../common/Input'; +import { streamNarrow, streamIdOfNarrow } from '../utils/narrow'; +import { getStreamForId } from '../subscriptions/subscriptionSelectors'; +import type { AppNavigationProp } from '../nav/AppNavigator'; +import { getAuth, getStreams, getOwnUser } from '../selectors'; +import { useSelector } from '../react-redux'; +import { showErrorAlert, showToast } from '../utils/info'; +import { Icon } from '../common/Icons'; +import type { Narrow, Message, Outbox } from '../types'; +import TopicAutocomplete from '../autocomplete/TopicAutocomplete'; +import { TranslationContext } from '../boot/TranslationProvider'; +import ZulipButton from '../common/ZulipButton'; + +type Props = $ReadOnly<{| + navigation: AppNavigationProp<'move-message'>, + route: RouteProp<'move-message', {| message: Message | Outbox, messageNarrow: Narrow |}>, +|}>; + +const inputMarginPadding = { + paddingHorizontal: 8, + paddingVertical: Platform.select({ + ios: 8, + android: 2, + }), +}; + +export default function MoveMessage(props: Props): Node { + const themeContext = useContext(ThemeContext); + const backgroundColor = themeContext.backgroundColor; + const cardColor = themeContext.cardColor; + const iconName = Platform.OS === 'android' ? 'arrow-left' : 'chevron-left'; + const auth = useSelector(getAuth); + const allStreams = useSelector(getStreams); + const isAdmin = useSelector(getOwnUser).is_admin; + const messageId = props.route.params.message.id; + const currentStreamId = streamIdOfNarrow(props.route.params.messageNarrow); + const currentStreamName = useSelector(state => getStreamForId(state, currentStreamId)).name; + const [narrow, setNarrow] = useState(streamNarrow(currentStreamId)); + const [subject, setSubject] = useState(props.route.params.message.subject); + const [propagateMode, setPropagateMode] = useState('change_one'); + const [streamId, setStreamId] = useState(currentStreamId); + const [topicFocus, setTopicFocus] = useState(false); + const _ = useContext(TranslationContext); + + const styles = { + parent: { + backgroundColor: cardColor, + }, + layout: { + margin: 10, + }, + title: { + fontSize: 18, + color: backgroundColor === 'white' ? 'black' : 'white', + }, + topicInput: { + height: 50, + backgroundColor, + ...inputMarginPadding, + }, + viewTitle: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + height: 50, + paddingHorizontal: 10, + marginBottom: 20, + }, + textColor: { + color: backgroundColor === 'white' ? 'black' : 'white', + }, + picker: { backgroundColor, marginBottom: 20 }, + submitButton: { + marginTop: 10, + paddingTop: 15, + paddingBottom: 15, + marginLeft: 30, + marginRight: 30, + backgroundColor: BRAND_COLOR, + borderRadius: 10, + borderWidth: 1, + }, + }; + + const handleTopicChange = (topic: string) => { + setSubject(topic); + }; + + const handleTopicFocus = () => { + setTopicFocus(true); + }; + + const setTopicInputValue = (topic: string) => { + handleTopicChange(topic); + setTopicFocus(false); + }; + + const handleTopicAutocomplete = (topic: string) => { + setTopicInputValue(topic); + }; + + const updateMessage = () => { + try { + if (isAdmin) { + api.updateMessage(auth, messageId, { + subject, + stream_id: streamId, + propagate_mode: propagateMode, + }); + } else { + api.updateMessage(auth, messageId, { subject, propagate_mode: propagateMode }); + } + } catch (error) { + showErrorAlert(_('Failed to move message'), error.message); + props.navigation.goBack(); + return; + } + props.navigation.goBack(); + showToast(_('Moved message')); + }; + + const handleNarrow = (pickedStreamId: number) => { + setStreamId(pickedStreamId); + setNarrow(streamNarrow(pickedStreamId)); + }; + + return ( + + + + props.navigation.goBack()}> + + + Move Message + + + Stream: + {isAdmin ? ( + + handleNarrow(parseInt(itemValue, 10))} + style={styles.textColor} + > + {allStreams.map(item => ( + + ))} + + + ) : ( + {currentStreamName} + )} + Topic: + + handleTopicChange(value)} + onFocus={handleTopicFocus} + blurOnSubmit={false} + returnKeyType="next" + style={styles.topicInput} + /> + + + Move options: + + setPropagateMode(itemValue)} + style={styles.textColor} + > + + + + + + Content: + + {props.route.params.message.content.replace(/<(?:.|\n)*?>/gm, '')} + + + + + ); +} diff --git a/src/nav/AppNavigator.js b/src/nav/AppNavigator.js index 5f2a0279681..1f12f81434f 100644 --- a/src/nav/AppNavigator.js +++ b/src/nav/AppNavigator.js @@ -45,6 +45,7 @@ import SettingsScreen from '../settings/SettingsScreen'; import UserStatusScreen from '../user-statuses/UserStatusScreen'; import SharingScreen from '../sharing/SharingScreen'; import { useHaveServerDataGate } from '../withHaveServerDataGate'; +import moveMessage from '../message/MoveMessage'; export type AppNavigatorParamList = {| 'account-pick': RouteParamsOf, @@ -56,6 +57,7 @@ export type AppNavigatorParamList = {| 'emoji-picker': RouteParamsOf, 'main-tabs': RouteParamsOf, 'message-reactions': RouteParamsOf, + 'move-message': RouteParamsOf, 'password-auth': RouteParamsOf, 'realm-input': RouteParamsOf, 'search-messages': RouteParamsOf, @@ -124,6 +126,7 @@ export default function AppNavigator(props: Props): Node { name="message-reactions" component={useHaveServerDataGate(MessageReactionsScreen)} /> + StackActions.push('message-reactions', { messageId, reactionName }); +export const navigateToMoveMessage = ( + message: Message | Outbox, + messageNarrow: Narrow, +): GenericNavigationAction => + StackActions.push('move-message', { + message, + messageNarrow, + }); + export const navigateToLegal = (): GenericNavigationAction => StackActions.push('legal'); export const navigateToUserStatus = (): GenericNavigationAction => StackActions.push('user-status'); diff --git a/static/translations/messages_en.json b/static/translations/messages_en.json index a91f7dc57e4..678d9d581c9 100644 --- a/static/translations/messages_en.json +++ b/static/translations/messages_en.json @@ -52,6 +52,7 @@ "Search": "Search", "Log in": "Log in", "Enter": "Enter", + "Submit": "Submit", "Switch account": "Switch account", "Log out": "Log out", "Log out?": "Log out?", @@ -200,12 +201,15 @@ "Cancel": "Cancel", "Message copied": "Message copied", "Edit message": "Edit message", + "Move message": "Move message", + "Moved message": "Moved message", "Network request failed": "Network request failed", "Failed to add reaction": "Failed to add reaction", "Failed to reply": "Failed to reply", "Failed to copy message to clipboard": "Failed to copy message to clipboard", "Failed to share message": "Failed to share message", "Failed to edit message": "Failed to edit message", + "Failed to move message": "Failed to move message", "Failed to delete message": "Failed to delete message", "Failed to star message": "Failed to star message", "Failed to unstar message": "Failed to unstar message",