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 9, 2022
1 parent 1a281fa commit 139c454
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 2 deletions.
10 changes: 10 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 @@ -137,6 +138,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 }) => {
NavigationService.dispatch(navigateToMoveMessage(message, moveMessage.narrow, moveMessage.admin));
};
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 +456,9 @@ export const constructMessageActionButtons = ({
&& (isStreamOrTopicNarrow(narrow) || isPmNarrow(narrow))
) {
buttons.push(editMessage);
moveMessage.narrow = narrow;
moveMessage.admin = ownUser.is_admin;
buttons.push(moveMessage);
}
if (message.sender_id === ownUser.user_id && messageNotDeleted(message)) {
// TODO(#2793): Don't show if message isn't deletable.
Expand Down
2 changes: 2 additions & 0 deletions src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import getServerSettings from './settings/getServerSettings';
import toggleMobilePushSettings from './settings/toggleMobilePushSettings';
import createStream from './streams/createStream';
import getStreams from './streams/getStreams';
import getStreamId from './streams/getStreamId';
import updateStream from './streams/updateStream';
import sendSubmessage from './submessages/sendSubmessage';
import getSubscriptions from './subscriptions/getSubscriptions';
Expand Down Expand Up @@ -86,6 +87,7 @@ export {
toggleMobilePushSettings,
createStream,
getStreams,
getStreamId,
updateStream,
sendSubmessage,
getSubscriptions,
Expand Down
3 changes: 2 additions & 1 deletion src/api/messages/updateMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ export default async (
auth: Auth,
params: $ReadOnly<{|
subject?: string,
propagate_mode?: boolean,
propagate_mode?: string,
content?: string,
stream_id?: number,
|}>,
id: number,
): Promise<ApiResponse> => apiPatch(auth, `messages/${id}`, params);
1 change: 1 addition & 0 deletions src/common/Icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,4 @@ export const IconUserMuted: SpecificIconType = makeIcon(FontAwesome, 'user');
export const IconAttach: SpecificIconType = makeIcon(Feather, 'paperclip');
export const IconAttachment: SpecificIconType = makeIcon(IoniconsIcon, 'document-attach-outline');
export const IconGroup: SpecificIconType = makeIcon(FontAwesome, 'group');
export const IconBack: SpecificIconType = makeIcon(FontAwesome, 'arrow-left');
225 changes: 225 additions & 0 deletions src/message/moveMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/* @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 } from '../selectors';
import { useSelector } from '../react-redux';
import { showErrorAlert } from '../utils/info';
import { IconBack } from '../common/Icons';
import type { Narrow, Message } from '../types';
import TopicAutocomplete from '../autocomplete/TopicAutocomplete';
import { streamNameOfNarrow } from '../utils/narrow';

type Props = $ReadOnly<{|
navigation: AppNavigationProp<'move-message'>,
route: RouteProp<'move-message', {| message: Message, messageNarrow: Narrow, isadmin: boolean |}>,
|}>;

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 auth = useSelector(getAuth);
const streams = useSelector(getStreams);
const isadmin = props.route.params.isadmin;
const id = props.route.params.message.id;
const names = [];
const [subject, setSubject] = useState(props.route.params.message.subject);
const [propagate_mode, setPropagateMode] = useState('javachange_one');
const [stream, setStream] = useState(streamNameOfNarrow(props.route.params.messageNarrow));
const [topicFocus, setTopicFocus] = useState(false);
const messageInputRef = React.createRef<$FlowFixMe>();
const topicInputRef = React.createRef<$FlowFixMe>();

streams.map(item => names.push({ value: item.name }));

const styles = {
parent: {
backgroundColor: cardColor,
},
layout: {
margin: 10,
},
title: {
fontSize: 18,
color: backgroundColor === 'white' ? 'black' : 'white',
},
autocompleteWrapper: {
position: 'absolute',
top: 0,
width: '100%',
},
composeText: {
flex: 1,
paddingVertical: 8,
},
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 updateTextInput = (textInput, text) => {
if (textInput === null) {
// Depending on the lifecycle events this function is called from,
// this might not be set yet.
return;
}

// `textInput` is untyped; see definition.
textInput.setNativeProps({ text });
};

const handleTopicChange = (Topic: string) => {
setSubject(Topic);
};

const handleTopicFocus = () => {
setTopicFocus(true);
};

const setTopicInputValue = (Topic: string) => {
updateTextInput(topicInputRef.current, subject);
handleTopicChange(Topic);
setTopicFocus(false);
};

const handleTopicAutocomplete = (Topic: string) => {
setTopicInputValue(Topic);
messageInputRef.current?.focus();
};

const updateMessage = async () => {
const stream_info = await api.getStreamId(auth, stream);
const stream_id = stream_info.stream_id;
if (isadmin) {
api.updateMessage(auth, { subject, stream_id, propagate_mode }, id).catch(error => {
showErrorAlert('Failed to move message', error.message);
props.navigation.goBack();
});
} else {
api.updateMessage(auth, { subject, propagate_mode }, id).catch(error => {
showErrorAlert('Failed to move message', error.message);
props.navigation.goBack();
});
}
props.navigation.goBack();
Toast.show('Moved Message');
};

return (
<ScrollView style={styles.parent}>
<View style={styles.layout}>
<View style={styles.viewTitle}>
<IconBack style={{ color: 'gray' }} size={20} onPress={() => props.navigation.goBack()} />
<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={stream}
onValueChange={(itemValue, itemIndex) => setStream(itemValue.toString())}
style={styles.textColor}
>
{streams.map(item => (
<Picker.Item label={item.name} value={item.name} key={item.name} />
))}
</Picker>
</View>
) : (
<Text style={[styles.textColor, { marginBottom: 10 }]}>{stream}</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
textInputRef={messageInputRef}
onChangeText={value => handleTopicChange(value)}
onFocus={handleTopicFocus}
onSubmitEditing={() => messageInputRef.current?.focus()}
blurOnSubmit={false}
returnKeyType="next"
style={styles.topicInput}
/>
<TopicAutocomplete
isFocused={topicFocus}
narrow={props.route.params.messageNarrow}
text={subject}
onAutocomplete={handleTopicAutocomplete}
/>
</View>
<Text style={{ fontSize: 14, color: 'gray', marginBottom: 10 }}>Move options:</Text>
<View style={styles.picker}>
<Picker
selectedValue={propagate_mode}
onValueChange={(itemValue, itemIndex) => setPropagateMode(itemValue.toString())}
style={styles.textColor}
>
<Picker.Item label="Change only this message" value="change_one" key="change_one" />
<Picker.Item
label="Change later messages to this topic"
value="change_later"
key="change_later"
/>
<Picker.Item
label="Change previous and following messages to this topic"
value="change_all"
key="change_all"
/>
</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
13 changes: 12 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,17 @@ export const navigateToMessageReactionScreen = (
reactionName?: string,
): GenericNavigationAction => StackActions.push('message-reactions', { messageId, reactionName });

export const navigateToMoveMessage = (
message: Message | Outbox,
messageNarrow: Narrow,
isadmin: boolean,
): GenericNavigationAction =>
StackActions.push('move-message', {
message,
messageNarrow,
isadmin,
});

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 139c454

Please sign in to comment.