Skip to content

Commit

Permalink
message_actions: Add move message option
Browse files Browse the repository at this point in the history
  • Loading branch information
SilentCruzer committed Jan 21, 2022
1 parent 33116f3 commit 133c7e0
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/action-sheets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { doNarrow, deleteOutboxMessage, navigateToEmojiPicker, navigateToStream
import {
navigateToMessageReactionScreen,
navigateToPmConversationDetails,
navigateToMoveMessage,
} from '../nav/navActions';
import { deleteMessagesForTopic } from '../topics/topicActions';
import * as logging from '../utils/logging';
Expand Down Expand Up @@ -81,6 +82,7 @@ type MessageArgs = {
dispatch: Dispatch,
_: GetText,
startEditMessage: (editMessage: EditMessage) => void,
narrow: Narrow,
...
};

Expand Down Expand Up @@ -137,6 +139,12 @@ const editMessage = async ({ message, dispatch, startEditMessage, auth }) => {
editMessage.title = 'Edit message';
editMessage.errorMessage = 'Failed to edit message';

const moveMessage = async ({ message, dispatch, startEditMessage, auth, narrow }) => {
NavigationService.dispatch(navigateToMoveMessage(message, narrow));
};
moveMessage.title = 'Move message';
moveMessage.errorMessage = 'Move message';

const deleteMessage = async ({ auth, message, dispatch }) => {
if (message.isOutbox) {
dispatch(deleteOutboxMessage(message.timestamp));
Expand Down Expand Up @@ -449,6 +457,7 @@ export const constructMessageActionButtons = ({
&& (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.
Expand Down
216 changes: 216 additions & 0 deletions src/message/MoveMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/* @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 Toast from 'react-native-simple-toast';
import { ThemeContext, BRAND_COLOR } from '../styles';
import type { RouteProp } from '../react-navigation';
import * as api from '../api';
import { Input } from '../common';
import type { AppNavigationProp } from '../nav/AppNavigator';
import { getAuth, getStreams, getOwnUser } from '../selectors';
import { useSelector } from '../react-redux';
import { showErrorAlert } from '../utils/info';
import { Icon } from '../common/Icons';
import type { Narrow, Message, Outbox } from '../types';
import TopicAutocomplete from '../autocomplete/TopicAutocomplete';
import { streamNameOfNarrow, streamNarrow } from '../utils/narrow';

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 currentStreamName = streamNameOfNarrow(props.route.params.messageNarrow);
const currentStreamId = allStreams.find(s => s.name === currentStreamName)?.stream_id;
const [narrow, setNarrow] = useState(streamNarrow(currentStreamName));
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 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 = () => {
if (isAdmin) {
api
.updateMessage(
auth,
{ subject, stream_id: streamId, propagate_mode: propagateMode },
messageId,
)
.catch(error => {
showErrorAlert('Failed to move message', error.message);
props.navigation.goBack();
});
} else {
api
.updateMessage(auth, { subject, propagate_mode: propagateMode }, messageId)
.catch(error => {
showErrorAlert('Failed to move message', error.message);
props.navigation.goBack();
});
}
props.navigation.goBack();
Toast.show('Moved Message');
};

const handleNarrow = (pickedStreamId: number) => {
setStreamId(pickedStreamId);
const pickedStreamName = allStreams.find(s => s.stream_id === pickedStreamId)?.name;
setNarrow(streamNarrow(String(pickedStreamName)));
};

const handlePropagateMode = (propagatePickerIndex: number) => {
if (propagatePickerIndex === 2) {
setPropagateMode('change_all');
} else if (propagatePickerIndex === 1) {
setPropagateMode('change_later');
} else {
setPropagateMode('change_one');
}
};

return (
<ScrollView style={styles.parent}>
<View style={styles.layout}>
<View style={styles.viewTitle}>
<TouchableOpacity onPress={() => props.navigation.goBack()}>
<Icon size={20} color="gray" name={iconName} />
</TouchableOpacity>
<Text style={styles.title}>Move Message</Text>
<View />
</View>
<Text style={{ fontSize: 14, marginBottom: 10, color: 'gray' }}>Content:</Text>
<Text style={[styles.textColor, { marginBottom: 20 }]}>
{props.route.params.message.content.replace(/<(?:.|\n)*?>/gm, '')}
</Text>
<Text style={{ fontSize: 14, color: 'gray', marginBottom: 10 }}>Stream:</Text>
{isAdmin ? (
<View style={styles.picker}>
<Picker
selectedValue={currentStreamName}
onValueChange={(itemValue, itemIndex) => handleNarrow(parseInt(itemValue, 10))}
style={styles.textColor}
>
{allStreams.map(item => (
<Picker.Item label={item.name} value={item.stream_id.toString()} />
))}
</Picker>
</View>
) : (
<Text style={[styles.textColor, { marginBottom: 10 }]}>{currentStreamName}</Text>
)}
<Text style={{ fontSize: 14, color: 'gray', marginBottom: 10 }}>Topic:</Text>
<View style={{ marginBottom: 20 }}>
<Input
underlineColorAndroid="transparent"
placeholder="Topic"
autoFocus={false}
defaultValue={subject}
selectTextOnFocus
onChangeText={value => handleTopicChange(value)}
onFocus={handleTopicFocus}
blurOnSubmit={false}
returnKeyType="next"
style={styles.topicInput}
/>
<TopicAutocomplete
isFocused={topicFocus}
narrow={narrow}
text={subject}
onAutocomplete={handleTopicAutocomplete}
/>
</View>
<Text style={{ fontSize: 14, color: 'gray', marginBottom: 10 }}>Move options:</Text>
<View style={styles.picker}>
<Picker
selectedValue={propagateMode}
onValueChange={(itemValue, itemIndex) => handlePropagateMode(itemIndex)}
style={styles.textColor}
>
<Picker.Item label="Change only this message" />
<Picker.Item label="Change later messages to this topic" />
<Picker.Item label="Change previous and following messages to this topic" />
</Picker>
</View>
<TouchableOpacity onPress={updateMessage} style={styles.submitButton}>
<Text style={{ textAlign: 'center' }}>Submit</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
}
3 changes: 3 additions & 0 deletions src/nav/AppNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import SettingsScreen from '../settings/SettingsScreen';
import UserStatusScreen from '../user-status/UserStatusScreen';
import SharingScreen from '../sharing/SharingScreen';
import { useHaveServerDataGate } from '../withHaveServerDataGate';
import moveMessage from '../message/MoveMessage';

export type AppNavigatorParamList = {|
'account-pick': RouteParamsOf<typeof AccountPickScreen>,
Expand All @@ -56,6 +57,7 @@ export type AppNavigatorParamList = {|
'emoji-picker': RouteParamsOf<typeof EmojiPickerScreen>,
'main-tabs': RouteParamsOf<typeof MainTabsScreen>,
'message-reactions': RouteParamsOf<typeof MessageReactionsScreen>,
'move-message': RouteParamsOf<typeof moveMessage>,
'password-auth': RouteParamsOf<typeof PasswordAuthScreen>,
'realm-input': RouteParamsOf<typeof RealmInputScreen>,
'search-messages': RouteParamsOf<typeof SearchMessagesScreen>,
Expand Down Expand Up @@ -124,6 +126,7 @@ export default function AppNavigator(props: Props): Node {
name="message-reactions"
component={useHaveServerDataGate(MessageReactionsScreen)}
/>
<Stack.Screen name="move-message" component={useHaveServerDataGate(moveMessage)} />
<Stack.Screen
name="search-messages"
component={useHaveServerDataGate(SearchMessagesScreen)}
Expand Down
11 changes: 10 additions & 1 deletion src/nav/navActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@react-navigation/native';

import * as NavigationService from './NavigationService';
import type { Message, Narrow, UserId } from '../types';
import type { Message, Narrow, UserId, Outbox } from '../types';
import type { PmKeyRecipients } from '../utils/recipient';
import type { SharedData } from '../sharing/types';
import type { ApiResponseServerSettings } from '../api/settings/getServerSettings';
Expand Down Expand Up @@ -125,6 +125,15 @@ export const navigateToMessageReactionScreen = (
reactionName?: string,
): GenericNavigationAction => 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');
Expand Down
1 change: 1 addition & 0 deletions static/translations/messages_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
"Cancel": "Cancel",
"Message copied": "Message copied",
"Edit message": "Edit message",
"Move message": "Move message",
"Network request failed": "Network request failed",
"Failed to add reaction": "Failed to add reaction",
"Failed to reply": "Failed to reply",
Expand Down

0 comments on commit 133c7e0

Please sign in to comment.