From 896240457f608870974b4c449dfb70620623f1f2 Mon Sep 17 00:00:00 2001
From: Diego Mello <diegolmello@gmail.com>
Date: Tue, 21 May 2019 09:12:15 -0300
Subject: [PATCH] [IMPROVEMENT] Add toggle markdown to settings (#907)

* Add toggle markdown to settings

* Remove unused translation
---
 app/actions/actionsTypes.js        |  4 +-
 app/actions/markdown.js            |  8 ++++
 app/containers/message/Markdown.js |  2 +-
 app/i18n/locales/de.js             |  1 -
 app/i18n/locales/en.js             |  2 +-
 app/i18n/locales/fr.js             |  1 -
 app/i18n/locales/pt-BR.js          |  1 +
 app/i18n/locales/pt-PT.js          |  1 -
 app/i18n/locales/ru.js             |  1 -
 app/i18n/locales/zh-CN.js          |  1 -
 app/lib/rocketchat.js              |  8 ++++
 app/reducers/index.js              |  4 +-
 app/reducers/markdown.js           | 17 ++++++++
 app/sagas/init.js                  |  4 ++
 app/views/RoomView/index.js        |  7 ++--
 app/views/RoomsListView/index.js   | 19 ++-------
 app/views/SettingsView/index.js    | 66 +++++++++++++++++++++++++++---
 17 files changed, 112 insertions(+), 35 deletions(-)
 create mode 100644 app/actions/markdown.js
 create mode 100644 app/reducers/markdown.js

diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js
index 3a0d3bbd4..29545dc22 100644
--- a/app/actions/actionsTypes.js
+++ b/app/actions/actionsTypes.js
@@ -12,8 +12,7 @@ function createRequestTypes(base, types = defaultTypes) {
 export const LOGIN = createRequestTypes('LOGIN', [
 	...defaultTypes,
 	'SET_SERVICES',
-	'SET_PREFERENCE',
-	'SET_SORT_PREFERENCE'
+	'SET_PREFERENCE'
 ]);
 export const USER = createRequestTypes('USER', ['SET']);
 export const ROOMS = createRequestTypes('ROOMS', [
@@ -67,3 +66,4 @@ export const LOGOUT = 'LOGOUT'; // logout is always success
 export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
 export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
 export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
+export const TOGGLE_MARKDOWN = 'TOGGLE_MARKDOWN';
diff --git a/app/actions/markdown.js b/app/actions/markdown.js
new file mode 100644
index 000000000..d33075a38
--- /dev/null
+++ b/app/actions/markdown.js
@@ -0,0 +1,8 @@
+import * as types from './actionsTypes';
+
+export function toggleMarkdown(value) {
+	return {
+		type: types.TOGGLE_MARKDOWN,
+		payload: value
+	};
+}
diff --git a/app/containers/message/Markdown.js b/app/containers/message/Markdown.js
index 9cdd6804e..ae8cc77af 100644
--- a/app/containers/message/Markdown.js
+++ b/app/containers/message/Markdown.js
@@ -36,7 +36,7 @@ const Markdown = React.memo(({
 	}
 
 	if (!useMarkdown) {
-		return <Text style={styles.text}>{m}</Text>;
+		return <Text style={styles.text} numberOfLines={numberOfLines}>{m}</Text>;
 	}
 
 	return (
diff --git a/app/i18n/locales/de.js b/app/i18n/locales/de.js
index 0e90bcb53..e0b0c6f6e 100644
--- a/app/i18n/locales/de.js
+++ b/app/i18n/locales/de.js
@@ -308,7 +308,6 @@ export default {
 	This_room_is_blocked: 'Dieser Raum ist gesperrt',
 	This_room_is_read_only: 'Dieser Raum kann nur gelesen werden',
 	Timezone: 'Zeitzone',
-	Toggle_Drawer: 'Toggle_Drawer',
 	topic: 'Thema',
 	Topic: 'Thema',
 	Try_again: 'Versuchen Sie es nochmal',
diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js
index f6bd6d801..62b4f117a 100644
--- a/app/i18n/locales/en.js
+++ b/app/i18n/locales/en.js
@@ -153,6 +153,7 @@ export default {
 	Email_or_password_field_is_empty: 'Email or password field is empty',
 	Email: 'Email',
 	email: 'e-mail',
+	Enable_markdown: 'Enable markdown',
 	Enable_notifications: 'Enable notifications',
 	Everyone_can_access_this_channel: 'Everyone can access this channel',
 	erasing_room: 'erasing room',
@@ -328,7 +329,6 @@ export default {
 	Thread: 'Thread',
 	Threads: 'Threads',
 	Timezone: 'Timezone',
-	Toggle_Drawer: 'Toggle_Drawer',
 	topic: 'topic',
 	Topic: 'Topic',
 	Try_again: 'Try again',
diff --git a/app/i18n/locales/fr.js b/app/i18n/locales/fr.js
index bb9ff4e04..cf5129e67 100644
--- a/app/i18n/locales/fr.js
+++ b/app/i18n/locales/fr.js
@@ -309,7 +309,6 @@ export default {
 	This_room_is_blocked: 'Cette canal est bloquée',
 	This_room_is_read_only: 'Cette canal est en lecture seule',
 	Timezone: 'Fuseau horaire',
-	Toggle_Drawer: 'Toggle_Drawer',
 	topic: 'sujet',
 	Topic: 'Sujet',
 	Try_again: 'Réessayer',
diff --git a/app/i18n/locales/pt-BR.js b/app/i18n/locales/pt-BR.js
index 53ed7270d..859b4f299 100644
--- a/app/i18n/locales/pt-BR.js
+++ b/app/i18n/locales/pt-BR.js
@@ -160,6 +160,7 @@ export default {
 	Email_or_password_field_is_empty: 'Email ou senha estão vazios',
 	Email: 'Email',
 	email: 'e-mail',
+	Enable_markdown: 'Habilitar markdown',
 	Enable_notifications: 'Habilitar notificações',
 	Everyone_can_access_this_channel: 'Todos podem acessar este canal',
 	Error_uploading: 'Erro subindo',
diff --git a/app/i18n/locales/pt-PT.js b/app/i18n/locales/pt-PT.js
index f16bc95ac..c81675694 100644
--- a/app/i18n/locales/pt-PT.js
+++ b/app/i18n/locales/pt-PT.js
@@ -311,7 +311,6 @@ export default {
 	This_room_is_blocked: 'Esta sala está bloqueada',
 	This_room_is_read_only: 'Esta sala é apenas de leitura',
 	Timezone: 'Fuso Horário',
-	Toggle_Drawer: 'Toggle_Drawer',
 	topic: 'tópico',
 	Topic: 'Tópico',
 	Try_again: 'Tente novamente',
diff --git a/app/i18n/locales/ru.js b/app/i18n/locales/ru.js
index 32ca154cd..780706f9a 100644
--- a/app/i18n/locales/ru.js
+++ b/app/i18n/locales/ru.js
@@ -270,7 +270,6 @@ export default {
 	This_room_is_blocked: 'Этот канал заблокирован',
 	This_room_is_read_only: 'Этот канал доступен только для чтения',
 	Timezone: 'Часовой пояс',
-	Toggle_Drawer: 'Toggle_Drawer',
 	topic: 'топик',
 	Topic: 'Топик',
 	Try_again: 'Попробуйте еще раз',
diff --git a/app/i18n/locales/zh-CN.js b/app/i18n/locales/zh-CN.js
index 4748d8202..56c18f1bc 100644
--- a/app/i18n/locales/zh-CN.js
+++ b/app/i18n/locales/zh-CN.js
@@ -305,7 +305,6 @@ export default {
 	This_room_is_blocked: '这个房间被锁了',
 	This_room_is_read_only: '这个房间是只读的',
 	Timezone: '时区',
-	Toggle_Drawer: 'Toggle_Drawer',
 	topic: '主题',
 	Topic: '主题',
 	Try_again: '再试一次',
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index b0a118dc0..e83c3b5ef 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -40,6 +40,7 @@ import { roomsRequest } from '../actions/rooms';
 
 const TOKEN_KEY = 'reactnativemeteor_usertoken';
 const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
+export const MARKDOWN_KEY = 'RC_MARKDOWN_KEY';
 const returnAnArray = obj => obj || [];
 const MIN_ROCKETCHAT_VERSION = '0.70.0';
 
@@ -685,6 +686,13 @@ const RocketChat = {
 		// RC 0.51.0
 		return this.sdk.methodCall('setAvatarFromService', data, contentType, service);
 	},
+	async getUseMarkdown() {
+		const useMarkdown = await AsyncStorage.getItem(MARKDOWN_KEY);
+		if (useMarkdown === null) {
+			return true;
+		}
+		return JSON.parse(useMarkdown);
+	},
 	async getSortPreferences() {
 		const prefs = await AsyncStorage.getItem(SORT_PREFS_KEY);
 		return JSON.parse(prefs);
diff --git a/app/reducers/index.js b/app/reducers/index.js
index 9d2b023cb..d33c26abc 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -9,6 +9,7 @@ import selectedUsers from './selectedUsers';
 import createChannel from './createChannel';
 import app from './app';
 import sortPreferences from './sortPreferences';
+import markdown from './markdown';
 
 export default combineReducers({
 	settings,
@@ -20,5 +21,6 @@ export default combineReducers({
 	createChannel,
 	app,
 	rooms,
-	sortPreferences
+	sortPreferences,
+	markdown
 });
diff --git a/app/reducers/markdown.js b/app/reducers/markdown.js
new file mode 100644
index 000000000..aa3fb49f4
--- /dev/null
+++ b/app/reducers/markdown.js
@@ -0,0 +1,17 @@
+import { TOGGLE_MARKDOWN } from '../actions/actionsTypes';
+
+const initialState = {
+	useMarkdown: true
+};
+
+
+export default (state = initialState, action) => {
+	switch (action.type) {
+		case TOGGLE_MARKDOWN:
+			return {
+				useMarkdown: action.payload
+			};
+		default:
+			return state;
+	}
+};
diff --git a/app/sagas/init.js b/app/sagas/init.js
index 1b458e6e5..cf15cbc2a 100644
--- a/app/sagas/init.js
+++ b/app/sagas/init.js
@@ -5,6 +5,7 @@ import SplashScreen from 'react-native-splash-screen';
 import * as actions from '../actions';
 import { selectServerRequest } from '../actions/server';
 import { setAllPreferences } from '../actions/sortPreferences';
+import { toggleMarkdown } from '../actions/markdown';
 import { APP } from '../actions/actionsTypes';
 import RocketChat from '../lib/rocketchat';
 import log from '../utils/log';
@@ -21,6 +22,9 @@ const restore = function* restore() {
 		const sortPreferences = yield RocketChat.getSortPreferences();
 		yield put(setAllPreferences(sortPreferences));
 
+		const useMarkdown = yield RocketChat.getUseMarkdown();
+		yield put(toggleMarkdown(useMarkdown));
+
 		if (!token || !server) {
 			yield all([
 				AsyncStorage.removeItem(RocketChat.TOKEN_KEY),
diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js
index 888615e91..df829b89e 100644
--- a/app/views/RoomView/index.js
+++ b/app/views/RoomView/index.js
@@ -60,6 +60,7 @@ import { Toast } from '../../utils/info';
 	isAuthenticated: state.login.isAuthenticated,
 	Message_GroupingPeriod: state.settings.Message_GroupingPeriod,
 	Message_TimeFormat: state.settings.Message_TimeFormat,
+	useMarkdown: state.markdown.useMarkdown,
 	baseUrl: state.settings.baseUrl || state.server ? state.server.server : ''
 }), dispatch => ({
 	editCancel: () => dispatch(editCancelAction()),
@@ -120,6 +121,7 @@ export default class RoomView extends LoggedView {
 		editing: PropTypes.bool,
 		replying: PropTypes.bool,
 		baseUrl: PropTypes.string,
+		useMarkdown: PropTypes.bool,
 		toggleReactionPicker: PropTypes.func,
 		actionsShow: PropTypes.func,
 		editCancel: PropTypes.func,
@@ -135,7 +137,6 @@ export default class RoomView extends LoggedView {
 		this.rid = props.navigation.getParam('rid');
 		this.t = props.navigation.getParam('t');
 		this.tmid = props.navigation.getParam('tmid');
-		this.useMarkdown = props.navigation.getParam('useMarkdown', true);
 		this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
 		this.state = {
 			joined: this.rooms.length > 0,
@@ -504,7 +505,7 @@ export default class RoomView extends LoggedView {
 	renderItem = (item, previousItem) => {
 		const { room, lastOpen } = this.state;
 		const {
-			user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl
+			user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, useMarkdown
 		} = this.props;
 		let dateSeparator = null;
 		let showUnreadSeparator = false;
@@ -545,7 +546,7 @@ export default class RoomView extends LoggedView {
 				Message_GroupingPeriod={Message_GroupingPeriod}
 				timeFormat={Message_TimeFormat}
 				useRealName={useRealName}
-				useMarkdown={this.useMarkdown}
+				useMarkdown={useMarkdown}
 			/>
 		);
 
diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js
index 49e48bddf..072cda7cd 100644
--- a/app/views/RoomsListView/index.js
+++ b/app/views/RoomsListView/index.js
@@ -66,7 +66,6 @@ export default class RoomsListView extends LoggedView {
 		const cancelSearchingAndroid = navigation.getParam('cancelSearchingAndroid');
 		const onPressItem = navigation.getParam('onPressItem', () => {});
 		const initSearchingAndroid = navigation.getParam('initSearchingAndroid', () => {});
-		const toggleUseMarkdown = navigation.getParam('toggleUseMarkdown', () => {});
 
 		return {
 			headerLeft: (
@@ -76,7 +75,7 @@ export default class RoomsListView extends LoggedView {
 							<Item title='cancel' iconName='cross' onPress={cancelSearchingAndroid} />
 						</CustomHeaderButtons>
 					)
-					: <DrawerButton navigation={navigation} testID='rooms-list-view-sidebar' onLongPress={toggleUseMarkdown} />
+					: <DrawerButton navigation={navigation} testID='rooms-list-view-sidebar' />
 			),
 			headerTitle: <RoomsListHeaderView />,
 			headerRight: (
@@ -125,7 +124,6 @@ export default class RoomsListView extends LoggedView {
 			searching: false,
 			search: [],
 			loading: true,
-			useMarkdown: true,
 			chats: [],
 			unread: [],
 			favorites: [],
@@ -146,8 +144,7 @@ export default class RoomsListView extends LoggedView {
 		navigation.setParams({
 			onPressItem: this._onPressItem,
 			initSearchingAndroid: this.initSearchingAndroid,
-			cancelSearchingAndroid: this.cancelSearchingAndroid,
-			toggleUseMarkdown: this.toggleUseMarkdown
+			cancelSearchingAndroid: this.cancelSearchingAndroid
 		});
 		console.timeEnd(`${ this.constructor.name } mount`);
 	}
@@ -316,15 +313,6 @@ export default class RoomsListView extends LoggedView {
 		}
 	}
 
-	// Just for tests purposes
-	toggleUseMarkdown = () => {
-		this.setState(({ useMarkdown }) => ({ useMarkdown: !useMarkdown }),
-			() => {
-				const { useMarkdown } = this.state;
-				alert(`Markdown ${ useMarkdown ? 'enabled' : 'disabled' }`);
-			});
-	}
-
 	// this is necessary during development (enables Cmd + r)
 	hasActiveDB = () => database && database.databases && database.databases.activeDB;
 
@@ -355,10 +343,9 @@ export default class RoomsListView extends LoggedView {
 
 	goRoom = (item) => {
 		this.cancelSearchingAndroid();
-		const { useMarkdown } = this.state;
 		const { navigation } = this.props;
 		navigation.navigate('RoomView', {
-			rid: item.rid, name: this.getRoomTitle(item), t: item.t, prid: item.prid, useMarkdown
+			rid: item.rid, name: this.getRoomTitle(item), t: item.t, prid: item.prid
 		});
 	}
 
diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js
index 50a073668..1c0c84fe9 100644
--- a/app/views/SettingsView/index.js
+++ b/app/views/SettingsView/index.js
@@ -1,12 +1,15 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { View, ScrollView } from 'react-native';
+import {
+	View, ScrollView, Switch, Text, StyleSheet, AsyncStorage
+} from 'react-native';
 import RNPickerSelect from 'react-native-picker-select';
 import { connect } from 'react-redux';
 import { SafeAreaView } from 'react-navigation';
+import { Answers } from 'react-native-fabric';
 
 import LoggedView from '../View';
-import RocketChat from '../../lib/rocketchat';
+import RocketChat, { MARKDOWN_KEY } from '../../lib/rocketchat';
 import KeyboardView from '../../presentation/KeyboardView';
 import sharedStyles from '../Styles';
 import RCTextInput from '../../containers/TextInput';
@@ -17,13 +20,41 @@ import Loading from '../../containers/Loading';
 import { showErrorAlert, Toast } from '../../utils/info';
 import log from '../../utils/log';
 import { setUser as setUserAction } from '../../actions/login';
+import { toggleMarkdown as toggleMarkdownAction } from '../../actions/markdown';
 import { DrawerButton } from '../../containers/HeaderButton';
 import StatusBar from '../../containers/StatusBar';
+import { isAndroid } from '../../utils/deviceInfo';
+import {
+	COLOR_WHITE, COLOR_SEPARATOR, COLOR_DANGER, COLOR_SUCCESS
+} from '../../constants/colors';
+
+const styles = StyleSheet.create({
+	swithContainer: {
+		backgroundColor: COLOR_WHITE,
+		alignItems: 'center',
+		justifyContent: 'space-between',
+		flexDirection: 'row'
+	},
+	label: {
+		fontSize: 17,
+		flex: 1,
+		...sharedStyles.textMedium,
+		...sharedStyles.textColorNormal
+	},
+	separator: {
+		flex: 1,
+		height: 1,
+		backgroundColor: COLOR_SEPARATOR,
+		marginVertical: 10
+	}
+});
 
 @connect(state => ({
-	userLanguage: state.login.user && state.login.user.language
+	userLanguage: state.login.user && state.login.user.language,
+	useMarkdown: state.markdown.useMarkdown
 }), dispatch => ({
-	setUser: params => dispatch(setUserAction(params))
+	setUser: params => dispatch(setUserAction(params)),
+	toggleMarkdown: params => dispatch(toggleMarkdownAction(params))
 }))
 /** @extends React.Component */
 export default class SettingsView extends LoggedView {
@@ -35,7 +66,9 @@ export default class SettingsView extends LoggedView {
 	static propTypes = {
 		componentId: PropTypes.string,
 		userLanguage: PropTypes.string,
-		setUser: PropTypes.func
+		useMarkdown: PropTypes.bool,
+		setUser: PropTypes.func,
+		toggleMarkdown: PropTypes.func
 	}
 
 	constructor(props) {
@@ -71,13 +104,16 @@ export default class SettingsView extends LoggedView {
 
 	shouldComponentUpdate(nextProps, nextState) {
 		const { language, saving } = this.state;
-		const { userLanguage } = this.props;
+		const { userLanguage, useMarkdown } = this.props;
 		if (nextState.language !== language) {
 			return true;
 		}
 		if (nextState.saving !== saving) {
 			return true;
 		}
+		if (nextProps.useMarkdown !== useMarkdown) {
+			return true;
+		}
 		if (nextProps.userLanguage !== userLanguage) {
 			return true;
 		}
@@ -133,10 +169,18 @@ export default class SettingsView extends LoggedView {
 		}
 	}
 
+	toggleMarkdown = (value) => {
+		AsyncStorage.setItem(MARKDOWN_KEY, JSON.stringify(value));
+		const { toggleMarkdown } = this.props;
+		toggleMarkdown(value);
+		Answers.logCustom('toggle_markdown', { value });
+	}
+
 	render() {
 		const {
 			language, languages, placeholder, saving
 		} = this.state;
+		const { useMarkdown } = this.props;
 		return (
 			<KeyboardView
 				contentContainerStyle={sharedStyles.container}
@@ -174,6 +218,16 @@ export default class SettingsView extends LoggedView {
 								testID='settings-view-button'
 							/>
 						</View>
+						<View style={styles.separator} />
+						<View style={styles.swithContainer}>
+							<Text style={styles.label}>{I18n.t('Enable_markdown')}</Text>
+							<Switch
+								value={useMarkdown}
+								onValueChange={this.toggleMarkdown}
+								onTintColor={COLOR_SUCCESS}
+								tintColor={isAndroid ? COLOR_DANGER : null}
+							/>
+						</View>
 						<Loading visible={saving} />
 						<Toast ref={toast => this.toast = toast} />
 					</SafeAreaView>
-- 
GitLab