[NEW] Themes (#1298)

This commit is contained in:
Diego Mello 2019-12-04 13:39:53 -03:00 committed by GitHub
parent ea296d1007
commit 7b51df377d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
234 changed files with 17683 additions and 13308 deletions

View File

@ -2,7 +2,7 @@ module.exports = {
"settings": { "settings": {
"import/resolver": { "import/resolver": {
"node": { "node": {
"extensions": [".js", ".ios.js", ".android.js", ".native.js"] "extensions": [".js", ".ios.js", ".android.js", ".native.js", ".tsx"]
} }
} }
}, },

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@ -1,32 +1,121 @@
import { isIOS, isAndroid } from '../utils/deviceInfo'; import { isIOS, isAndroid } from '../utils/deviceInfo';
export const COLOR_DANGER = '#f5455c';
export const COLOR_SUCCESS = '#2de0a5';
export const COLOR_PRIMARY = '#1d74f5';
export const COLOR_WHITE = '#fff';
export const COLOR_BUTTON_PRIMARY = COLOR_PRIMARY;
export const COLOR_TITLE = '#0C0D0F';
export const COLOR_TEXT = '#2F343D';
export const COLOR_TEXT_DESCRIPTION = '#9ca2a8';
export const COLOR_SEPARATOR = '#A7A7AA';
export const COLOR_BACKGROUND_CONTAINER = '#f3f4f5';
export const COLOR_BACKGROUND_NOTIFICATION = '#f8f8f8';
export const COLOR_BORDER = '#e1e5e8';
export const COLOR_UNREAD = '#e1e5e8';
export const COLOR_TOAST = '#0C0D0F';
export const STATUS_COLORS = { export const STATUS_COLORS = {
online: '#2de0a5', online: '#2de0a5',
busy: COLOR_DANGER, busy: '#f5455c',
away: '#ffd21f', away: '#ffd21f',
offline: '#cbced1' offline: '#cbced1'
}; };
export const HEADER_BACKGROUND = isIOS ? '#f8f8f8' : '#2F343D';
export const HEADER_TITLE = isIOS ? COLOR_TITLE : COLOR_WHITE;
export const HEADER_BACK = isIOS ? COLOR_PRIMARY : COLOR_WHITE;
export const HEADER_TINT = isIOS ? COLOR_PRIMARY : COLOR_WHITE;
export const SWITCH_TRACK_COLOR = { export const SWITCH_TRACK_COLOR = {
false: isAndroid ? COLOR_DANGER : null, false: isAndroid ? '#f5455c' : null,
true: COLOR_SUCCESS true: '#2de0a5'
};
export const themes = {
light: {
backgroundColor: '#ffffff',
focusedBackground: '#ffffff',
chatComponentBackground: '#f3f4f5',
auxiliaryBackground: '#efeff4',
bannerBackground: '#f1f2f4',
titleText: '#0d0e12',
bodyText: '#2f343d',
backdropColor: '#000000',
dangerColor: '#f5455c',
successColor: '#2de0a5',
borderColor: '#e1e5e8',
controlText: '#54585e',
auxiliaryText: '#9ca2a8',
infoText: '#6d6d72',
tintColor: '#1d74f5',
auxiliaryTintColor: '#caced1',
actionTintColor: '#1d74f5',
separatorColor: '#cbcbcc',
navbarBackground: '#ffffff',
headerBorder: '#B2B2B2',
headerBackground: isIOS ? '#f8f8f8' : '#2f343d',
headerSecondaryBackground: '#ffffff',
headerTintColor: isAndroid ? '#ffffff' : '#1d74f5',
headerTitleColor: isAndroid ? '#ffffff' : '#0d0e12',
headerSecondaryText: isAndroid ? '#9ca2a8' : '#1d74f5',
toastBackground: '#0C0D0F',
videoBackground: '#1f2329',
favoriteBackground: '#ffbb00',
hideBackground: '#54585e',
messageboxBackground: '#ffffff',
searchboxBackground: '#E6E6E7',
buttonBackground: '#414852',
buttonText: '#ffffff'
},
dark: {
backgroundColor: '#030b1b',
focusedBackground: '#0b182c',
chatComponentBackground: '#192132',
auxiliaryBackground: '#07101e',
bannerBackground: '#0e1f38',
titleText: '#FFFFFF',
bodyText: '#e8ebed',
backdropColor: '#000000',
dangerColor: '#f5455c',
successColor: '#2de0a5',
borderColor: '#0f213d',
controlText: '#dadde6',
auxiliaryText: '#9297a2',
infoText: '#6D6D72',
tintColor: '#1d74f5',
auxiliaryTintColor: '#cdcdcd',
actionTintColor: '#1d74f5',
separatorColor: '#2b2b2d',
navbarBackground: '#0b182c',
headerBorder: '#2F3A4B',
headerBackground: '#0b182c',
headerSecondaryBackground: '#0b182c',
headerTintColor: isAndroid ? '#ffffff' : '#1d74f5',
headerTitleColor: '#FFFFFF',
headerSecondaryText: isAndroid ? '#9297a2' : '#1d74f5',
toastBackground: '#0C0D0F',
videoBackground: '#1f2329',
favoriteBackground: '#ffbb00',
hideBackground: '#54585e',
messageboxBackground: '#0b182c',
searchboxBackground: '#192d4d',
buttonBackground: '#414852',
buttonText: '#ffffff'
},
black: {
backgroundColor: '#000000',
focusedBackground: '#0d0d0d',
chatComponentBackground: '#16181a',
auxiliaryBackground: '#080808',
bannerBackground: '#1f2329',
titleText: '#f9f9f9',
bodyText: '#e8ebed',
backdropColor: '#000000',
dangerColor: '#f5455c',
successColor: '#2de0a5',
borderColor: '#1f2329',
controlText: '#dadde6',
auxiliaryText: '#b2b8c6',
infoText: '#6d6d72',
tintColor: '#1e9bfe',
auxiliaryTintColor: '#cdcdcd',
actionTintColor: '#1ea1fe',
separatorColor: '#272728',
navbarBackground: '#0d0d0d',
headerBorder: '#323232',
headerBackground: '#0d0d0d',
headerSecondaryBackground: '#0d0d0d',
headerTintColor: isAndroid ? '#ffffff' : '#1e9bfe',
headerTitleColor: '#f9f9f9',
headerSecondaryText: isAndroid ? '#b2b8c6' : '#1e9bfe',
toastBackground: '#0C0D0F',
videoBackground: '#1f2329',
favoriteBackground: '#ffbb00',
hideBackground: '#54585e',
messageboxBackground: '#0d0d0d',
searchboxBackground: '#1f1f1f',
buttonBackground: '#414852',
buttonText: '#ffffff'
}
}; };

View File

@ -1,12 +1,40 @@
import React from 'react'; import React from 'react';
import { ActivityIndicator, StyleSheet } from 'react-native'; import { ActivityIndicator, StyleSheet } from 'react-native';
import { PropTypes } from 'prop-types';
import { themes } from '../constants/colors';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
indicator: { indicator: {
padding: 10 padding: 16,
flex: 1
},
absolute: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
} }
}); });
const RCActivityIndicator = () => <ActivityIndicator style={styles.indicator} />; const RCActivityIndicator = ({ theme, absolute, ...props }) => (
<ActivityIndicator
style={[styles.indicator, absolute && styles.absolute]}
color={themes[theme].auxiliaryText}
{...props}
/>
);
RCActivityIndicator.propTypes = {
theme: PropTypes.string,
absolute: PropTypes.bool,
props: PropTypes.object
};
RCActivityIndicator.defaultProps = {
theme: 'light'
};
export default RCActivityIndicator; export default RCActivityIndicator;

View File

@ -9,7 +9,7 @@ const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
); );
const Avatar = React.memo(({ const Avatar = React.memo(({
text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress, theme
}) => { }) => {
const avatarStyle = { const avatarStyle = {
width: size, width: size,
@ -52,7 +52,7 @@ const Avatar = React.memo(({
if (onPress) { if (onPress) {
image = ( image = (
<Touch onPress={onPress}> <Touch onPress={onPress} theme={theme}>
{image} {image}
</Touch> </Touch>
); );
@ -77,6 +77,7 @@ Avatar.propTypes = {
children: PropTypes.object, children: PropTypes.object,
userId: PropTypes.string, userId: PropTypes.string,
token: PropTypes.string, token: PropTypes.string,
theme: PropTypes.string,
onPress: PropTypes.func onPress: PropTypes.func
}; };

View File

@ -1,18 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { StyleSheet, Text, ActivityIndicator } from 'react-native'; import { StyleSheet, Text } from 'react-native';
import { RectButton } from 'react-native-gesture-handler'; import { RectButton } from 'react-native-gesture-handler';
import { COLOR_BUTTON_PRIMARY } from '../../constants/colors'; import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import ActivityIndicator from '../ActivityIndicator';
const colors = {
background_primary: COLOR_BUTTON_PRIMARY,
background_secondary: 'white',
text_color_primary: 'white',
text_color_secondary: COLOR_BUTTON_PRIMARY
};
/* eslint-disable react-native/no-unused-styles */ /* eslint-disable react-native/no-unused-styles */
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -26,23 +19,6 @@ const styles = StyleSheet.create({
text: { text: {
fontSize: 18, fontSize: 18,
textAlign: 'center' textAlign: 'center'
},
background_primary: {
backgroundColor: colors.background_primary
},
background_secondary: {
backgroundColor: colors.background_secondary
},
text_primary: {
...sharedStyles.textMedium,
color: colors.text_color_primary
},
text_secondary: {
...sharedStyles.textBold,
color: colors.text_color_secondary
},
disabled: {
backgroundColor: '#e1e5e8'
} }
}); });
@ -54,6 +30,7 @@ export default class Button extends React.PureComponent {
disabled: PropTypes.bool, disabled: PropTypes.bool,
backgroundColor: PropTypes.string, backgroundColor: PropTypes.string,
loading: PropTypes.bool, loading: PropTypes.bool,
theme: PropTypes.string,
style: PropTypes.any style: PropTypes.any
} }
@ -67,24 +44,37 @@ export default class Button extends React.PureComponent {
render() { render() {
const { const {
title, type, onPress, disabled, backgroundColor, loading, style, ...otherProps title, type, onPress, disabled, backgroundColor, loading, style, theme, ...otherProps
} = this.props; } = this.props;
const isPrimary = type === 'primary';
return ( return (
<RectButton <RectButton
onPress={onPress} onPress={onPress}
enabled={!(disabled || loading)} enabled={!(disabled || loading)}
style={[ style={[
styles.container, styles.container,
backgroundColor ? { backgroundColor } : styles[`background_${ type }`], backgroundColor
disabled && styles.disabled, ? { backgroundColor }
: { backgroundColor: isPrimary ? themes[theme].actionTintColor : themes[theme].backgroundColor },
disabled && { backgroundColor: themes[theme].borderColor },
style style
]} ]}
{...otherProps} {...otherProps}
> >
{ {
loading loading
? <ActivityIndicator color={colors[`text_color_${ type }`]} /> ? <ActivityIndicator color={isPrimary ? themes[theme].buttonText : themes[theme].actionTintColor} />
: <Text style={[styles.text, styles[`text_${ type }`]]}>{title}</Text> : (
<Text
style={[
styles.text,
isPrimary ? sharedStyles.textMedium : sharedStyles.textBold,
{ color: isPrimary ? themes[theme].buttonText : themes[theme].actionTintColor }
]}
>
{title}
</Text>
)
} }
</RectButton> </RectButton>
); );

View File

@ -1,18 +1,22 @@
import React from 'react'; import React from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles'; import { themes } from '../constants/colors';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
icon: { icon: {
width: 22, width: 22,
height: 22, height: 22,
marginHorizontal: 15, marginHorizontal: 15
...sharedStyles.textColorDescription
} }
}); });
const Check = React.memo(() => <CustomIcon style={styles.icon} size={22} name='check' />); const Check = React.memo(({ theme }) => <CustomIcon style={styles.icon} color={themes[theme].tintColor} size={22} name='check' />);
Check.propTypes = {
theme: PropTypes.string
};
export default Check; export default Check;

View File

@ -1,5 +1,8 @@
import React from 'react'; import React from 'react';
import { View, Image, StyleSheet } from 'react-native'; import { View, Image, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../constants/colors';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
disclosureContainer: { disclosureContainer: {
@ -14,12 +17,23 @@ const styles = StyleSheet.create({
} }
}); });
export const DisclosureImage = React.memo(() => <Image source={{ uri: 'disclosure_indicator' }} style={styles.disclosureIndicator} />); export const DisclosureImage = React.memo(({ theme }) => (
<Image
source={{ uri: 'disclosure_indicator' }}
style={[styles.disclosureIndicator, { tintColor: themes[theme].auxiliaryTintColor }]}
/>
));
DisclosureImage.propTypes = {
theme: PropTypes.string
};
const DisclosureIndicator = React.memo(() => ( const DisclosureIndicator = React.memo(({ theme }) => (
<View style={styles.disclosureContainer}> <View style={styles.disclosureContainer}>
<DisclosureImage /> <DisclosureImage theme={theme} />
</View> </View>
)); ));
DisclosureIndicator.propTypes = {
theme: PropTypes.string
};
export default DisclosureIndicator; export default DisclosureIndicator;

View File

@ -2,26 +2,31 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, TouchableOpacity, Text } from 'react-native'; import { View, TouchableOpacity, Text } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors';
export default class TabBar extends React.Component { export default class TabBar extends React.Component {
static propTypes = { static propTypes = {
goToPage: PropTypes.func, goToPage: PropTypes.func,
activeTab: PropTypes.number, activeTab: PropTypes.number,
tabs: PropTypes.array, tabs: PropTypes.array,
tabEmojiStyle: PropTypes.object tabEmojiStyle: PropTypes.object,
theme: PropTypes.string
} }
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
const { activeTab } = this.props; const { activeTab, theme } = this.props;
if (nextProps.activeTab !== activeTab) { if (nextProps.activeTab !== activeTab) {
return true; return true;
} }
if (nextProps.theme !== theme) {
return true;
}
return false; return false;
} }
render() { render() {
const { const {
tabs, goToPage, tabEmojiStyle, activeTab tabs, goToPage, tabEmojiStyle, activeTab, theme
} = this.props; } = this.props;
return ( return (
@ -35,7 +40,7 @@ export default class TabBar extends React.Component {
testID={`reaction-picker-${ tab }`} testID={`reaction-picker-${ tab }`}
> >
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text> <Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
{activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />} {activeTab === i ? <View style={[styles.activeTabLine, { backgroundColor: themes[theme].tintColor }]} /> : <View style={styles.tabLine} />}
</TouchableOpacity> </TouchableOpacity>
))} ))}
</View> </View>

View File

@ -16,6 +16,8 @@ import database from '../../lib/database';
import { emojisByCategory } from '../../emojis'; import { emojisByCategory } from '../../emojis';
import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import protectedFunction from '../../lib/methods/helpers/protectedFunction';
import log from '../../utils/log'; import log from '../../utils/log';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
const scrollProps = { const scrollProps = {
keyboardShouldPersistTaps: 'always', keyboardShouldPersistTaps: 'always',
@ -27,7 +29,8 @@ class EmojiPicker extends Component {
baseUrl: PropTypes.string.isRequired, baseUrl: PropTypes.string.isRequired,
customEmojis: PropTypes.object, customEmojis: PropTypes.object,
onEmojiSelected: PropTypes.func, onEmojiSelected: PropTypes.func,
tabEmojiStyle: PropTypes.object tabEmojiStyle: PropTypes.object,
theme: PropTypes.string
}; };
constructor(props) { constructor(props) {
@ -54,6 +57,10 @@ class EmojiPicker extends Component {
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { frequentlyUsed, show, width } = this.state; const { frequentlyUsed, show, width } = this.state;
const { theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextState.show !== show) { if (nextState.show !== show) {
return true; return true;
} }
@ -152,7 +159,7 @@ class EmojiPicker extends Component {
render() { render() {
const { show, frequentlyUsed } = this.state; const { show, frequentlyUsed } = this.state;
const { tabEmojiStyle } = this.props; const { tabEmojiStyle, theme } = this.props;
if (!show) { if (!show) {
return null; return null;
@ -160,9 +167,9 @@ class EmojiPicker extends Component {
return ( return (
<View onLayout={this.onLayout} style={{ flex: 1 }}> <View onLayout={this.onLayout} style={{ flex: 1 }}>
<ScrollableTabView <ScrollableTabView
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} />} renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
contentProps={scrollProps} contentProps={scrollProps}
style={styles.background} style={{ backgroundColor: themes[theme].focusedBackground }}
> >
{ {
categories.tabs.map((tab, i) => ( categories.tabs.map((tab, i) => (
@ -181,4 +188,4 @@ const mapStateToProps = state => ({
customEmojis: state.customEmojis customEmojis: state.customEmojis
}); });
export default connect(mapStateToProps)(EmojiPicker); export default connect(mapStateToProps)(withTheme(EmojiPicker));

View File

@ -1,10 +1,6 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { COLOR_PRIMARY, COLOR_WHITE } from '../../constants/colors';
export default StyleSheet.create({ export default StyleSheet.create({
background: {
backgroundColor: COLOR_WHITE
},
container: { container: {
flex: 1 flex: 1
}, },
@ -28,7 +24,6 @@ export default StyleSheet.create({
left: 0, left: 0,
right: 0, right: 0,
height: 2, height: 2,
backgroundColor: COLOR_PRIMARY,
bottom: 0 bottom: 0
}, },
tabLine: { tabLine: {
@ -51,7 +46,6 @@ export default StyleSheet.create({
flex: 1 flex: 1
}, },
categoryEmoji: { categoryEmoji: {
color: 'black',
backgroundColor: 'transparent', backgroundColor: 'transparent',
textAlign: 'center' textAlign: 'center'
}, },

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { import {
View, Text, TouchableWithoutFeedback, ActivityIndicator, StyleSheet, SafeAreaView View, Text, TouchableWithoutFeedback, StyleSheet, SafeAreaView
} from 'react-native'; } from 'react-native';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -9,8 +9,10 @@ import ImageViewer from 'react-native-image-zoom-viewer';
import { Video } from 'expo-av'; import { Video } from 'expo-av';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { COLOR_WHITE } from '../constants/colors';
import { formatAttachmentUrl } from '../lib/utils'; import { formatAttachmentUrl } from '../lib/utils';
import ActivityIndicator from './ActivityIndicator';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
safeArea: { safeArea: {
@ -25,40 +27,22 @@ const styles = StyleSheet.create({
marginVertical: 10 marginVertical: 10
}, },
title: { title: {
color: COLOR_WHITE,
textAlign: 'center', textAlign: 'center',
fontSize: 16, fontSize: 16,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
description: { description: {
color: COLOR_WHITE,
textAlign: 'center', textAlign: 'center',
fontSize: 14, fontSize: 14,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
indicator: {
flex: 1
},
video: { video: {
flex: 1 flex: 1
},
loading: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
} }
}); });
const Indicator = React.memo(() => (
<ActivityIndicator style={styles.indicator} />
));
const ModalContent = React.memo(({ const ModalContent = React.memo(({
attachment, onClose, user, baseUrl attachment, onClose, user, baseUrl, theme
}) => { }) => {
if (attachment && attachment.image_url) { if (attachment && attachment.image_url) {
const url = formatAttachmentUrl(attachment.image_url, user.id, user.token, baseUrl); const url = formatAttachmentUrl(attachment.image_url, user.id, user.token, baseUrl);
@ -66,8 +50,8 @@ const ModalContent = React.memo(({
<SafeAreaView style={styles.safeArea}> <SafeAreaView style={styles.safeArea}>
<TouchableWithoutFeedback onPress={onClose}> <TouchableWithoutFeedback onPress={onClose}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<Text style={styles.title}>{attachment.title}</Text> <Text style={[styles.title, { color: themes[theme].buttonText }]}>{attachment.title}</Text>
{attachment.description ? <Text style={styles.description}>{attachment.description}</Text> : null} {attachment.description ? <Text style={[styles.description, { color: themes[theme].buttonText }]}>{attachment.description}</Text> : null}
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
<ImageViewer <ImageViewer
@ -78,7 +62,7 @@ const ModalContent = React.memo(({
onSwipeDown={onClose} onSwipeDown={onClose}
renderIndicator={() => null} renderIndicator={() => null}
renderImage={props => <FastImage {...props} />} renderImage={props => <FastImage {...props} />}
loadingRender={() => <Indicator />} loadingRender={() => <ActivityIndicator size='large' theme={theme} />}
/> />
</SafeAreaView> </SafeAreaView>
); );
@ -102,7 +86,7 @@ const ModalContent = React.memo(({
onLoadStart={() => setLoading(true)} onLoadStart={() => setLoading(true)}
onError={console.log} onError={console.log}
/> />
{ loading ? <ActivityIndicator size='large' style={styles.loading} /> : null } { loading ? <ActivityIndicator size='large' theme={theme} absolute /> : null }
</> </>
); );
} }
@ -110,7 +94,7 @@ const ModalContent = React.memo(({
}); });
const FileModal = React.memo(({ const FileModal = React.memo(({
isVisible, onClose, attachment, user, baseUrl isVisible, onClose, attachment, user, baseUrl, theme
}) => ( }) => (
<Modal <Modal
style={styles.modal} style={styles.modal}
@ -120,15 +104,18 @@ const FileModal = React.memo(({
onSwipeComplete={onClose} onSwipeComplete={onClose}
swipeDirection={['up', 'down']} swipeDirection={['up', 'down']}
> >
<ModalContent attachment={attachment} onClose={onClose} user={user} baseUrl={baseUrl} /> <ModalContent attachment={attachment} onClose={onClose} user={user} baseUrl={baseUrl} theme={theme} />
</Modal> </Modal>
), (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.loading === nextProps.loading); ), (prevProps, nextProps) => (
prevProps.isVisible === nextProps.isVisible && prevProps.loading === nextProps.loading && prevProps.theme === nextProps.theme
));
FileModal.propTypes = { FileModal.propTypes = {
isVisible: PropTypes.bool, isVisible: PropTypes.bool,
attachment: PropTypes.object, attachment: PropTypes.object,
user: PropTypes.object, user: PropTypes.object,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
theme: PropTypes.string,
onClose: PropTypes.func onClose: PropTypes.func
}; };
FileModal.displayName = 'FileModal'; FileModal.displayName = 'FileModal';
@ -137,8 +124,9 @@ ModalContent.propTypes = {
attachment: PropTypes.object, attachment: PropTypes.object,
user: PropTypes.object, user: PropTypes.object,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
theme: PropTypes.string,
onClose: PropTypes.func onClose: PropTypes.func
}; };
ModalContent.displayName = 'FileModalContent'; ModalContent.displayName = 'FileModalContent';
export default FileModal; export default withTheme(FileModal);

View File

@ -3,16 +3,25 @@ import PropTypes from 'prop-types';
import { HeaderButtons, HeaderButton, Item } from 'react-navigation-header-buttons'; import { HeaderButtons, HeaderButton, Item } from 'react-navigation-header-buttons';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import { isIOS } from '../utils/deviceInfo'; import { isIOS, isAndroid } from '../utils/deviceInfo';
import { COLOR_PRIMARY, COLOR_WHITE } from '../constants/colors'; import { themes } from '../constants/colors';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme';
const color = isIOS ? COLOR_PRIMARY : COLOR_WHITE;
export const headerIconSize = 23; export const headerIconSize = 23;
const CustomHeaderButton = React.memo(props => ( const CustomHeaderButton = React.memo(withTheme(({ theme, ...props }) => (
<HeaderButton {...props} IconComponent={CustomIcon} iconSize={headerIconSize} color={color} /> <HeaderButton
)); {...props}
IconComponent={CustomIcon}
iconSize={headerIconSize}
color={
isAndroid
? themes[theme].headerTitleColor
: themes[theme].headerTintColor
}
/>
)));
export const CustomHeaderButtons = React.memo(props => ( export const CustomHeaderButtons = React.memo(props => (
<HeaderButtons <HeaderButtons
@ -52,6 +61,9 @@ export const LegalButton = React.memo(({ navigation, testID }) => (
<MoreButton onPress={() => navigation.navigate('LegalView')} testID={testID} /> <MoreButton onPress={() => navigation.navigate('LegalView')} testID={testID} />
)); ));
CustomHeaderButton.propTypes = {
theme: PropTypes.string
};
DrawerButton.propTypes = { DrawerButton.propTypes = {
navigation: PropTypes.object.isRequired, navigation: PropTypes.object.isRequired,
testID: PropTypes.string.isRequired testID: PropTypes.string.isRequired

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { RectButton } from 'react-native-gesture-handler';
import { COLOR_TEXT } from '../constants/colors'; import Touch from '../utils/touch';
import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -12,7 +12,7 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
height: 56, height: 46,
paddingHorizontal: 15 paddingHorizontal: 15
}, },
disabled: { disabled: {
@ -24,24 +24,22 @@ const styles = StyleSheet.create({
}, },
title: { title: {
fontSize: 16, fontSize: 16,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
subtitle: { subtitle: {
fontSize: 14, fontSize: 14,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular ...sharedStyles.textRegular
} }
}); });
const Content = React.memo(({ const Content = React.memo(({
title, subtitle, disabled, testID, right, color title, subtitle, disabled, testID, right, color, theme
}) => ( }) => (
<View style={[styles.container, disabled && styles.disabled]} testID={testID}> <View style={[styles.container, disabled && styles.disabled]} testID={testID}>
<View style={styles.textContainer}> <View style={styles.textContainer}>
<Text style={[styles.title, color && { color }]}>{title}</Text> <Text style={[styles.title, { color: color || themes[theme].titleText }]}>{title}</Text>
{subtitle {subtitle
? <Text style={styles.subtitle}>{subtitle}</Text> ? <Text style={[styles.subtitle, { color: themes[theme].bodyText }]}>{subtitle}</Text>
: null : null
} }
</View> </View>
@ -52,25 +50,30 @@ const Content = React.memo(({
const Button = React.memo(({ const Button = React.memo(({
onPress, ...props onPress, ...props
}) => ( }) => (
<RectButton <Touch
onPress={onPress} onPress={onPress}
activeOpacity={0.1} style={{ backgroundColor: themes[props.theme].backgroundColor }}
underlayColor={COLOR_TEXT}
enabled={!props.disabled} enabled={!props.disabled}
theme={props.theme}
> >
<Content {...props} /> <Content {...props} />
</RectButton> </Touch>
)); ));
const Item = React.memo(({ ...props }) => { const Item = React.memo(({ ...props }) => {
if (props.onPress) { if (props.onPress) {
return <Button {...props} />; return <Button {...props} />;
} }
return <Content {...props} />; return (
<View style={{ backgroundColor: themes[props.theme].backgroundColor }}>
<Content {...props} />
</View>
);
}); });
Item.propTypes = { Item.propTypes = {
onPress: PropTypes.func onPress: PropTypes.func,
theme: PropTypes.string
}; };
Content.propTypes = { Content.propTypes = {
@ -78,13 +81,15 @@ Content.propTypes = {
subtitle: PropTypes.string, subtitle: PropTypes.string,
right: PropTypes.func, right: PropTypes.func,
disabled: PropTypes.bool, disabled: PropTypes.bool,
color: PropTypes.string, testID: PropTypes.string,
testID: PropTypes.string theme: PropTypes.string,
color: PropTypes.string
}; };
Button.propTypes = { Button.propTypes = {
onPress: PropTypes.func, onPress: PropTypes.func,
disabled: PropTypes.bool disabled: PropTypes.bool,
theme: PropTypes.string
}; };
Button.defaultProps = { Button.defaultProps = {

View File

@ -1,14 +1,15 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { TouchableOpacity, ActivityIndicator } from 'react-native'; import { TouchableOpacity } from 'react-native';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import styles from '../styles'; import styles from '../styles';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
import { COLOR_PRIMARY } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import MessageboxContext from '../Context'; import MessageboxContext from '../Context';
import ActivityIndicator from '../../ActivityIndicator';
const Item = ({ item }) => { const Item = ({ item, theme }) => {
const context = useContext(MessageboxContext); const context = useContext(MessageboxContext);
const { onPressCommandPreview } = context; const { onPressCommandPreview } = context;
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -28,17 +29,18 @@ const Item = ({ item }) => {
onLoadStart={() => setLoading(true)} onLoadStart={() => setLoading(true)}
onLoad={() => setLoading(false)} onLoad={() => setLoading(false)}
> >
{ loading ? <ActivityIndicator /> : null } { loading ? <ActivityIndicator theme={theme} /> : null }
</FastImage> </FastImage>
) )
: <CustomIcon name='file-generic' size={36} color={COLOR_PRIMARY} /> : <CustomIcon name='file-generic' size={36} color={themes[theme].actionTintColor} />
} }
</TouchableOpacity> </TouchableOpacity>
); );
}; };
Item.propTypes = { Item.propTypes = {
item: PropTypes.object item: PropTypes.object,
theme: PropTypes.string
}; };
export default Item; export default Item;

View File

@ -5,17 +5,19 @@ import equal from 'deep-equal';
import Item from './Item'; import Item from './Item';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../constants/colors';
import { withTheme } from '../../../theme';
const CommandsPreview = React.memo(({ commandPreview, showCommandPreview }) => { const CommandsPreview = React.memo(({ theme, commandPreview, showCommandPreview }) => {
if (!showCommandPreview) { if (!showCommandPreview) {
return null; return null;
} }
return ( return (
<FlatList <FlatList
testID='commandbox-container' testID='commandbox-container'
style={styles.mentionList} style={[styles.mentionList, { backgroundColor: themes[theme].messageboxBackground }]}
data={commandPreview} data={commandPreview}
renderItem={({ item }) => <Item item={item} />} renderItem={({ item }) => <Item item={item} theme={theme} />}
keyExtractor={item => item.id} keyExtractor={item => item.id}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
horizontal horizontal
@ -23,6 +25,9 @@ const CommandsPreview = React.memo(({ commandPreview, showCommandPreview }) => {
/> />
); );
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {
if (prevProps.theme !== nextProps.theme) {
return false;
}
if (prevProps.showCommandPreview !== nextProps.showCommandPreview) { if (prevProps.showCommandPreview !== nextProps.showCommandPreview) {
return false; return false;
} }
@ -34,7 +39,8 @@ const CommandsPreview = React.memo(({ commandPreview, showCommandPreview }) => {
CommandsPreview.propTypes = { CommandsPreview.propTypes = {
commandPreview: PropTypes.array, commandPreview: PropTypes.array,
showCommandPreview: PropTypes.bool showCommandPreview: PropTypes.bool,
theme: PropTypes.string
}; };
export default CommandsPreview; export default withTheme(CommandsPreview);

View File

@ -1,12 +1,19 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { KeyboardRegistry } from 'react-native-keyboard-input'; import { KeyboardRegistry } from 'react-native-keyboard-input';
import PropTypes from 'prop-types';
import store from '../../lib/createStore'; import store from '../../lib/createStore';
import EmojiPicker from '../EmojiPicker'; import EmojiPicker from '../EmojiPicker';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
export default class EmojiKeyboard extends React.PureComponent { export default class EmojiKeyboard extends React.PureComponent {
static propTypes = {
theme: PropTypes.string
};
constructor(props) { constructor(props) {
super(props); super(props);
const state = store.getState(); const state = store.getState();
@ -18,11 +25,12 @@ export default class EmojiKeyboard extends React.PureComponent {
} }
render() { render() {
const { theme } = this.props;
return ( return (
<View style={styles.emojiKeyboardContainer} testID='messagebox-keyboard-emoji'> <View style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]} testID='messagebox-keyboard-emoji'>
<EmojiPicker onEmojiSelected={this.onEmojiSelected} baseUrl={this.baseUrl} /> <EmojiPicker onEmojiSelected={this.onEmojiSelected} baseUrl={this.baseUrl} />
</View> </View>
); );
} }
} }
KeyboardRegistry.registerKeyboard('EmojiKeyboard', () => EmojiKeyboard); KeyboardRegistry.registerKeyboard('EmojiKeyboard', () => withTheme(EmojiKeyboard));

View File

@ -4,21 +4,23 @@ import PropTypes from 'prop-types';
import { CancelEditingButton, ToggleEmojiButton } from './buttons'; import { CancelEditingButton, ToggleEmojiButton } from './buttons';
const LeftButtons = React.memo(({ const LeftButtons = React.memo(({
showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji theme, showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji
}) => { }) => {
if (editing) { if (editing) {
return <CancelEditingButton onPress={editCancel} />; return <CancelEditingButton onPress={editCancel} theme={theme} />;
} }
return ( return (
<ToggleEmojiButton <ToggleEmojiButton
show={showEmojiKeyboard} show={showEmojiKeyboard}
open={openEmoji} open={openEmoji}
close={closeEmoji} close={closeEmoji}
theme={theme}
/> />
); );
}); });
LeftButtons.propTypes = { LeftButtons.propTypes = {
theme: PropTypes.string,
showEmojiKeyboard: PropTypes.bool, showEmojiKeyboard: PropTypes.bool,
openEmoji: PropTypes.func.isRequired, openEmoji: PropTypes.func.isRequired,
closeEmoji: PropTypes.func.isRequired, closeEmoji: PropTypes.func.isRequired,

View File

@ -4,15 +4,16 @@ import PropTypes from 'prop-types';
import { CancelEditingButton, FileButton } from './buttons'; import { CancelEditingButton, FileButton } from './buttons';
const LeftButtons = React.memo(({ const LeftButtons = React.memo(({
showFileActions, editing, editCancel theme, showFileActions, editing, editCancel
}) => { }) => {
if (editing) { if (editing) {
return <CancelEditingButton onPress={editCancel} />; return <CancelEditingButton onPress={editCancel} theme={theme} />;
} }
return <FileButton onPress={showFileActions} />; return <FileButton onPress={showFileActions} theme={theme} />;
}); });
LeftButtons.propTypes = { LeftButtons.propTypes = {
theme: PropTypes.string,
showFileActions: PropTypes.func.isRequired, showFileActions: PropTypes.func.isRequired,
editing: PropTypes.bool, editing: PropTypes.bool,
editCancel: PropTypes.func.isRequired editCancel: PropTypes.func.isRequired

View File

@ -4,20 +4,30 @@ import PropTypes from 'prop-types';
import styles from '../styles'; import styles from '../styles';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import { themes } from '../../../constants/colors';
const FixedMentionItem = ({ item, onPress }) => ( const FixedMentionItem = ({ item, onPress, theme }) => (
<TouchableOpacity <TouchableOpacity
style={styles.mentionItem} style={[
styles.mentionItem,
{
backgroundColor: themes[theme].auxiliaryBackground,
borderTopColor: themes[theme].separatorColor
}
]}
onPress={() => onPress(item)} onPress={() => onPress(item)}
> >
<Text style={styles.fixedMentionAvatar}>{item.username}</Text> <Text style={[styles.fixedMentionAvatar, { color: themes[theme].titleText }]}>{item.username}</Text>
<Text style={styles.mentionText}>{item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')}</Text> <Text style={[styles.mentionText, { color: themes[theme].titleText }]}>
{item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')}
</Text>
</TouchableOpacity> </TouchableOpacity>
); );
FixedMentionItem.propTypes = { FixedMentionItem.propTypes = {
item: PropTypes.object, item: PropTypes.object,
onPress: PropTypes.func onPress: PropTypes.func,
theme: PropTypes.string
}; };
export default FixedMentionItem; export default FixedMentionItem;

View File

@ -11,9 +11,10 @@ import {
MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_EMOJIS,
MENTIONS_TRACKING_TYPE_COMMANDS MENTIONS_TRACKING_TYPE_COMMANDS
} from '../constants'; } from '../constants';
import { themes } from '../../../constants/colors';
const MentionItem = ({ const MentionItem = ({
item, trackingType item, trackingType, theme
}) => { }) => {
const context = useContext(MessageboxContext); const context = useContext(MessageboxContext);
const { baseUrl, user, onPressMention } = context; const { baseUrl, user, onPressMention } = context;
@ -32,7 +33,7 @@ const MentionItem = ({
const testID = defineTestID(trackingType); const testID = defineTestID(trackingType);
if (item.username === 'all' || item.username === 'here') { if (item.username === 'all' || item.username === 'here') {
return <FixedMentionItem item={item} onPress={onPressMention} />; return <FixedMentionItem item={item} onPress={onPressMention} theme={theme} />;
} }
let content = ( let content = (
@ -46,7 +47,7 @@ const MentionItem = ({
userId={user.id} userId={user.id}
token={user.token} token={user.token}
/> />
<Text style={styles.mentionText}>{ item.username || item.name || item }</Text> <Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{ item.username || item.name || item }</Text>
</> </>
); );
@ -54,7 +55,7 @@ const MentionItem = ({
content = ( content = (
<> <>
<MentionEmoji item={item} /> <MentionEmoji item={item} />
<Text style={styles.mentionText}>:{ item.name || item }:</Text> <Text style={[styles.mentionText, { color: themes[theme].titleText }]}>:{ item.name || item }:</Text>
</> </>
); );
} }
@ -62,15 +63,21 @@ const MentionItem = ({
if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) { if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) {
content = ( content = (
<> <>
<Text style={styles.slash}>/</Text> <Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
<Text>{ item.command}</Text> <Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{ item.command}</Text>
</> </>
); );
} }
return ( return (
<TouchableOpacity <TouchableOpacity
style={styles.mentionItem} style={[
styles.mentionItem,
{
backgroundColor: themes[theme].auxiliaryBackground,
borderTopColor: themes[theme].separatorColor
}
]}
onPress={() => onPressMention(item)} onPress={() => onPressMention(item)}
testID={testID} testID={testID}
> >
@ -81,7 +88,8 @@ const MentionItem = ({
MentionItem.propTypes = { MentionItem.propTypes = {
item: PropTypes.object, item: PropTypes.object,
trackingType: PropTypes.string trackingType: PropTypes.string,
theme: PropTypes.string
}; };
export default MentionItem; export default MentionItem;

View File

@ -5,23 +5,27 @@ import equal from 'deep-equal';
import styles from '../styles'; import styles from '../styles';
import MentionItem from './MentionItem'; import MentionItem from './MentionItem';
import { themes } from '../../../constants/colors';
const Mentions = React.memo(({ mentions, trackingType }) => { const Mentions = React.memo(({ mentions, trackingType, theme }) => {
if (!trackingType) { if (!trackingType) {
return null; return null;
} }
return ( return (
<FlatList <FlatList
testID='messagebox-container' testID='messagebox-container'
style={styles.mentionList} style={[styles.mentionList, { backgroundColor: themes[theme].auxiliaryBackground }]}
data={mentions} data={mentions}
extraData={mentions} extraData={mentions}
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} />} renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} theme={theme} />}
keyExtractor={item => item.id || item.username || item.command || item} keyExtractor={item => item.id || item.username || item.command || item}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />
); );
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {
if (prevProps.theme !== nextProps.theme) {
return false;
}
if (prevProps.trackingType !== nextProps.trackingType) { if (prevProps.trackingType !== nextProps.trackingType) {
return false; return false;
} }
@ -33,7 +37,8 @@ const Mentions = React.memo(({ mentions, trackingType }) => {
Mentions.propTypes = { Mentions.propTypes = {
mentions: PropTypes.array, mentions: PropTypes.array,
trackingType: PropTypes.string trackingType: PropTypes.string,
theme: PropTypes.string
}; };
export default Mentions; export default Mentions;

View File

@ -11,7 +11,7 @@ import styles from './styles';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { isIOS, isAndroid } from '../../utils/deviceInfo'; import { isIOS, isAndroid } from '../../utils/deviceInfo';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { COLOR_SUCCESS, COLOR_DANGER } from '../../constants/colors'; import { themes } from '../../constants/colors';
export const _formatTime = function(seconds) { export const _formatTime = function(seconds) {
let minutes = Math.floor(seconds / 60); let minutes = Math.floor(seconds / 60);
@ -37,6 +37,7 @@ export default class extends React.PureComponent {
} }
static propTypes = { static propTypes = {
theme: PropTypes.string,
onFinish: PropTypes.func.isRequired onFinish: PropTypes.func.isRequired
} }
@ -122,14 +123,17 @@ export default class extends React.PureComponent {
render() { render() {
const { currentTime } = this.state; const { currentTime } = this.state;
const { theme } = this.props;
return ( return (
<SafeAreaView <SafeAreaView
key='messagebox-recording'
testID='messagebox-recording' testID='messagebox-recording'
style={styles.textBox} style={[
styles.textBox,
{ borderTopColor: themes[theme].borderColor }
]}
> >
<View style={styles.textArea}> <View style={[styles.textArea, { backgroundColor: themes[theme].messageboxBackground }]}>
<BorderlessButton <BorderlessButton
onPress={this.cancelAudioMessage} onPress={this.cancelAudioMessage}
accessibilityLabel={I18n.t('Cancel_recording')} accessibilityLabel={I18n.t('Cancel_recording')}
@ -138,11 +142,11 @@ export default class extends React.PureComponent {
> >
<CustomIcon <CustomIcon
size={22} size={22}
color={COLOR_DANGER} color={themes[theme].dangerColor}
name='cross' name='cross'
/> />
</BorderlessButton> </BorderlessButton>
<Text key='currentTime' style={styles.textBoxInput}>{currentTime}</Text> <Text key='currentTime' style={[styles.textBoxInput, { color: themes[theme].titleText }]}>{currentTime}</Text>
<BorderlessButton <BorderlessButton
onPress={this.finishAudioMessage} onPress={this.finishAudioMessage}
accessibilityLabel={I18n.t('Finish_recording')} accessibilityLabel={I18n.t('Finish_recording')}
@ -151,7 +155,7 @@ export default class extends React.PureComponent {
> >
<CustomIcon <CustomIcon
size={22} size={22}
color={COLOR_SUCCESS} color={themes[theme].successColor}
name='check' name='check'
/> />
</BorderlessButton> </BorderlessButton>

View File

@ -7,20 +7,16 @@ import { connect } from 'react-redux';
import Markdown from '../markdown'; import Markdown from '../markdown';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { import { themes } from '../../constants/colors';
COLOR_PRIMARY, COLOR_BACKGROUND_CONTAINER, COLOR_TEXT_DESCRIPTION, COLOR_WHITE
} from '../../constants/colors';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flexDirection: 'row', flexDirection: 'row',
marginTop: 10, paddingTop: 10
backgroundColor: COLOR_WHITE
}, },
messageContainer: { messageContainer: {
flex: 1, flex: 1,
marginHorizontal: 10, marginHorizontal: 10,
backgroundColor: COLOR_BACKGROUND_CONTAINER,
paddingHorizontal: 15, paddingHorizontal: 15,
paddingVertical: 10, paddingVertical: 10,
borderRadius: 4 borderRadius: 4
@ -30,7 +26,6 @@ const styles = StyleSheet.create({
alignItems: 'center' alignItems: 'center'
}, },
username: { username: {
color: COLOR_PRIMARY,
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
@ -38,7 +33,6 @@ const styles = StyleSheet.create({
fontSize: 12, fontSize: 12,
lineHeight: 16, lineHeight: 16,
marginLeft: 6, marginLeft: 6,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular, ...sharedStyles.textRegular,
fontWeight: '300' fontWeight: '300'
}, },
@ -48,7 +42,7 @@ const styles = StyleSheet.create({
}); });
const ReplyPreview = React.memo(({ const ReplyPreview = React.memo(({
message, Message_TimeFormat, baseUrl, username, useMarkdown, replying, getCustomEmoji, close message, Message_TimeFormat, baseUrl, username, useMarkdown, replying, getCustomEmoji, close, theme
}) => { }) => {
if (!replying) { if (!replying) {
return null; return null;
@ -56,18 +50,32 @@ const ReplyPreview = React.memo(({
const time = moment(message.ts).format(Message_TimeFormat); const time = moment(message.ts).format(Message_TimeFormat);
return ( return (
<View style={styles.container}> <View
<View style={styles.messageContainer}> style={[
styles.container,
{ backgroundColor: themes[theme].messageboxBackground }
]}
>
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
<View style={styles.header}> <View style={styles.header}>
<Text style={styles.username}>{message.u.username}</Text> <Text style={[styles.username, { color: themes[theme].tintColor }]}>{message.u.username}</Text>
<Text style={styles.time}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View> </View>
<Markdown msg={message.msg} baseUrl={baseUrl} username={username} getCustomEmoji={getCustomEmoji} numberOfLines={1} useMarkdown={useMarkdown} preview /> <Markdown
msg={message.msg}
baseUrl={baseUrl}
username={username}
getCustomEmoji={getCustomEmoji}
numberOfLines={1}
useMarkdown={useMarkdown}
preview
theme={theme}
/>
</View> </View>
<CustomIcon name='cross' color={COLOR_TEXT_DESCRIPTION} size={20} style={styles.close} onPress={close} /> <CustomIcon name='cross' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} />
</View> </View>
); );
}, (prevProps, nextProps) => prevProps.replying === nextProps.replying); }, (prevProps, nextProps) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme);
ReplyPreview.propTypes = { ReplyPreview.propTypes = {
replying: PropTypes.bool, replying: PropTypes.bool,
@ -77,7 +85,8 @@ ReplyPreview.propTypes = {
close: PropTypes.func.isRequired, close: PropTypes.func.isRequired,
baseUrl: PropTypes.string.isRequired, baseUrl: PropTypes.string.isRequired,
username: PropTypes.string.isRequired, username: PropTypes.string.isRequired,
getCustomEmoji: PropTypes.func getCustomEmoji: PropTypes.func,
theme: PropTypes.string
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({

View File

@ -4,20 +4,21 @@ import PropTypes from 'prop-types';
import { SendButton, AudioButton, FileButton } from './buttons'; import { SendButton, AudioButton, FileButton } from './buttons';
const RightButtons = React.memo(({ const RightButtons = React.memo(({
showSend, submit, recordAudioMessage, showFileActions theme, showSend, submit, recordAudioMessage, showFileActions
}) => { }) => {
if (showSend) { if (showSend) {
return <SendButton onPress={submit} />; return <SendButton onPress={submit} theme={theme} />;
} }
return ( return (
<> <>
<AudioButton onPress={recordAudioMessage} /> <AudioButton onPress={recordAudioMessage} theme={theme} />
<FileButton onPress={showFileActions} /> <FileButton onPress={showFileActions} theme={theme} />
</> </>
); );
}); });
RightButtons.propTypes = { RightButtons.propTypes = {
theme: PropTypes.string,
showSend: PropTypes.bool, showSend: PropTypes.bool,
submit: PropTypes.func.isRequired, submit: PropTypes.func.isRequired,
recordAudioMessage: PropTypes.func.isRequired, recordAudioMessage: PropTypes.func.isRequired,

View File

@ -4,15 +4,16 @@ import PropTypes from 'prop-types';
import { SendButton, AudioButton } from './buttons'; import { SendButton, AudioButton } from './buttons';
const RightButtons = React.memo(({ const RightButtons = React.memo(({
showSend, submit, recordAudioMessage theme, showSend, submit, recordAudioMessage
}) => { }) => {
if (showSend) { if (showSend) {
return <SendButton onPress={submit} />; return <SendButton theme={theme} onPress={submit} />;
} }
return <AudioButton onPress={recordAudioMessage} />; return <AudioButton theme={theme} onPress={recordAudioMessage} />;
}); });
RightButtons.propTypes = { RightButtons.propTypes = {
theme: PropTypes.string,
showSend: PropTypes.bool, showSend: PropTypes.bool,
submit: PropTypes.func.isRequired, submit: PropTypes.func.isRequired,
recordAudioMessage: PropTypes.func.isRequired recordAudioMessage: PropTypes.func.isRequired

View File

@ -11,15 +11,12 @@ import TextInput from '../TextInput';
import Button from '../Button'; import Button from '../Button';
import I18n from '../../i18n'; import I18n from '../../i18n';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS } from '../../utils/deviceInfo';
import { import { themes } from '../../constants/colors';
COLOR_PRIMARY, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE
} from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { withTheme } from '../../theme';
import { withSplit } from '../../split'; import { withSplit } from '../../split';
const cancelButtonColor = COLOR_BACKGROUND_CONTAINER;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
modal: { modal: {
width: '100%', width: '100%',
@ -33,12 +30,10 @@ const styles = StyleSheet.create({
}, },
title: { title: {
fontSize: 14, fontSize: 14,
...sharedStyles.textColorTitle,
...sharedStyles.textBold ...sharedStyles.textBold
}, },
container: { container: {
height: 430, height: 430,
backgroundColor: COLOR_WHITE,
flexDirection: 'column' flexDirection: 'column'
}, },
scrollView: { scrollView: {
@ -57,8 +52,7 @@ const styles = StyleSheet.create({
buttonContainer: { buttonContainer: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
padding: 16, padding: 16
backgroundColor: COLOR_BACKGROUND_CONTAINER
}, },
button: { button: {
marginBottom: 0 marginBottom: 0
@ -74,7 +68,6 @@ const styles = StyleSheet.create({
textAlign: 'center' textAlign: 'center'
}, },
fileIcon: { fileIcon: {
color: COLOR_PRIMARY,
margin: 20, margin: 20,
flex: 1, flex: 1,
textAlign: 'center' textAlign: 'center'
@ -83,7 +76,6 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
borderRadius: 4, borderRadius: 4,
height: 150, height: 150,
backgroundColor: '#1f2329',
marginBottom: 6, marginBottom: 6,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
@ -98,6 +90,7 @@ class UploadModal extends Component {
close: PropTypes.func, close: PropTypes.func,
submit: PropTypes.func, submit: PropTypes.func,
window: PropTypes.object, window: PropTypes.object,
theme: PropTypes.string,
split: PropTypes.bool split: PropTypes.bool
} }
@ -120,7 +113,9 @@ class UploadModal extends Component {
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { name, description, file } = this.state; const { name, description, file } = this.state;
const { window, isVisible, split } = this.props; const {
window, isVisible, split, theme
} = this.props;
if (nextState.name !== name) { if (nextState.name !== name) {
return true; return true;
@ -128,6 +123,9 @@ class UploadModal extends Component {
if (nextProps.split !== split) { if (nextProps.split !== split) {
return true; return true;
} }
if (nextProps.theme !== theme) {
return true;
}
if (nextState.description !== description) { if (nextState.description !== description) {
return true; return true;
} }
@ -150,67 +148,69 @@ class UploadModal extends Component {
} }
renderButtons = () => { renderButtons = () => {
const { close } = this.props; const { close, theme } = this.props;
if (isIOS) { if (isIOS) {
return ( return (
<View style={styles.buttonContainer}> <View style={[styles.buttonContainer, { backgroundColor: themes[theme].auxiliaryBackground }]}>
<Button <Button
title={I18n.t('Cancel')} title={I18n.t('Cancel')}
type='secondary' type='secondary'
backgroundColor={cancelButtonColor} backgroundColor={themes[theme].chatComponentBackground}
style={styles.button} style={styles.button}
onPress={close} onPress={close}
theme={theme}
/> />
<Button <Button
title={I18n.t('Send')} title={I18n.t('Send')}
type='primary' type='primary'
style={styles.button} style={styles.button}
onPress={this.submit} onPress={this.submit}
theme={theme}
/> />
</View> </View>
); );
} }
// FIXME: RNGH don't work well on Android modals: https://github.com/kmagiera/react-native-gesture-handler/issues/139 // FIXME: RNGH don't work well on Android modals: https://github.com/kmagiera/react-native-gesture-handler/issues/139
return ( return (
<View style={styles.buttonContainer}> <View style={[styles.buttonContainer, { backgroundColor: themes[theme].auxiliaryBackground }]}>
<TouchableHighlight <TouchableHighlight
onPress={close} onPress={close}
style={[styles.androidButton, { backgroundColor: cancelButtonColor }]} style={[styles.androidButton, { backgroundColor: themes[theme].chatComponentBackground }]}
underlayColor={cancelButtonColor} underlayColor={themes[theme].chatComponentBackground}
activeOpacity={0.5} activeOpacity={0.5}
> >
<Text style={[styles.androidButtonText, { ...sharedStyles.textBold, color: COLOR_PRIMARY }]}>{I18n.t('Cancel')}</Text> <Text style={[styles.androidButtonText, { ...sharedStyles.textBold, color: themes[theme].tintColor }]}>{I18n.t('Cancel')}</Text>
</TouchableHighlight> </TouchableHighlight>
<TouchableHighlight <TouchableHighlight
onPress={this.submit} onPress={this.submit}
style={[styles.androidButton, { backgroundColor: COLOR_PRIMARY }]} style={[styles.androidButton, { backgroundColor: themes[theme].tintColor }]}
underlayColor={COLOR_PRIMARY} underlayColor={themes[theme].tintColor}
activeOpacity={0.5} activeOpacity={0.5}
> >
<Text style={[styles.androidButtonText, { ...sharedStyles.textMedium, color: COLOR_WHITE }]}>{I18n.t('Send')}</Text> <Text style={[styles.androidButtonText, { ...sharedStyles.textMedium, color: themes[theme].buttonText }]}>{I18n.t('Send')}</Text>
</TouchableHighlight> </TouchableHighlight>
</View> </View>
); );
} }
renderPreview() { renderPreview() {
const { file, split } = this.props; const { file, split, theme } = this.props;
if (file.mime && file.mime.match(/image/)) { if (file.mime && file.mime.match(/image/)) {
return (<Image source={{ isStatic: true, uri: file.path }} style={[styles.image, split && styles.bigPreview]} />); return (<Image source={{ isStatic: true, uri: file.path }} style={[styles.image, split && styles.bigPreview]} />);
} }
if (file.mime && file.mime.match(/video/)) { if (file.mime && file.mime.match(/video/)) {
return ( return (
<View style={styles.video}> <View style={[styles.video, { backgroundColor: themes[theme].bannerBackground }]}>
<CustomIcon name='play' size={72} color={COLOR_WHITE} /> <CustomIcon name='play' size={72} color={themes[theme].buttonText} />
</View> </View>
); );
} }
return (<CustomIcon name='file-generic' size={72} style={styles.fileIcon} />); return (<CustomIcon name='file-generic' size={72} style={[styles.fileIcon, { color: themes[theme].tintColor }]} />);
} }
render() { render() {
const { const {
window: { width }, isVisible, close, split window: { width }, isVisible, close, split, theme
} = this.props; } = this.props;
const { name, description } = this.state; const { name, description } = this.state;
return ( return (
@ -225,9 +225,9 @@ class UploadModal extends Component {
hideModalContentWhileAnimating hideModalContentWhileAnimating
avoidKeyboard avoidKeyboard
> >
<View style={[styles.container, { width: (isTablet ? '80%' : width - 32) }, split && sharedStyles.modal]}> <View style={[styles.container, { width: width - 32, backgroundColor: themes[theme].chatComponentBackground }, split && sharedStyles.modal]}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<Text style={styles.title}>{I18n.t('Upload_file_question_mark')}</Text> <Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Upload_file_question_mark')}</Text>
</View> </View>
<ScrollView style={styles.scrollView}> <ScrollView style={styles.scrollView}>
@ -236,11 +236,13 @@ class UploadModal extends Component {
placeholder={I18n.t('File_name')} placeholder={I18n.t('File_name')}
value={name} value={name}
onChangeText={value => this.setState({ name: value })} onChangeText={value => this.setState({ name: value })}
theme={theme}
/> />
<TextInput <TextInput
placeholder={I18n.t('File_description')} placeholder={I18n.t('File_description')}
value={description} value={description}
onChangeText={value => this.setState({ description: value })} onChangeText={value => this.setState({ description: value })}
theme={theme}
/> />
</ScrollView> </ScrollView>
{this.renderButtons()} {this.renderButtons()}
@ -250,4 +252,4 @@ class UploadModal extends Component {
} }
} }
export default responsive(withSplit(UploadModal)); export default responsive(withTheme(withSplit(UploadModal)));

View File

@ -3,16 +3,18 @@ import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
const AudioButton = React.memo(({ onPress }) => ( const AudioButton = React.memo(({ theme, onPress }) => (
<BaseButton <BaseButton
onPress={onPress} onPress={onPress}
testID='messagebox-send-audio' testID='messagebox-send-audio'
accessibilityLabel='Send_audio_message' accessibilityLabel='Send_audio_message'
icon='mic' icon='mic'
theme={theme}
/> />
)); ));
AudioButton.propTypes = { AudioButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired onPress: PropTypes.func.isRequired
}; };

View File

@ -2,13 +2,13 @@ import React from 'react';
import { BorderlessButton } from 'react-native-gesture-handler'; import { BorderlessButton } from 'react-native-gesture-handler';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { COLOR_PRIMARY } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
import styles from '../styles'; import styles from '../styles';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
const BaseButton = React.memo(({ const BaseButton = React.memo(({
onPress, testID, accessibilityLabel, icon onPress, testID, accessibilityLabel, icon, theme
}) => ( }) => (
<BorderlessButton <BorderlessButton
onPress={onPress} onPress={onPress}
@ -17,11 +17,12 @@ const BaseButton = React.memo(({
accessibilityLabel={I18n.t(accessibilityLabel)} accessibilityLabel={I18n.t(accessibilityLabel)}
accessibilityTraits='button' accessibilityTraits='button'
> >
<CustomIcon name={icon} size={23} color={COLOR_PRIMARY} /> <CustomIcon name={icon} size={23} color={themes[theme].tintColor} />
</BorderlessButton> </BorderlessButton>
)); ));
BaseButton.propTypes = { BaseButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired, onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired, testID: PropTypes.string.isRequired,
accessibilityLabel: PropTypes.string.isRequired, accessibilityLabel: PropTypes.string.isRequired,

View File

@ -3,16 +3,18 @@ import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
const CancelEditingButton = React.memo(({ onPress }) => ( const CancelEditingButton = React.memo(({ theme, onPress }) => (
<BaseButton <BaseButton
onPress={onPress} onPress={onPress}
testID='messagebox-cancel-editing' testID='messagebox-cancel-editing'
accessibilityLabel='Cancel_editing' accessibilityLabel='Cancel_editing'
icon='cross' icon='cross'
theme={theme}
/> />
)); ));
CancelEditingButton.propTypes = { CancelEditingButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired onPress: PropTypes.func.isRequired
}; };

View File

@ -3,16 +3,18 @@ import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
const FileButton = React.memo(({ onPress }) => ( const FileButton = React.memo(({ theme, onPress }) => (
<BaseButton <BaseButton
onPress={onPress} onPress={onPress}
testID='messagebox-actions' testID='messagebox-actions'
accessibilityLabel='Message_actions' accessibilityLabel='Message_actions'
icon='plus' icon='plus'
theme={theme}
/> />
)); ));
FileButton.propTypes = { FileButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired onPress: PropTypes.func.isRequired
}; };

View File

@ -3,16 +3,18 @@ import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
const SendButton = React.memo(({ onPress }) => ( const SendButton = React.memo(({ theme, onPress }) => (
<BaseButton <BaseButton
onPress={onPress} onPress={onPress}
testID='messagebox-send-message' testID='messagebox-send-message'
accessibilityLabel='Send_message' accessibilityLabel='Send_message'
icon='send1' icon='send1'
theme={theme}
/> />
)); ));
SendButton.propTypes = { SendButton.propTypes = {
theme: PropTypes.string,
onPress: PropTypes.func.isRequired onPress: PropTypes.func.isRequired
}; };

View File

@ -3,7 +3,9 @@ import PropTypes from 'prop-types';
import BaseButton from './BaseButton'; import BaseButton from './BaseButton';
const ToggleEmojiButton = React.memo(({ show, open, close }) => { const ToggleEmojiButton = React.memo(({
theme, show, open, close
}) => {
if (show) { if (show) {
return ( return (
<BaseButton <BaseButton
@ -11,6 +13,7 @@ const ToggleEmojiButton = React.memo(({ show, open, close }) => {
testID='messagebox-close-emoji' testID='messagebox-close-emoji'
accessibilityLabel='Close_emoji_selector' accessibilityLabel='Close_emoji_selector'
icon='keyboard' icon='keyboard'
theme={theme}
/> />
); );
} }
@ -20,11 +23,13 @@ const ToggleEmojiButton = React.memo(({ show, open, close }) => {
testID='messagebox-open-emoji' testID='messagebox-open-emoji'
accessibilityLabel='Open_emoji_selector' accessibilityLabel='Open_emoji_selector'
icon='emoji' icon='emoji'
theme={theme}
/> />
); );
}); });
ToggleEmojiButton.propTypes = { ToggleEmojiButton.propTypes = {
theme: PropTypes.string,
show: PropTypes.bool, show: PropTypes.bool,
open: PropTypes.func.isRequired, open: PropTypes.func.isRequired,
close: PropTypes.func.isRequired close: PropTypes.func.isRequired

View File

@ -1,8 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { View, Alert, Keyboard } from 'react-native';
View, TextInput, Alert, Keyboard
} from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-keyboard-input'; import { KeyboardAccessoryView } from 'react-native-keyboard-input';
import ImagePicker from 'react-native-image-crop-picker'; import ImagePicker from 'react-native-image-crop-picker';
@ -11,6 +9,7 @@ import DocumentPicker from 'react-native-document-picker';
import ActionSheet from 'react-native-action-sheet'; import ActionSheet from 'react-native-action-sheet';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import TextInput from '../../presentation/TextInput';
import { userTyping as userTypingAction } from '../../actions/room'; import { userTyping as userTypingAction } from '../../actions/room';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import styles from './styles'; import styles from './styles';
@ -22,7 +21,7 @@ import log from '../../utils/log';
import I18n from '../../i18n'; import I18n from '../../i18n';
import ReplyPreview from './ReplyPreview'; import ReplyPreview from './ReplyPreview';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import { COLOR_TEXT_DESCRIPTION } from '../../constants/colors'; import { themes } from '../../constants/colors';
import LeftButtons from './LeftButtons'; import LeftButtons from './LeftButtons';
import RightButtons from './RightButtons'; import RightButtons from './RightButtons';
import { isAndroid, isTablet } from '../../utils/deviceInfo'; import { isAndroid, isTablet } from '../../utils/deviceInfo';
@ -43,6 +42,7 @@ import {
MENTIONS_TRACKING_TYPE_USERS MENTIONS_TRACKING_TYPE_USERS
} from './constants'; } from './constants';
import CommandsPreview from './CommandsPreview'; import CommandsPreview from './CommandsPreview';
import { withTheme } from '../../theme';
const imagePickerConfig = { const imagePickerConfig = {
cropping: true, cropping: true,
@ -88,6 +88,7 @@ class MessageBox extends Component {
editRequest: PropTypes.func.isRequired, editRequest: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
typing: PropTypes.func, typing: PropTypes.func,
theme: PropTypes.string,
replyCancel: PropTypes.func replyCancel: PropTypes.func
} }
@ -198,8 +199,11 @@ class MessageBox extends Component {
} = this.state; } = this.state;
const { const {
roomType, replying, editing, isFocused roomType, replying, editing, isFocused, theme
} = this.props; } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (!isFocused()) { if (!isFocused()) {
return false; return false;
} }
@ -763,7 +767,7 @@ class MessageBox extends Component {
recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview
} = this.state; } = this.state;
const { const {
editing, message, replying, replyCancel, user, getCustomEmoji editing, message, replying, replyCancel, user, getCustomEmoji, theme
} = this.props; } = this.props;
const isAndroidTablet = isTablet && isAndroid ? { const isAndroidTablet = isTablet && isAndroid ? {
@ -773,25 +777,30 @@ class MessageBox extends Component {
} : {}; } : {};
if (recording) { if (recording) {
return <Recording onFinish={this.finishAudioMessage} />; return <Recording theme={theme} onFinish={this.finishAudioMessage} />;
} }
return ( return (
<> <>
<CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} /> <CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} />
<Mentions mentions={mentions} trackingType={trackingType} /> <Mentions mentions={mentions} trackingType={trackingType} theme={theme} />
<View style={styles.composer}> <View style={[styles.composer, { borderTopColor: themes[theme].separatorColor }]}>
<ReplyPreview <ReplyPreview
message={message} message={message}
close={replyCancel} close={replyCancel}
username={user.username} username={user.username}
replying={replying} replying={replying}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme}
/> />
<View <View
style={[styles.textArea, editing && styles.editing]} style={[
styles.textArea,
{ backgroundColor: themes[theme].messageboxBackground }, editing && { backgroundColor: themes[theme].chatComponentBackground }
]}
testID='messagebox' testID='messagebox'
> >
<LeftButtons <LeftButtons
theme={theme}
showEmojiKeyboard={showEmojiKeyboard} showEmojiKeyboard={showEmojiKeyboard}
editing={editing} editing={editing}
showFileActions={this.showFileActions} showFileActions={this.showFileActions}
@ -810,11 +819,12 @@ class MessageBox extends Component {
underlineColorAndroid='transparent' underlineColorAndroid='transparent'
defaultValue='' defaultValue=''
multiline multiline
placeholderTextColor={COLOR_TEXT_DESCRIPTION}
testID='messagebox-input' testID='messagebox-input'
theme={theme}
{...isAndroidTablet} {...isAndroidTablet}
/> />
<RightButtons <RightButtons
theme={theme}
showSend={showSend} showSend={showSend}
submit={this.submit} submit={this.submit}
recordAudioMessage={this.recordAudioMessage} recordAudioMessage={this.recordAudioMessage}
@ -829,7 +839,7 @@ class MessageBox extends Component {
render() { render() {
console.count(`${ this.constructor.name }.render calls`); console.count(`${ this.constructor.name }.render calls`);
const { showEmojiKeyboard, file } = this.state; const { showEmojiKeyboard, file } = this.state;
const { user, baseUrl } = this.props; const { user, baseUrl, theme } = this.props;
return ( return (
<MessageboxContext.Provider <MessageboxContext.Provider
value={{ value={{
@ -849,6 +859,7 @@ class MessageBox extends Component {
// revealKeyboardInteractive // revealKeyboardInteractive
requiresSameParentToManageScrollView requiresSameParentToManageScrollView
addBottomView addBottomView
bottomViewColor={themes[theme].messageboxBackground}
/> />
<UploadModal <UploadModal
isVisible={(file && file.isVisible)} isVisible={(file && file.isVisible)}
@ -877,4 +888,4 @@ const dispatchToProps = ({
typing: (rid, status) => userTypingAction(rid, status) typing: (rid, status) => userTypingAction(rid, status)
}); });
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(MessageBox); export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withTheme(MessageBox));

View File

@ -2,33 +2,25 @@ import { StyleSheet } from 'react-native';
import { isIOS } from '../../utils/deviceInfo'; import { isIOS } from '../../utils/deviceInfo';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import {
COLOR_BORDER, COLOR_SEPARATOR, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_PRIMARY
} from '../../constants/colors';
const MENTION_HEIGHT = 50; const MENTION_HEIGHT = 50;
const SCROLLVIEW_MENTION_HEIGHT = 4 * MENTION_HEIGHT; const SCROLLVIEW_MENTION_HEIGHT = 4 * MENTION_HEIGHT;
export default StyleSheet.create({ export default StyleSheet.create({
textBox: { textBox: {
backgroundColor: COLOR_WHITE,
flex: 0, flex: 0,
alignItems: 'center', alignItems: 'center',
borderTopWidth: StyleSheet.hairlineWidth, borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: COLOR_SEPARATOR,
zIndex: 2 zIndex: 2
}, },
composer: { composer: {
backgroundColor: COLOR_WHITE,
flexDirection: 'column', flexDirection: 'column',
borderTopColor: COLOR_SEPARATOR,
borderTopWidth: StyleSheet.hairlineWidth borderTopWidth: StyleSheet.hairlineWidth
}, },
textArea: { textArea: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
flexGrow: 0, flexGrow: 0
backgroundColor: COLOR_WHITE
}, },
textBoxInput: { textBoxInput: {
textAlignVertical: 'center', textAlignVertical: 'center',
@ -42,12 +34,8 @@ export default StyleSheet.create({
paddingRight: 0, paddingRight: 0,
fontSize: 17, fontSize: 17,
letterSpacing: 0, letterSpacing: 0,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
editing: {
backgroundColor: '#fff5df'
},
actionButton: { actionButton: {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
@ -59,9 +47,7 @@ export default StyleSheet.create({
}, },
mentionItem: { mentionItem: {
height: MENTION_HEIGHT, height: MENTION_HEIGHT,
backgroundColor: COLOR_BACKGROUND_CONTAINER, borderTopWidth: StyleSheet.hairlineWidth,
borderTopWidth: 1,
borderTopColor: COLOR_BORDER,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingHorizontal: 5 paddingHorizontal: 5
@ -81,30 +67,17 @@ export default StyleSheet.create({
textAlign: 'center', textAlign: 'center',
width: 46, width: 46,
fontSize: 14, fontSize: 14,
...sharedStyles.textBold, ...sharedStyles.textBold
...sharedStyles.textColorNormal
}, },
mentionText: { mentionText: {
fontSize: 14, fontSize: 14,
...sharedStyles.textRegular, ...sharedStyles.textRegular
...sharedStyles.textColorNormal
}, },
emojiKeyboardContainer: { emojiKeyboardContainer: {
flex: 1, flex: 1,
borderTopColor: COLOR_BORDER, borderTopWidth: StyleSheet.hairlineWidth
borderTopWidth: 1
},
iphoneXArea: {
height: 50,
backgroundColor: COLOR_WHITE,
position: 'absolute',
bottom: 0,
left: 0,
right: 0
}, },
slash: { slash: {
color: COLOR_PRIMARY,
backgroundColor: COLOR_BORDER,
height: 30, height: 30,
width: 30, width: 30,
padding: 5, padding: 5,
@ -120,7 +93,6 @@ export default StyleSheet.create({
borderRadius: 4 borderRadius: 4
}, },
commandPreview: { commandPreview: {
backgroundColor: COLOR_BACKGROUND_CONTAINER,
height: 100, height: 100,
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',

View File

@ -10,7 +10,8 @@ import Emoji from './message/Emoji';
import I18n from '../i18n'; import I18n from '../i18n';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { COLOR_WHITE } from '../constants/colors'; import { themes } from '../constants/colors';
import { withTheme } from '../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
titleContainer: { titleContainer: {
@ -18,18 +19,15 @@ const styles = StyleSheet.create({
paddingVertical: 10 paddingVertical: 10
}, },
title: { title: {
color: COLOR_WHITE,
textAlign: 'center', textAlign: 'center',
fontSize: 16, fontSize: 16,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
reactCount: { reactCount: {
color: COLOR_WHITE,
fontSize: 13, fontSize: 13,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
peopleReacted: { peopleReacted: {
color: COLOR_WHITE,
fontSize: 14, fontSize: 14,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
@ -54,15 +52,14 @@ const styles = StyleSheet.create({
closeButton: { closeButton: {
position: 'absolute', position: 'absolute',
left: 0, left: 0,
top: 10, top: 10
color: COLOR_WHITE
} }
}); });
const standardEmojiStyle = { fontSize: 20 }; const standardEmojiStyle = { fontSize: 20 };
const customEmojiStyle = { width: 20, height: 20 }; const customEmojiStyle = { width: 20, height: 20 };
const Item = React.memo(({ const Item = React.memo(({
item, user, baseUrl, getCustomEmoji item, user, baseUrl, getCustomEmoji, theme
}) => { }) => {
const count = item.usernames.length; const count = item.usernames.length;
let usernames = item.usernames.slice(0, 3) let usernames = item.usernames.slice(0, 3)
@ -84,27 +81,29 @@ const Item = React.memo(({
/> />
</View> </View>
<View style={styles.peopleItemContainer}> <View style={styles.peopleItemContainer}>
<Text style={styles.reactCount}> <Text style={[styles.reactCount, { color: themes[theme].buttonText }]}>
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })} {count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
</Text> </Text>
<Text style={styles.peopleReacted}>{ usernames }</Text> <Text style={[styles.peopleReacted, { color: themes[theme].buttonText }]}>{ usernames }</Text>
</View> </View>
</View> </View>
); );
}); });
const ModalContent = React.memo(({ message, onClose, ...props }) => { const ModalContent = React.memo(({
message, onClose, ...props
}) => {
if (message && message.reactions) { if (message && message.reactions) {
return ( return (
<SafeAreaView style={{ flex: 1 }}> <SafeAreaView style={{ flex: 1 }}>
<Touchable onPress={onClose}> <Touchable onPress={onClose}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<CustomIcon <CustomIcon
style={styles.closeButton} style={[styles.closeButton, { color: themes[props.theme].buttonText }]}
name='cross' name='cross'
size={20} size={20}
/> />
<Text style={styles.title}>{I18n.t('Reactions')}</Text> <Text style={[styles.title, { color: themes[props.theme].buttonText }]}>{I18n.t('Reactions')}</Text>
</View> </View>
</Touchable> </Touchable>
<FlatList <FlatList
@ -119,7 +118,9 @@ const ModalContent = React.memo(({ message, onClose, ...props }) => {
return null; return null;
}); });
const ReactionsModal = React.memo(({ isVisible, onClose, ...props }) => ( const ReactionsModal = React.memo(({
isVisible, onClose, theme, ...props
}) => (
<Modal <Modal
isVisible={isVisible} isVisible={isVisible}
onBackdropPress={onClose} onBackdropPress={onClose}
@ -128,19 +129,21 @@ const ReactionsModal = React.memo(({ isVisible, onClose, ...props }) => (
onSwipeComplete={onClose} onSwipeComplete={onClose}
swipeDirection={['up', 'left', 'right', 'down']} swipeDirection={['up', 'left', 'right', 'down']}
> >
<ModalContent onClose={onClose} {...props} /> <ModalContent onClose={onClose} theme={theme} {...props} />
</Modal> </Modal>
), (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible); ), (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.theme === nextProps.theme);
ReactionsModal.propTypes = { ReactionsModal.propTypes = {
isVisible: PropTypes.bool, isVisible: PropTypes.bool,
onClose: PropTypes.func onClose: PropTypes.func,
theme: PropTypes.string
}; };
ReactionsModal.displayName = 'ReactionsModal'; ReactionsModal.displayName = 'ReactionsModal';
ModalContent.propTypes = { ModalContent.propTypes = {
message: PropTypes.object, message: PropTypes.object,
onClose: PropTypes.func onClose: PropTypes.func,
theme: PropTypes.string
}; };
ModalContent.displayName = 'ReactionsModalContent'; ModalContent.displayName = 'ReactionsModalContent';
@ -148,8 +151,9 @@ Item.propTypes = {
item: PropTypes.object, item: PropTypes.object,
user: PropTypes.object, user: PropTypes.object,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
getCustomEmoji: PropTypes.func getCustomEmoji: PropTypes.func,
theme: PropTypes.string
}; };
Item.displayName = 'ReactionsModalItem'; Item.displayName = 'ReactionsModalItem';
export default ReactionsModal; export default withTheme(ReactionsModal);

View File

@ -2,43 +2,42 @@ import React from 'react';
import { Image, StyleSheet } from 'react-native'; import { Image, StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import { COLOR_TEXT_DESCRIPTION } from '../constants/colors'; import { themes } from '../constants/colors';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
style: { style: {
marginRight: 7, marginRight: 7,
marginTop: 3 marginTop: 3
}, },
imageColor: {
tintColor: COLOR_TEXT_DESCRIPTION
},
iconColor: {
color: COLOR_TEXT_DESCRIPTION
},
discussion: { discussion: {
marginRight: 6 marginRight: 6
} }
}); });
const RoomTypeIcon = React.memo(({ type, size, style }) => { const RoomTypeIcon = React.memo(({
type, size, style, theme
}) => {
if (!type) { if (!type) {
return null; return null;
} }
const color = themes[theme].auxiliaryText;
if (type === 'discussion') { if (type === 'discussion') {
// FIXME: These are temporary only. We should have all room icons on <Customicon />, but our design team is still working on this. // FIXME: These are temporary only. We should have all room icons on <Customicon />, but our design team is still working on this.
return <CustomIcon name='chat' size={13} style={[styles.style, styles.iconColor, styles.discussion]} />; return <CustomIcon name='chat' size={13} style={[styles.style, styles.iconColor, styles.discussion, { color }]} />;
} }
if (type === 'c') { if (type === 'c') {
return <Image source={{ uri: 'hashtag' }} style={[styles.style, styles.imageColor, style, { width: size, height: size }]} />; return <Image source={{ uri: 'hashtag' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
} if (type === 'd') { } if (type === 'd') {
return <CustomIcon name='at' size={13} style={[styles.style, styles.iconColor, styles.discussion]} />; return <CustomIcon name='at' size={13} style={[styles.style, styles.discussion, { color }]} />;
} }
return <Image source={{ uri: 'lock' }} style={[styles.style, styles.imageColor, style, { width: size, height: size }]} />; return <Image source={{ uri: 'lock' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
}); });
RoomTypeIcon.propTypes = { RoomTypeIcon.propTypes = {
theme: PropTypes.string,
type: PropTypes.string, type: PropTypes.string,
size: PropTypes.number, size: PropTypes.number,
style: PropTypes.object style: PropTypes.object

View File

@ -1,27 +1,25 @@
import React from 'react'; import React from 'react';
import { import { View, StyleSheet, Text } from 'react-native';
View, StyleSheet, TextInput, Text
} from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import TextInput from '../presentation/TextInput';
import I18n from '../i18n'; import I18n from '../i18n';
import { isIOS } from '../utils/deviceInfo';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { withTheme } from '../theme';
import { themes } from '../constants/colors';
import { isIOS } from '../utils/deviceInfo';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: isIOS ? '#F7F8FA' : '#54585E',
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
flex: 1 flex: 1
}, },
searchBox: { searchBox: {
alignItems: 'center', alignItems: 'center',
backgroundColor: '#E1E5E8',
borderRadius: 10, borderRadius: 10,
color: '#8E8E93',
flexDirection: 'row', flexDirection: 'row',
fontSize: 17, fontSize: 17,
height: 36, height: 36,
@ -31,7 +29,6 @@ const styles = StyleSheet.create({
flex: 1 flex: 1
}, },
input: { input: {
color: '#8E8E93',
flex: 1, flex: 1,
fontSize: 17, fontSize: 17,
marginLeft: 8, marginLeft: 8,
@ -44,23 +41,27 @@ const styles = StyleSheet.create({
}, },
cancelText: { cancelText: {
...sharedStyles.textRegular, ...sharedStyles.textRegular,
...sharedStyles.textColorHeaderBack,
fontSize: 17 fontSize: 17
} }
}); });
const CancelButton = onCancelPress => ( const CancelButton = (onCancelPress, theme) => (
<Touchable onPress={onCancelPress} style={styles.cancel}> <Touchable onPress={onCancelPress} style={styles.cancel}>
<Text style={styles.cancelText}>{I18n.t('Cancel')}</Text> <Text style={[styles.cancelText, { color: themes[theme].tintColor }]}>{I18n.t('Cancel')}</Text>
</Touchable> </Touchable>
); );
const SearchBox = ({ const SearchBox = ({
onChangeText, onSubmitEditing, testID, hasCancel, onCancelPress, inputRef, ...props onChangeText, onSubmitEditing, testID, hasCancel, onCancelPress, inputRef, theme, ...props
}) => ( }) => (
<View style={styles.container}> <View
<View style={styles.searchBox}> style={[
<CustomIcon name='magnifier' size={14} color='#8E8E93' /> styles.container,
{ backgroundColor: isIOS ? themes[theme].headerBackground : themes[theme].headerSecondaryBackground }
]}
>
<View style={[styles.searchBox, { backgroundColor: themes[theme].searchboxBackground }]}>
<CustomIcon name='magnifier' size={14} color={themes[theme].auxiliaryText} />
<TextInput <TextInput
ref={inputRef} ref={inputRef}
autoCapitalize='none' autoCapitalize='none'
@ -74,10 +75,11 @@ const SearchBox = ({
underlineColorAndroid='transparent' underlineColorAndroid='transparent'
onChangeText={onChangeText} onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing} onSubmitEditing={onSubmitEditing}
theme={theme}
{...props} {...props}
/> />
</View> </View>
{ hasCancel ? CancelButton(onCancelPress) : null } { hasCancel ? CancelButton(onCancelPress, theme) : null }
</View> </View>
); );
@ -86,8 +88,9 @@ SearchBox.propTypes = {
onSubmitEditing: PropTypes.func, onSubmitEditing: PropTypes.func,
hasCancel: PropTypes.bool, hasCancel: PropTypes.bool,
onCancelPress: PropTypes.func, onCancelPress: PropTypes.func,
theme: PropTypes.string,
inputRef: PropTypes.func, inputRef: PropTypes.func,
testID: PropTypes.string testID: PropTypes.string
}; };
export default SearchBox; export default withTheme(SearchBox);

View File

@ -2,20 +2,28 @@ import React from 'react';
import { View, StyleSheet } from 'react-native'; import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { COLOR_SEPARATOR } from '../constants/colors'; import { themes } from '../constants/colors';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
separator: { separator: {
height: StyleSheet.hairlineWidth, height: StyleSheet.hairlineWidth
backgroundColor: COLOR_SEPARATOR
} }
}); });
const Separator = React.memo(({ style }) => <View style={[styles.separator, style]} />); const Separator = React.memo(({ style, theme }) => (
<View
style={[
styles.separator,
style,
{ backgroundColor: themes[theme].separatorColor }
]}
/>
));
Separator.propTypes = { Separator.propTypes = {
style: PropTypes.object style: PropTypes.object,
theme: PropTypes.string
}; };
export default Separator; export default Separator;

View File

@ -3,23 +3,19 @@ import { StatusBar as StatusBarRN } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { isIOS } from '../utils/deviceInfo'; import { isIOS } from '../utils/deviceInfo';
import { HEADER_BACKGROUND, COLOR_WHITE } from '../constants/colors'; import { themes } from '../constants/colors';
import { withTheme } from '../theme';
const HEADER_BAR_STYLE = isIOS ? 'dark-content' : 'light-content'; const StatusBar = React.memo(({ theme }) => {
let barStyle = 'light-content';
const StatusBar = React.memo(({ light }) => { if (theme === 'light' && isIOS) {
if (light) { barStyle = 'dark-content';
return <StatusBarRN backgroundColor={COLOR_WHITE} barStyle='dark-content' animated />;
} }
return <StatusBarRN backgroundColor={HEADER_BACKGROUND} barStyle={HEADER_BAR_STYLE} animated />; return <StatusBarRN backgroundColor={themes[theme].headerBackground} barStyle={barStyle} animated />;
}); });
StatusBar.propTypes = { StatusBar.propTypes = {
light: PropTypes.bool theme: PropTypes.string
}; };
StatusBar.defaultProps = { export default withTheme(StatusBar);
light: false
};
export default StatusBar;

View File

@ -1,37 +1,34 @@
import React from 'react'; import React from 'react';
import { import { View, StyleSheet, Text } from 'react-native';
View, StyleSheet, Text, TextInput
} from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { BorderlessButton } from 'react-native-gesture-handler'; import { BorderlessButton } from 'react-native-gesture-handler';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { import TextInput from '../presentation/TextInput';
COLOR_DANGER, COLOR_TEXT_DESCRIPTION, COLOR_TEXT, COLOR_BORDER import { themes } from '../constants/colors';
} from '../constants/colors';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
error: {
textAlign: 'center',
paddingTop: 5
},
inputContainer: { inputContainer: {
marginBottom: 10 marginBottom: 10
}, },
label: { label: {
marginBottom: 10, marginBottom: 10,
fontSize: 14, fontSize: 14,
...sharedStyles.textSemibold, ...sharedStyles.textSemibold
...sharedStyles.textColorNormal
}, },
input: { input: {
...sharedStyles.textRegular, ...sharedStyles.textRegular,
...sharedStyles.textColorNormal,
height: 48, height: 48,
fontSize: 16, fontSize: 16,
paddingLeft: 14, paddingLeft: 14,
paddingRight: 14, paddingRight: 14,
borderWidth: 1, borderWidth: StyleSheet.hairlineWidth,
borderRadius: 2, borderRadius: 2
backgroundColor: 'white',
borderColor: COLOR_BORDER
}, },
inputIconLeft: { inputIconLeft: {
paddingLeft: 45 paddingLeft: 45
@ -39,13 +36,6 @@ const styles = StyleSheet.create({
inputIconRight: { inputIconRight: {
paddingRight: 45 paddingRight: 45
}, },
labelError: {
color: COLOR_DANGER
},
inputError: {
color: COLOR_DANGER,
borderColor: COLOR_DANGER
},
wrap: { wrap: {
position: 'relative' position: 'relative'
}, },
@ -58,12 +48,6 @@ const styles = StyleSheet.create({
}, },
iconRight: { iconRight: {
right: 15 right: 15
},
icon: {
color: COLOR_TEXT
},
password: {
color: COLOR_TEXT_DESCRIPTION
} }
}); });
@ -78,11 +62,13 @@ export default class RCTextInput extends React.PureComponent {
inputRef: PropTypes.func, inputRef: PropTypes.func,
testID: PropTypes.string, testID: PropTypes.string,
iconLeft: PropTypes.string, iconLeft: PropTypes.string,
placeholder: PropTypes.string placeholder: PropTypes.string,
theme: PropTypes.string
} }
static defaultProps = { static defaultProps = {
error: {} error: {},
theme: 'light'
} }
state = { state = {
@ -90,12 +76,12 @@ export default class RCTextInput extends React.PureComponent {
} }
get iconLeft() { get iconLeft() {
const { testID, iconLeft } = this.props; const { testID, iconLeft, theme } = this.props;
return ( return (
<CustomIcon <CustomIcon
name={iconLeft} name={iconLeft}
testID={testID ? `${ testID }-icon-left` : null} testID={testID ? `${ testID }-icon-left` : null}
style={[styles.iconContainer, styles.iconLeft, styles.icon]} style={[styles.iconContainer, styles.iconLeft, { color: themes[theme].bodyText }]}
size={20} size={20}
/> />
); );
@ -103,13 +89,13 @@ export default class RCTextInput extends React.PureComponent {
get iconPassword() { get iconPassword() {
const { showPassword } = this.state; const { showPassword } = this.state;
const { testID } = this.props; const { testID, theme } = this.props;
return ( return (
<BorderlessButton onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}> <BorderlessButton onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}>
<CustomIcon <CustomIcon
name={showPassword ? 'Eye' : 'eye-off'} name={showPassword ? 'Eye' : 'eye-off'}
testID={testID ? `${ testID }-icon-right` : null} testID={testID ? `${ testID }-icon-right` : null}
style={[styles.icon, styles.password]} style={{ color: themes[theme].auxiliaryText }}
size={20} size={20}
/> />
</BorderlessButton> </BorderlessButton>
@ -123,19 +109,40 @@ export default class RCTextInput extends React.PureComponent {
render() { render() {
const { showPassword } = this.state; const { showPassword } = this.state;
const { const {
label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, ...inputProps label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
} = this.props; } = this.props;
const { dangerColor } = themes[theme];
return ( return (
<View style={[styles.inputContainer, containerStyle]}> <View style={[styles.inputContainer, containerStyle]}>
{label ? <Text contentDescription={null} accessibilityLabel={null} style={[styles.label, error.error && styles.labelError]}>{label}</Text> : null} {label ? (
<Text
contentDescription={null}
accessibilityLabel={null}
style={[
styles.label,
{ color: themes[theme].titleText },
error.error && { color: dangerColor }
]}
>
{label}
</Text>
) : null}
<View style={styles.wrap}> <View style={styles.wrap}>
<TextInput <TextInput
style={[ style={[
styles.input, styles.input,
error.error && styles.inputError, error.error && {
inputStyle, color: dangerColor,
borderColor: dangerColor
},
iconLeft && styles.inputIconLeft, iconLeft && styles.inputIconLeft,
secureTextEntry && styles.inputIconRight secureTextEntry && styles.inputIconRight,
{
backgroundColor: themes[theme].backgroundColor,
borderColor: themes[theme].separatorColor,
color: themes[theme].titleText
},
inputStyle
]} ]}
ref={inputRef} ref={inputRef}
autoCorrect={false} autoCorrect={false}
@ -145,14 +152,14 @@ export default class RCTextInput extends React.PureComponent {
testID={testID} testID={testID}
accessibilityLabel={placeholder} accessibilityLabel={placeholder}
placeholder={placeholder} placeholder={placeholder}
placeholderTextColor={COLOR_TEXT_DESCRIPTION}
contentDescription={placeholder} contentDescription={placeholder}
theme={theme}
{...inputProps} {...inputProps}
/> />
{iconLeft ? this.iconLeft : null} {iconLeft ? this.iconLeft : null}
{secureTextEntry ? this.iconPassword : null} {secureTextEntry ? this.iconPassword : null}
</View> </View>
{error.error ? <Text style={sharedStyles.error}>{error.reason}</Text> : null} {error.error ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null}
</View> </View>
); );
} }

View File

@ -1,20 +1,20 @@
import React from 'react'; import React from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import EasyToast from 'react-native-easy-toast'; import EasyToast from 'react-native-easy-toast';
import PropTypes from 'prop-types';
import { COLOR_TOAST, COLOR_WHITE } from '../constants/colors'; import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import { withTheme } from '../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
toast: { toast: {
backgroundColor: COLOR_TOAST,
maxWidth: 300, maxWidth: 300,
padding: 10 padding: 10
}, },
text: { text: {
...sharedStyles.textRegular, ...sharedStyles.textRegular,
color: COLOR_WHITE,
fontSize: 14, fontSize: 14,
textAlign: 'center' textAlign: 'center'
} }
@ -22,12 +22,20 @@ const styles = StyleSheet.create({
export const LISTENER = 'Toast'; export const LISTENER = 'Toast';
export default class Toast extends React.Component { class Toast extends React.Component {
static propTypes = {
theme: PropTypes.string
}
componentDidMount() { componentDidMount() {
EventEmitter.addEventListener(LISTENER, this.showToast); EventEmitter.addEventListener(LISTENER, this.showToast);
} }
shouldComponentUpdate() { shouldComponentUpdate(nextProps) {
const { theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
return false; return false;
} }
@ -40,14 +48,17 @@ export default class Toast extends React.Component {
} }
render() { render() {
const { theme } = this.props;
return ( return (
<EasyToast <EasyToast
ref={toast => this.toast = toast} ref={toast => this.toast = toast}
position='center' position='center'
style={styles.toast} style={[styles.toast, { backgroundColor: themes[theme].toastBackground }]}
textStyle={styles.text} textStyle={[styles.text, { color: themes[theme].buttonText }]}
opacity={0.9} opacity={0.9}
/> />
); );
} }
} }
export default withTheme(Toast);

View File

@ -2,12 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Text } from 'react-native'; import { Text } from 'react-native';
import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
const AtMention = React.memo(({ const AtMention = React.memo(({
mention, mentions, username, navToRoomInfo, preview, style = [] mention, mentions, username, navToRoomInfo, preview, style = [], theme
}) => { }) => {
let mentionStyle = styles.mention; let mentionStyle = { ...styles.mention, color: themes[theme].buttonText };
if (mention === 'all' || mention === 'here') { if (mention === 'all' || mention === 'here') {
mentionStyle = { mentionStyle = {
...mentionStyle, ...mentionStyle,
@ -16,7 +18,12 @@ const AtMention = React.memo(({
} else if (mention === username) { } else if (mention === username) {
mentionStyle = { mentionStyle = {
...mentionStyle, ...mentionStyle,
...styles.mentionLoggedUser backgroundColor: themes[theme].actionTintColor
};
} else {
mentionStyle = {
...mentionStyle,
color: themes[theme].actionTintColor
}; };
} }
@ -33,7 +40,7 @@ const AtMention = React.memo(({
return ( return (
<Text <Text
style={[preview ? styles.text : mentionStyle, ...style]} style={[preview ? { ...styles.text, color: themes[theme].titleText } : mentionStyle, ...style]}
onPress={preview ? undefined : handlePress} onPress={preview ? undefined : handlePress}
> >
{`@${ mention }`} {`@${ mention }`}
@ -47,6 +54,7 @@ AtMention.propTypes = {
navToRoomInfo: PropTypes.func, navToRoomInfo: PropTypes.func,
style: PropTypes.array, style: PropTypes.array,
preview: PropTypes.bool, preview: PropTypes.bool,
theme: PropTypes.string,
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
}; };

View File

@ -2,11 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View } from 'react-native'; import { View } from 'react-native';
import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
const BlockQuote = React.memo(({ children }) => ( const BlockQuote = React.memo(({ children, theme }) => (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.quote} /> <View style={[styles.quote, { backgroundColor: themes[theme].borderColor }]} />
<View style={styles.childContainer}> <View style={styles.childContainer}>
{children} {children}
</View> </View>
@ -14,7 +16,8 @@ const BlockQuote = React.memo(({ children }) => (
)); ));
BlockQuote.propTypes = { BlockQuote.propTypes = {
children: PropTypes.node.isRequired children: PropTypes.node.isRequired,
theme: PropTypes.string
}; };
export default BlockQuote; export default BlockQuote;

View File

@ -4,11 +4,12 @@ import { Text } from 'react-native';
import { shortnameToUnicode } from 'emoji-toolkit'; import { shortnameToUnicode } from 'emoji-toolkit';
import CustomEmoji from '../EmojiPicker/CustomEmoji'; import CustomEmoji from '../EmojiPicker/CustomEmoji';
import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
const Emoji = React.memo(({ const Emoji = React.memo(({
emojiName, literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis, style = [] emojiName, literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis, style = [], theme
}) => { }) => {
const emojiUnicode = shortnameToUnicode(literal); const emojiUnicode = shortnameToUnicode(literal);
const emoji = getCustomEmoji && getCustomEmoji(emojiName); const emoji = getCustomEmoji && getCustomEmoji(emojiName);
@ -24,6 +25,7 @@ const Emoji = React.memo(({
return ( return (
<Text <Text
style={[ style={[
{ color: themes[theme].titleText },
isMessageContainsOnlyEmoji ? styles.textBig : styles.text, isMessageContainsOnlyEmoji ? styles.textBig : styles.text,
...style ...style
]} ]}
@ -40,7 +42,8 @@ Emoji.propTypes = {
getCustomEmoji: PropTypes.func, getCustomEmoji: PropTypes.func,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
customEmojis: PropTypes.bool, customEmojis: PropTypes.bool,
style: PropTypes.array style: PropTypes.array,
theme: PropTypes.string
}; };
export default Emoji; export default Emoji;

View File

@ -2,10 +2,12 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { Text } from 'react-native'; import { Text } from 'react-native';
import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
const Hashtag = React.memo(({ const Hashtag = React.memo(({
hashtag, channels, navToRoomInfo, preview, style = [] hashtag, channels, navToRoomInfo, preview, style = [], theme
}) => { }) => {
const handlePress = () => { const handlePress = () => {
const index = channels.findIndex(channel => channel.name === hashtag); const index = channels.findIndex(channel => channel.name === hashtag);
@ -19,14 +21,18 @@ const Hashtag = React.memo(({
if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) { if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) {
return ( return (
<Text <Text
style={[preview ? styles.text : styles.mention, ...style]} style={[preview ? { ...styles.text, color: themes[theme].titleText } : styles.mention, ...style]}
onPress={preview ? undefined : handlePress} onPress={preview ? undefined : handlePress}
> >
{`#${ hashtag }`} {`#${ hashtag }`}
</Text> </Text>
); );
} }
return `#${ hashtag }`; return (
<Text style={[preview ? { ...styles.text, color: themes[theme].titleText } : styles.mention, ...style]}>
{`#${ hashtag }`}
</Text>
);
}); });
Hashtag.propTypes = { Hashtag.propTypes = {
@ -34,6 +40,7 @@ Hashtag.propTypes = {
navToRoomInfo: PropTypes.func, navToRoomInfo: PropTypes.func,
style: PropTypes.array, style: PropTypes.array,
preview: PropTypes.bool, preview: PropTypes.bool,
theme: PropTypes.string,
channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
}; };

View File

@ -3,16 +3,17 @@ import PropTypes from 'prop-types';
import { Text } from 'react-native'; import { Text } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors';
import openLink from '../../utils/openLink'; import openLink from '../../utils/openLink';
const Link = React.memo(({ const Link = React.memo(({
children, link, preview children, link, preview, theme
}) => { }) => {
const handlePress = () => { const handlePress = () => {
if (!link) { if (!link) {
return; return;
} }
openLink(link); openLink(link, theme);
}; };
const childLength = React.Children.toArray(children).filter(o => o).length; const childLength = React.Children.toArray(children).filter(o => o).length;
@ -21,7 +22,11 @@ const Link = React.memo(({
return ( return (
<Text <Text
onPress={preview ? undefined : handlePress} onPress={preview ? undefined : handlePress}
style={styles.link} style={
!preview
? { ...styles.link, color: themes[theme].actionTintColor }
: { color: themes[theme].titleText }
}
> >
{ childLength !== 0 ? children : link } { childLength !== 0 ? children : link }
</Text> </Text>
@ -31,6 +36,7 @@ const Link = React.memo(({
Link.propTypes = { Link.propTypes = {
children: PropTypes.node, children: PropTypes.node,
link: PropTypes.string, link: PropTypes.string,
theme: PropTypes.string,
preview: PropTypes.bool preview: PropTypes.bool
}; };

View File

@ -6,6 +6,8 @@ import {
View View
} from 'react-native'; } from 'react-native';
import { themes } from '../../constants/colors';
const style = StyleSheet.create({ const style = StyleSheet.create({
container: { container: {
flexDirection: 'row', flexDirection: 'row',
@ -21,7 +23,7 @@ const style = StyleSheet.create({
}); });
const ListItem = React.memo(({ const ListItem = React.memo(({
children, level, bulletWidth, continue: _continue, ordered, index children, level, bulletWidth, continue: _continue, ordered, index, theme
}) => { }) => {
let bullet; let bullet;
if (_continue) { if (_continue) {
@ -37,7 +39,7 @@ const ListItem = React.memo(({
return ( return (
<View style={style.container}> <View style={style.container}>
<View style={[{ width: bulletWidth }, style.bullet]}> <View style={[{ width: bulletWidth }, style.bullet]}>
<Text> <Text style={{ color: themes[theme].titleText }}>
{bullet} {bullet}
</Text> </Text>
</View> </View>
@ -54,6 +56,7 @@ ListItem.propTypes = {
level: PropTypes.number, level: PropTypes.number,
ordered: PropTypes.bool, ordered: PropTypes.bool,
continue: PropTypes.bool, continue: PropTypes.bool,
theme: PropTypes.string,
index: PropTypes.number index: PropTypes.number
}; };

View File

@ -11,16 +11,17 @@ import { CELL_WIDTH } from './TableCell';
import styles from './styles'; import styles from './styles';
import Navigation from '../../lib/Navigation'; import Navigation from '../../lib/Navigation';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { themes } from '../../constants/colors';
const MAX_HEIGHT = 300; const MAX_HEIGHT = 300;
const Table = React.memo(({ const Table = React.memo(({
children, numColumns children, numColumns, theme
}) => { }) => {
const getTableWidth = () => numColumns * CELL_WIDTH; const getTableWidth = () => numColumns * CELL_WIDTH;
const renderRows = (drawExtraBorders = true) => { const renderRows = (drawExtraBorders = true) => {
const tableStyle = [styles.table]; const tableStyle = [styles.table, { borderColor: themes[theme].borderColor }];
if (drawExtraBorders) { if (drawExtraBorders) {
tableStyle.push(styles.tableExtraBorders); tableStyle.push(styles.tableExtraBorders);
} }
@ -45,18 +46,19 @@ const Table = React.memo(({
contentContainerStyle={{ width: getTableWidth() }} contentContainerStyle={{ width: getTableWidth() }}
scrollEnabled={false} scrollEnabled={false}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
style={[styles.containerTable, { maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT }]} style={[styles.containerTable, { maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT, borderColor: themes[theme].borderColor }]}
> >
{renderRows(false)} {renderRows(false)}
</ScrollView> </ScrollView>
<Text style={styles.textInfo}>{I18n.t('Full_table')}</Text> <Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]}>{I18n.t('Full_table')}</Text>
</TouchableOpacity> </TouchableOpacity>
); );
}); });
Table.propTypes = { Table.propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
numColumns: PropTypes.number.isRequired numColumns: PropTypes.number.isRequired,
theme: PropTypes.string
}; };
export default Table; export default Table;

View File

@ -2,14 +2,16 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
export const CELL_WIDTH = 100; export const CELL_WIDTH = 100;
const TableCell = React.memo(({ const TableCell = React.memo(({
isLastCell, align, children isLastCell, align, children, theme
}) => { }) => {
const cellStyle = [styles.cell]; const cellStyle = [styles.cell, { borderColor: themes[theme].borderColor }];
if (!isLastCell) { if (!isLastCell) {
cellStyle.push(styles.cellRightBorder); cellStyle.push(styles.cellRightBorder);
} }
@ -23,7 +25,7 @@ const TableCell = React.memo(({
return ( return (
<View style={[...cellStyle, { width: CELL_WIDTH }]}> <View style={[...cellStyle, { width: CELL_WIDTH }]}>
<Text style={textStyle}> <Text style={[textStyle, { color: themes[theme].titleText }]}>
{children} {children}
</Text> </Text>
</View> </View>
@ -33,7 +35,8 @@ const TableCell = React.memo(({
TableCell.propTypes = { TableCell.propTypes = {
align: PropTypes.oneOf(['', 'left', 'center', 'right']), align: PropTypes.oneOf(['', 'left', 'center', 'right']),
children: PropTypes.node, children: PropTypes.node,
isLastCell: PropTypes.bool isLastCell: PropTypes.bool,
theme: PropTypes.string
}; };
export default TableCell; export default TableCell;

View File

@ -2,12 +2,14 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
const TableRow = React.memo(({ const TableRow = React.memo(({
isLastRow, children: _children isLastRow, children: _children, theme
}) => { }) => {
const rowStyle = [styles.row]; const rowStyle = [styles.row, { borderColor: themes[theme].borderColor }];
if (!isLastRow) { if (!isLastRow) {
rowStyle.push(styles.rowBottomBorder); rowStyle.push(styles.rowBottomBorder);
} }
@ -22,7 +24,8 @@ const TableRow = React.memo(({
TableRow.propTypes = { TableRow.propTypes = {
children: PropTypes.node, children: PropTypes.node,
isLastRow: PropTypes.bool isLastRow: PropTypes.bool,
theme: PropTypes.string
}; };
export default TableRow; export default TableRow;

View File

@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import { shortnameToUnicode } from 'emoji-toolkit'; import { shortnameToUnicode } from 'emoji-toolkit';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { themes } from '../../constants/colors';
import MarkdownLink from './Link'; import MarkdownLink from './Link';
import MarkdownList from './List'; import MarkdownList from './List';
@ -59,7 +60,7 @@ const emojiCount = (str) => {
return counter; return counter;
}; };
export default class Markdown extends PureComponent { class Markdown extends PureComponent {
static propTypes = { static propTypes = {
msg: PropTypes.string, msg: PropTypes.string,
getCustomEmoji: PropTypes.func, getCustomEmoji: PropTypes.func,
@ -74,6 +75,7 @@ export default class Markdown extends PureComponent {
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
navToRoomInfo: PropTypes.func, navToRoomInfo: PropTypes.func,
preview: PropTypes.bool, preview: PropTypes.bool,
theme: PropTypes.string,
style: PropTypes.array style: PropTypes.array
}; };
@ -140,7 +142,9 @@ export default class Markdown extends PureComponent {
}; };
renderText = ({ context, literal }) => { renderText = ({ context, literal }) => {
const { numberOfLines, preview, style = [] } = this.props; const {
numberOfLines, preview, style = []
} = this.props;
const defaultStyle = [ const defaultStyle = [
this.isMessageContainsOnlyEmoji && !preview ? styles.textBig : {}, this.isMessageContainsOnlyEmoji && !preview ? styles.textBig : {},
...context.map(type => styles[type]) ...context.map(type => styles[type])
@ -160,13 +164,45 @@ export default class Markdown extends PureComponent {
} }
renderCodeInline = ({ literal }) => { renderCodeInline = ({ literal }) => {
const { preview } = this.props; const { preview, theme, style = [] } = this.props;
return <Text style={!preview ? styles.codeInline : {}}>{literal}</Text>; return (
<Text
style={[
!preview
? {
...styles.codeInline,
color: themes[theme].titleText,
backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme].bannerBackground
}
: { ...styles.text, color: themes[theme].titleText },
...style
]}
>
{literal}
</Text>
);
}; };
renderCodeBlock = ({ literal }) => { renderCodeBlock = ({ literal }) => {
const { preview } = this.props; const { preview, theme, style = [] } = this.props;
return <Text style={!preview ? styles.codeBlock : {}}>{literal}</Text>; return (
<Text
style={[
!preview
? {
...styles.codeBlock,
color: themes[theme].titleText,
backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme].bannerBackground
}
: { ...styles.text, color: themes[theme].titleText },
...style
]}
>
{literal}
</Text>
);
}; };
renderBreak = () => { renderBreak = () => {
@ -175,21 +211,25 @@ export default class Markdown extends PureComponent {
} }
renderParagraph = ({ children }) => { renderParagraph = ({ children }) => {
const { numberOfLines, style } = this.props; const { numberOfLines, style, theme } = this.props;
if (!children || children.length === 0) { if (!children || children.length === 0) {
return null; return null;
} }
return ( return (
<Text style={style} numberOfLines={numberOfLines}> <Text style={[style, { color: themes[theme].titleText }]} numberOfLines={numberOfLines}>
{children} {children}
</Text> </Text>
); );
}; };
renderLink = ({ children, href }) => { renderLink = ({ children, href }) => {
const { preview } = this.props; const { preview, theme } = this.props;
return ( return (
<MarkdownLink link={href} preview={preview}> <MarkdownLink
link={href}
preview={preview}
theme={theme}
>
{children} {children}
</MarkdownLink> </MarkdownLink>
); );
@ -197,7 +237,7 @@ export default class Markdown extends PureComponent {
renderHashtag = ({ hashtag }) => { renderHashtag = ({ hashtag }) => {
const { const {
channels, navToRoomInfo, style, preview channels, navToRoomInfo, style, preview, theme
} = this.props; } = this.props;
return ( return (
<MarkdownHashtag <MarkdownHashtag
@ -205,6 +245,7 @@ export default class Markdown extends PureComponent {
channels={channels} channels={channels}
navToRoomInfo={navToRoomInfo} navToRoomInfo={navToRoomInfo}
preview={preview} preview={preview}
theme={theme}
style={style} style={style}
/> />
); );
@ -212,7 +253,7 @@ export default class Markdown extends PureComponent {
renderAtMention = ({ mentionName }) => { renderAtMention = ({ mentionName }) => {
const { const {
username, mentions, navToRoomInfo, preview, style username, mentions, navToRoomInfo, preview, style, theme
} = this.props; } = this.props;
return ( return (
<MarkdownAtMention <MarkdownAtMention
@ -221,6 +262,7 @@ export default class Markdown extends PureComponent {
username={username} username={username}
navToRoomInfo={navToRoomInfo} navToRoomInfo={navToRoomInfo}
preview={preview} preview={preview}
theme={theme}
style={style} style={style}
/> />
); );
@ -228,7 +270,7 @@ export default class Markdown extends PureComponent {
renderEmoji = ({ emojiName, literal }) => { renderEmoji = ({ emojiName, literal }) => {
const { const {
getCustomEmoji, baseUrl, customEmojis = true, preview, style getCustomEmoji, baseUrl, customEmojis = true, preview, style, theme
} = this.props; } = this.props;
return ( return (
<MarkdownEmoji <MarkdownEmoji
@ -239,19 +281,23 @@ export default class Markdown extends PureComponent {
baseUrl={baseUrl} baseUrl={baseUrl}
customEmojis={customEmojis} customEmojis={customEmojis}
style={style} style={style}
theme={theme}
/> />
); );
} }
renderImage = ({ src }) => <Image style={styles.inlineImage} source={{ uri: src }} />; renderImage = ({ src }) => <Image style={styles.inlineImage} source={{ uri: src }} />;
renderEditedIndicator = () => <Text style={styles.edited}> ({I18n.t('edited')})</Text>; renderEditedIndicator = () => {
const { theme } = this.props;
return <Text style={[styles.edited, { color: themes[theme].auxiliaryText }]}> ({I18n.t('edited')})</Text>;
}
renderHeading = ({ children, level }) => { renderHeading = ({ children, level }) => {
const { numberOfLines } = this.props; const { numberOfLines, theme } = this.props;
const textStyle = styles[`heading${ level }Text`]; const textStyle = styles[`heading${ level }Text`];
return ( return (
<Text numberOfLines={numberOfLines} style={textStyle}> <Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].titleText }]}>
{children} {children}
</Text> </Text>
); );
@ -276,11 +322,13 @@ export default class Markdown extends PureComponent {
renderListItem = ({ renderListItem = ({
children, context, ...otherProps children, context, ...otherProps
}) => { }) => {
const { theme } = this.props;
const level = context.filter(type => type === 'list').length; const level = context.filter(type => type === 'list').length;
return ( return (
<MarkdownListItem <MarkdownListItem
level={level} level={level}
theme={theme}
{...otherProps} {...otherProps}
> >
{children} {children}
@ -289,30 +337,39 @@ export default class Markdown extends PureComponent {
}; };
renderBlockQuote = ({ children }) => { renderBlockQuote = ({ children }) => {
const { preview } = this.props; const { preview, theme } = this.props;
if (preview) { if (preview) {
return children; return children;
} }
return ( return (
<MarkdownBlockQuote> <MarkdownBlockQuote theme={theme}>
{children} {children}
</MarkdownBlockQuote> </MarkdownBlockQuote>
); );
} }
renderTable = ({ children, numColumns }) => ( renderTable = ({ children, numColumns }) => {
<MarkdownTable numColumns={numColumns}> const { theme } = this.props;
return (
<MarkdownTable numColumns={numColumns} theme={theme}>
{children} {children}
</MarkdownTable> </MarkdownTable>
); );
}
renderTableRow = args => <MarkdownTableRow {...args} />; renderTableRow = (args) => {
const { theme } = this.props;
return <MarkdownTableRow {...args} theme={theme} />;
}
renderTableCell = args => <MarkdownTableCell {...args} />; renderTableCell = (args) => {
const { theme } = this.props;
return <MarkdownTableCell {...args} theme={theme} />;
}
render() { render() {
const { const {
msg, useMarkdown = true, numberOfLines, preview = false msg, useMarkdown = true, numberOfLines, preview = false, theme
} = this.props; } = this.props;
if (!msg) { if (!msg) {
@ -333,7 +390,7 @@ export default class Markdown extends PureComponent {
} }
if (!useMarkdown && !preview) { if (!useMarkdown && !preview) {
return <Text style={styles.text} numberOfLines={numberOfLines}>{m}</Text>; return <Text style={[styles.text, { color: themes[theme].titleText }]} numberOfLines={numberOfLines}>{m}</Text>;
} }
const ast = this.parser.parse(m); const ast = this.parser.parse(m);
@ -344,3 +401,5 @@ export default class Markdown extends PureComponent {
return this.renderer.render(ast); return this.renderer.render(ast);
} }
} }
export default Markdown;

View File

@ -1,9 +1,6 @@
import { StyleSheet, Platform } from 'react-native'; import { StyleSheet, Platform } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import {
COLOR_BORDER, COLOR_PRIMARY, COLOR_WHITE, COLOR_BACKGROUND_CONTAINER
} from '../../constants/colors';
const codeFontFamily = Platform.select({ const codeFontFamily = Platform.select({
ios: { fontFamily: 'Courier New' }, ios: { fontFamily: 'Courier New' },
@ -35,18 +32,15 @@ export default StyleSheet.create({
}, },
text: { text: {
fontSize: 16, fontSize: 16,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
textInfo: { textInfo: {
fontStyle: 'italic', fontStyle: 'italic',
fontSize: 16, fontSize: 16,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
textBig: { textBig: {
fontSize: 30, fontSize: 30,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
customEmoji: { customEmoji: {
@ -65,12 +59,7 @@ export default StyleSheet.create({
...sharedStyles.textMedium, ...sharedStyles.textMedium,
backgroundColor: '#E8F2FF' backgroundColor: '#E8F2FF'
}, },
mentionLoggedUser: {
color: COLOR_WHITE,
backgroundColor: COLOR_PRIMARY
},
mentionAll: { mentionAll: {
color: COLOR_WHITE,
backgroundColor: '#FF5B5A' backgroundColor: '#FF5B5A'
}, },
paragraph: { paragraph: {
@ -90,26 +79,21 @@ export default StyleSheet.create({
...sharedStyles.textRegular, ...sharedStyles.textRegular,
...codeFontFamily, ...codeFontFamily,
borderWidth: 1, borderWidth: 1,
backgroundColor: COLOR_BACKGROUND_CONTAINER,
borderRadius: 4 borderRadius: 4
}, },
codeBlock: { codeBlock: {
...sharedStyles.textRegular, ...sharedStyles.textRegular,
...codeFontFamily, ...codeFontFamily,
backgroundColor: COLOR_BACKGROUND_CONTAINER,
borderColor: COLOR_BORDER,
borderWidth: 1, borderWidth: 1,
borderRadius: 4, borderRadius: 4,
padding: 4 padding: 4
}, },
link: { link: {
fontSize: 16, fontSize: 16,
color: COLOR_PRIMARY,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
edited: { edited: {
fontSize: 14, fontSize: 14,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
heading1: { heading1: {
@ -139,7 +123,6 @@ export default StyleSheet.create({
quote: { quote: {
height: '100%', height: '100%',
width: 2, width: 2,
backgroundColor: COLOR_BORDER,
marginRight: 5 marginRight: 5
}, },
touchableTable: { touchableTable: {
@ -147,11 +130,9 @@ export default StyleSheet.create({
}, },
containerTable: { containerTable: {
borderBottomWidth: 1, borderBottomWidth: 1,
borderColor: COLOR_BORDER,
borderRightWidth: 1 borderRightWidth: 1
}, },
table: { table: {
borderColor: COLOR_BORDER,
borderLeftWidth: 1, borderLeftWidth: 1,
borderTopWidth: 1 borderTopWidth: 1
}, },
@ -163,11 +144,9 @@ export default StyleSheet.create({
flexDirection: 'row' flexDirection: 'row'
}, },
rowBottomBorder: { rowBottomBorder: {
borderColor: COLOR_BORDER,
borderBottomWidth: 1 borderBottomWidth: 1
}, },
cell: { cell: {
borderColor: COLOR_BORDER,
justifyContent: 'flex-start', justifyContent: 'flex-start',
paddingHorizontal: 13, paddingHorizontal: 13,
paddingVertical: 6 paddingVertical: 6

View File

@ -8,7 +8,7 @@ import Video from './Video';
import Reply from './Reply'; import Reply from './Reply';
const Attachments = React.memo(({ const Attachments = React.memo(({
attachments, timeFormat, user, baseUrl, useMarkdown, onOpenFileModal, getCustomEmoji attachments, timeFormat, user, baseUrl, useMarkdown, onOpenFileModal, getCustomEmoji, theme
}) => { }) => {
if (!attachments || attachments.length === 0) { if (!attachments || attachments.length === 0) {
return null; return null;
@ -16,19 +16,19 @@ const Attachments = React.memo(({
return attachments.map((file, index) => { return attachments.map((file, index) => {
if (file.image_url) { if (file.image_url) {
return <Image key={file.image_url} file={file} user={user} baseUrl={baseUrl} onOpenFileModal={onOpenFileModal} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} />; return <Image key={file.image_url} file={file} user={user} baseUrl={baseUrl} onOpenFileModal={onOpenFileModal} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
} }
if (file.audio_url) { if (file.audio_url) {
return <Audio key={file.audio_url} file={file} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} />; return <Audio key={file.audio_url} file={file} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
} }
if (file.video_url) { if (file.video_url) {
return <Video key={file.video_url} file={file} user={user} baseUrl={baseUrl} onOpenFileModal={onOpenFileModal} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} />; return <Video key={file.video_url} file={file} user={user} baseUrl={baseUrl} onOpenFileModal={onOpenFileModal} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
} }
// eslint-disable-next-line react/no-array-index-key // eslint-disable-next-line react/no-array-index-key
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} />; return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
}); });
}, (prevProps, nextProps) => isEqual(prevProps.attachments, nextProps.attachments)); }, (prevProps, nextProps) => isEqual(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme);
Attachments.propTypes = { Attachments.propTypes = {
attachments: PropTypes.array, attachments: PropTypes.array,
@ -37,7 +37,8 @@ Attachments.propTypes = {
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
useMarkdown: PropTypes.bool, useMarkdown: PropTypes.bool,
onOpenFileModal: PropTypes.func, onOpenFileModal: PropTypes.func,
getCustomEmoji: PropTypes.func getCustomEmoji: PropTypes.func,
theme: PropTypes.string
}; };
Attachments.displayName = 'MessageAttachments'; Attachments.displayName = 'MessageAttachments';

View File

@ -12,7 +12,7 @@ import Touchable from 'react-native-platform-touchable';
import Markdown from '../markdown'; import Markdown from '../markdown';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER, COLOR_PRIMARY } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { isAndroid, isIOS } from '../../utils/deviceInfo'; import { isAndroid, isIOS } from '../../utils/deviceInfo';
import { withSplit } from '../../split'; import { withSplit } from '../../split';
@ -22,8 +22,6 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
height: 56, height: 56,
backgroundColor: COLOR_BACKGROUND_CONTAINER,
borderColor: COLOR_BORDER,
borderWidth: 1, borderWidth: 1,
borderRadius: 4, borderRadius: 4,
marginBottom: 6 marginBottom: 6
@ -33,16 +31,12 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
backgroundColor: 'transparent' backgroundColor: 'transparent'
}, },
playPauseImage: {
color: COLOR_PRIMARY
},
slider: { slider: {
flex: 1 flex: 1
}, },
duration: { duration: {
marginHorizontal: 12, marginHorizontal: 12,
fontSize: 14, fontSize: 14,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular ...sharedStyles.textRegular
} }
}); });
@ -57,19 +51,20 @@ const sliderAnimationConfig = {
delay: 0 delay: 0
}; };
const Button = React.memo(({ paused, onPress }) => ( const Button = React.memo(({ paused, onPress, theme }) => (
<Touchable <Touchable
style={styles.playPauseButton} style={styles.playPauseButton}
onPress={onPress} onPress={onPress}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
background={Touchable.SelectableBackgroundBorderless()} background={Touchable.SelectableBackgroundBorderless()}
> >
<CustomIcon name={paused ? 'play' : 'pause'} size={36} style={styles.playPauseImage} /> <CustomIcon name={paused ? 'play' : 'pause'} size={36} color={themes[theme].tintColor} />
</Touchable> </Touchable>
)); ));
Button.propTypes = { Button.propTypes = {
paused: PropTypes.bool, paused: PropTypes.bool,
theme: PropTypes.string,
onPress: PropTypes.func onPress: PropTypes.func
}; };
Button.displayName = 'MessageAudioButton'; Button.displayName = 'MessageAudioButton';
@ -80,6 +75,7 @@ class Audio extends React.Component {
baseUrl: PropTypes.string.isRequired, baseUrl: PropTypes.string.isRequired,
user: PropTypes.object.isRequired, user: PropTypes.object.isRequired,
useMarkdown: PropTypes.bool, useMarkdown: PropTypes.bool,
theme: PropTypes.string,
split: PropTypes.bool, split: PropTypes.bool,
getCustomEmoji: PropTypes.func getCustomEmoji: PropTypes.func
} }
@ -99,7 +95,10 @@ class Audio extends React.Component {
const { const {
currentTime, duration, paused, uri currentTime, duration, paused, uri
} = this.state; } = this.state;
const { file, split } = this.props; const { file, split, theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextState.currentTime !== currentTime) { if (nextState.currentTime !== currentTime) {
return true; return true;
} }
@ -158,7 +157,7 @@ class Audio extends React.Component {
uri, paused, currentTime, duration uri, paused, currentTime, duration
} = this.state; } = this.state;
const { const {
user, baseUrl, file, getCustomEmoji, useMarkdown, split user, baseUrl, file, getCustomEmoji, useMarkdown, split, theme
} = this.props; } = this.props;
const { description } = file; const { description } = file;
@ -168,7 +167,13 @@ class Audio extends React.Component {
return ( return (
<> <>
<View style={[styles.audioContainer, split && sharedStyles.tabletContent]}> <View
style={[
styles.audioContainer,
{ backgroundColor: themes[theme].chatComponentBackground, borderColor: themes[theme].borderColor },
split && sharedStyles.tabletContent
]}
>
<Video <Video
ref={this.setRef} ref={this.setRef}
source={{ uri }} source={{ uri }}
@ -178,7 +183,7 @@ class Audio extends React.Component {
paused={paused} paused={paused}
repeat={false} repeat={false}
/> />
<Button paused={paused} onPress={this.togglePlayPause} /> <Button paused={paused} onPress={this.togglePlayPause} theme={theme} />
<Slider <Slider
style={styles.slider} style={styles.slider}
value={currentTime} value={currentTime}
@ -186,14 +191,15 @@ class Audio extends React.Component {
minimumValue={0} minimumValue={0}
animateTransitions animateTransitions
animationConfig={sliderAnimationConfig} animationConfig={sliderAnimationConfig}
thumbTintColor={isAndroid && COLOR_PRIMARY} thumbTintColor={isAndroid && themes[theme].tintColor}
minimumTrackTintColor={COLOR_PRIMARY} minimumTrackTintColor={themes[theme].tintColor}
maximumTrackTintColor={themes[theme].auxiliaryText}
onValueChange={this.onValueChange} onValueChange={this.onValueChange}
thumbImage={isIOS && { uri: 'audio_thumb', scale: Dimensions.get('window').scale }} thumbImage={isIOS && { uri: 'audio_thumb', scale: Dimensions.get('window').scale }}
/> />
<Text style={styles.duration}>{this.duration}</Text> <Text style={[styles.duration, { color: themes[theme].auxiliaryText }]}>{this.duration}</Text>
</View> </View>
<Markdown msg={description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} /> <Markdown msg={description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />
</> </>
); );
} }

View File

@ -7,9 +7,10 @@ import { CustomIcon } from '../../lib/Icons';
import styles from './styles'; import styles from './styles';
import { BUTTON_HIT_SLOP } from './utils'; import { BUTTON_HIT_SLOP } from './utils';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { themes } from '../../constants/colors';
const Broadcast = React.memo(({ const Broadcast = React.memo(({
author, user, broadcast, replyBroadcast author, user, broadcast, replyBroadcast, theme
}) => { }) => {
const isOwn = author._id === user.id; const isOwn = author._id === user.id;
if (broadcast && !isOwn) { if (broadcast && !isOwn) {
@ -17,25 +18,26 @@ const Broadcast = React.memo(({
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<Touchable <Touchable
onPress={replyBroadcast} onPress={replyBroadcast}
background={Touchable.Ripple('#fff')} background={Touchable.Ripple(themes[theme].bannerBackground)}
style={styles.button} style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
> >
<> <>
<CustomIcon name='back' size={20} style={styles.buttonIcon} /> <CustomIcon name='back' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
<Text style={styles.buttonText}>{I18n.t('Reply')}</Text> <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Reply')}</Text>
</> </>
</Touchable> </Touchable>
</View> </View>
); );
} }
return null; return null;
}, () => true); });
Broadcast.propTypes = { Broadcast.propTypes = {
author: PropTypes.object, author: PropTypes.object,
user: PropTypes.object, user: PropTypes.object,
broadcast: PropTypes.bool, broadcast: PropTypes.bool,
theme: PropTypes.string,
replyBroadcast: PropTypes.func replyBroadcast: PropTypes.func
}; };
Broadcast.displayName = 'MessageBroadcast'; Broadcast.displayName = 'MessageBroadcast';

View File

@ -7,31 +7,33 @@ import { formatLastMessage, BUTTON_HIT_SLOP } from './utils';
import styles from './styles'; import styles from './styles';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { themes } from '../../constants/colors';
const CallButton = React.memo(({ const CallButton = React.memo(({
dlm, callJitsi dlm, theme, callJitsi
}) => { }) => {
const time = formatLastMessage(dlm); const time = formatLastMessage(dlm);
return ( return (
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<Touchable <Touchable
onPress={callJitsi} onPress={callJitsi}
background={Touchable.Ripple('#fff')} background={Touchable.Ripple(themes[theme].bannerBackground)}
style={[styles.button, styles.smallButton]} style={[styles.button, styles.smallButton, { backgroundColor: themes[theme].tintColor }]}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
> >
<> <>
<CustomIcon name='video' size={20} style={styles.buttonIcon} /> <CustomIcon name='video' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
<Text style={styles.buttonText}>{I18n.t('Click_to_join')}</Text> <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Click_to_join')}</Text>
</> </>
</Touchable> </Touchable>
<Text style={styles.time}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View> </View>
); );
}); });
CallButton.propTypes = { CallButton.propTypes = {
dlm: PropTypes.string, dlm: PropTypes.string,
theme: PropTypes.string,
callJitsi: PropTypes.func callJitsi: PropTypes.func
}; };
CallButton.displayName = 'CallButton'; CallButton.displayName = 'CallButton';

View File

@ -6,16 +6,17 @@ import I18n from '../../i18n';
import styles from './styles'; import styles from './styles';
import Markdown from '../markdown'; import Markdown from '../markdown';
import { getInfoMessage } from './utils'; import { getInfoMessage } from './utils';
import { themes } from '../../constants/colors';
const Content = React.memo((props) => { const Content = React.memo((props) => {
if (props.isInfo) { if (props.isInfo) {
return <Text style={styles.textInfo}>{getInfoMessage({ ...props })}</Text>; return <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{getInfoMessage({ ...props })}</Text>;
} }
let content = null; let content = null;
if (props.tmid && !props.msg) { if (props.tmid && !props.msg) {
content = <Text style={styles.text}>{I18n.t('Sent_an_attachment')}</Text>; content = <Text style={[styles.text, { color: themes[props.theme].titleText }]}>{I18n.t('Sent_an_attachment')}</Text>;
} else { } else {
content = ( content = (
<Markdown <Markdown
@ -31,6 +32,7 @@ const Content = React.memo((props) => {
useMarkdown={props.useMarkdown && (!props.tmid || props.isThreadRoom)} useMarkdown={props.useMarkdown && (!props.tmid || props.isThreadRoom)}
navToRoomInfo={props.navToRoomInfo} navToRoomInfo={props.navToRoomInfo}
tmid={props.tmid} tmid={props.tmid}
theme={props.theme}
/> />
); );
} }
@ -40,7 +42,7 @@ const Content = React.memo((props) => {
{content} {content}
</View> </View>
); );
}, (prevProps, nextProps) => prevProps.isTemp === nextProps.isTemp && prevProps.msg === nextProps.msg); }, (prevProps, nextProps) => prevProps.isTemp === nextProps.isTemp && prevProps.msg === nextProps.msg && prevProps.theme === nextProps.theme);
Content.propTypes = { Content.propTypes = {
isTemp: PropTypes.bool, isTemp: PropTypes.bool,
@ -48,6 +50,7 @@ Content.propTypes = {
tmid: PropTypes.string, tmid: PropTypes.string,
isThreadRoom: PropTypes.bool, isThreadRoom: PropTypes.bool,
msg: PropTypes.string, msg: PropTypes.string,
theme: PropTypes.string,
isEdited: PropTypes.bool, isEdited: PropTypes.bool,
useMarkdown: PropTypes.bool, useMarkdown: PropTypes.bool,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,

View File

@ -8,29 +8,30 @@ import styles from './styles';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { DISCUSSION } from './constants'; import { DISCUSSION } from './constants';
import { themes } from '../../constants/colors';
const Discussion = React.memo(({ const Discussion = React.memo(({
msg, dcount, dlm, onDiscussionPress msg, dcount, dlm, onDiscussionPress, theme
}) => { }) => {
const time = formatLastMessage(dlm); const time = formatLastMessage(dlm);
const buttonText = formatMessageCount(dcount, DISCUSSION); const buttonText = formatMessageCount(dcount, DISCUSSION);
return ( return (
<> <>
<Text style={styles.startedDiscussion}>{I18n.t('Started_discussion')}</Text> <Text style={[styles.startedDiscussion, { color: themes[theme].auxiliaryText }]}>{I18n.t('Started_discussion')}</Text>
<Text style={styles.text}>{msg}</Text> <Text style={[styles.text, { color: themes[theme].titleText }]}>{msg}</Text>
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<Touchable <Touchable
onPress={onDiscussionPress} onPress={onDiscussionPress}
background={Touchable.Ripple('#fff')} background={Touchable.Ripple(themes[theme].bannerBackground)}
style={[styles.button, styles.smallButton]} style={[styles.button, styles.smallButton, { backgroundColor: themes[theme].tintColor }]}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
> >
<> <>
<CustomIcon name='chat' size={20} style={styles.buttonIcon} /> <CustomIcon name='chat' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
<Text style={styles.buttonText}>{buttonText}</Text> <Text style={[styles.buttonText, { color: themes[theme].titleText }]}>{buttonText}</Text>
</> </>
</Touchable> </Touchable>
<Text style={styles.time}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View> </View>
</> </>
); );
@ -44,6 +45,9 @@ const Discussion = React.memo(({
if (prevProps.dlm !== nextProps.dlm) { if (prevProps.dlm !== nextProps.dlm) {
return false; return false;
} }
if (prevProps.theme !== nextProps.theme) {
return false;
}
return true; return true;
}); });
@ -51,6 +55,7 @@ Discussion.propTypes = {
msg: PropTypes.string, msg: PropTypes.string,
dcount: PropTypes.number, dcount: PropTypes.number,
dlm: PropTypes.string, dlm: PropTypes.string,
theme: PropTypes.string,
onDiscussionPress: PropTypes.func onDiscussionPress: PropTypes.func
}; };
Discussion.displayName = 'MessageDiscussion'; Discussion.displayName = 'MessageDiscussion';

View File

@ -9,28 +9,31 @@ import Markdown from '../markdown';
import styles from './styles'; import styles from './styles';
import { formatAttachmentUrl } from '../../lib/utils'; import { formatAttachmentUrl } from '../../lib/utils';
import { withSplit } from '../../split'; import { withSplit } from '../../split';
import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
const Button = React.memo(({ children, onPress, split }) => ( const Button = React.memo(({
children, onPress, split, theme
}) => (
<Touchable <Touchable
onPress={onPress} onPress={onPress}
style={[styles.imageContainer, split && sharedStyles.tabletContent]} style={[styles.imageContainer, split && sharedStyles.tabletContent]}
background={Touchable.Ripple('#fff')} background={Touchable.Ripple(themes[theme].bannerBackground)}
> >
{children} {children}
</Touchable> </Touchable>
)); ));
const Image = React.memo(({ img }) => ( const Image = React.memo(({ img, theme }) => (
<FastImage <FastImage
style={styles.image} style={[styles.image, { borderColor: themes[theme].borderColor }]}
source={{ uri: encodeURI(img) }} source={{ uri: encodeURI(img) }}
resizeMode={FastImage.resizeMode.cover} resizeMode={FastImage.resizeMode.cover}
/> />
)); ));
const ImageContainer = React.memo(({ const ImageContainer = React.memo(({
file, baseUrl, user, useMarkdown, onOpenFileModal, getCustomEmoji, split file, baseUrl, user, useMarkdown, onOpenFileModal, getCustomEmoji, split, theme
}) => { }) => {
const img = formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl); const img = formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
if (!img) { if (!img) {
@ -41,21 +44,21 @@ const ImageContainer = React.memo(({
if (file.description) { if (file.description) {
return ( return (
<Button split={split} onPress={onPress}> <Button split={split} theme={theme} onPress={onPress}>
<View> <View>
<Image img={img} /> <Image img={img} theme={theme} />
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} /> <Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />
</View> </View>
</Button> </Button>
); );
} }
return ( return (
<Button split={split} onPress={onPress}> <Button split={split} theme={theme} onPress={onPress}>
<Image img={img} /> <Image img={img} theme={theme} />
</Button> </Button>
); );
}, (prevProps, nextProps) => equal(prevProps.file, nextProps.file) && prevProps.split === nextProps.split); }, (prevProps, nextProps) => equal(prevProps.file, nextProps.file) && prevProps.split === nextProps.split && prevProps.theme === nextProps.theme);
ImageContainer.propTypes = { ImageContainer.propTypes = {
file: PropTypes.object, file: PropTypes.object,
@ -63,19 +66,22 @@ ImageContainer.propTypes = {
user: PropTypes.object, user: PropTypes.object,
useMarkdown: PropTypes.bool, useMarkdown: PropTypes.bool,
onOpenFileModal: PropTypes.func, onOpenFileModal: PropTypes.func,
theme: PropTypes.string,
getCustomEmoji: PropTypes.func, getCustomEmoji: PropTypes.func,
split: PropTypes.bool split: PropTypes.bool
}; };
ImageContainer.displayName = 'MessageImageContainer'; ImageContainer.displayName = 'MessageImageContainer';
Image.propTypes = { Image.propTypes = {
img: PropTypes.string img: PropTypes.string,
theme: PropTypes.string
}; };
ImageContainer.displayName = 'MessageImage'; ImageContainer.displayName = 'MessageImage';
Button.propTypes = { Button.propTypes = {
children: PropTypes.node, children: PropTypes.node,
onPress: PropTypes.func, onPress: PropTypes.func,
theme: PropTypes.string,
split: PropTypes.bool split: PropTypes.bool
}; };
ImageContainer.displayName = 'MessageButton'; ImageContainer.displayName = 'MessageButton';

View File

@ -5,7 +5,6 @@ import Touchable from 'react-native-platform-touchable';
import User from './User'; import User from './User';
import styles from './styles'; import styles from './styles';
import sharedStyles from '../../views/Styles';
import RepliedThread from './RepliedThread'; import RepliedThread from './RepliedThread';
import MessageAvatar from './MessageAvatar'; import MessageAvatar from './MessageAvatar';
import Attachments from './Attachments'; import Attachments from './Attachments';
@ -56,7 +55,7 @@ const Message = React.memo((props) => {
return ( return (
<View style={[styles.container, props.style]}> <View style={[styles.container, props.style]}>
{thread} {thread}
<View style={[styles.flex, sharedStyles.alignItemsCenter]}> <View style={[styles.flex, styles.center]}>
<MessageAvatar small {...props} /> <MessageAvatar small {...props} />
<View <View
style={[ style={[
@ -85,6 +84,7 @@ const Message = React.memo((props) => {
<ReadReceipt <ReadReceipt
isReadReceiptEnabled={props.isReadReceiptEnabled} isReadReceiptEnabled={props.isReadReceiptEnabled}
unread={props.unread} unread={props.unread}
theme={props.theme}
/> />
</View> </View>
</View> </View>
@ -134,7 +134,8 @@ Message.propTypes = {
onLongPress: PropTypes.func, onLongPress: PropTypes.func,
onPress: PropTypes.func, onPress: PropTypes.func,
isReadReceiptEnabled: PropTypes.bool, isReadReceiptEnabled: PropTypes.bool,
unread: PropTypes.bool unread: PropTypes.bool,
theme: PropTypes.string
}; };
MessageInner.propTypes = { MessageInner.propTypes = {

View File

@ -3,24 +3,25 @@ import Touchable from 'react-native-platform-touchable';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { COLOR_DANGER } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
import { BUTTON_HIT_SLOP } from './utils'; import { BUTTON_HIT_SLOP } from './utils';
import { themes } from '../../constants/colors';
const MessageError = React.memo(({ hasError, onErrorPress }) => { const MessageError = React.memo(({ hasError, onErrorPress, theme }) => {
if (!hasError) { if (!hasError) {
return null; return null;
} }
return ( return (
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}> <Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
<CustomIcon name='warning' color={COLOR_DANGER} size={18} /> <CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
</Touchable> </Touchable>
); );
}, (prevProps, nextProps) => prevProps.hasError === nextProps.hasError); }, (prevProps, nextProps) => prevProps.hasError === nextProps.hasError && prevProps.theme === nextProps.theme);
MessageError.propTypes = { MessageError.propTypes = {
hasError: PropTypes.bool, hasError: PropTypes.bool,
onErrorPress: PropTypes.func onErrorPress: PropTypes.func,
theme: PropTypes.string
}; };
MessageError.displayName = 'MessageError'; MessageError.displayName = 'MessageError';

View File

@ -7,24 +7,26 @@ import { CustomIcon } from '../../lib/Icons';
import styles from './styles'; import styles from './styles';
import Emoji from './Emoji'; import Emoji from './Emoji';
import { BUTTON_HIT_SLOP } from './utils'; import { BUTTON_HIT_SLOP } from './utils';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
const AddReaction = React.memo(({ reactionInit }) => ( const AddReaction = React.memo(({ reactionInit, theme }) => (
<Touchable <Touchable
onPress={reactionInit} onPress={reactionInit}
key='message-add-reaction' key='message-add-reaction'
testID='message-add-reaction' testID='message-add-reaction'
style={styles.reactionButton} style={[styles.reactionButton, { backgroundColor: themes[theme].backgroundColor }]}
background={Touchable.Ripple('#fff')} background={Touchable.Ripple(themes[theme].bannerBackground)}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
> >
<View style={styles.reactionContainer}> <View style={[styles.reactionContainer, { borderColor: themes[theme].borderColor }]}>
<CustomIcon name='add-reaction' size={21} style={styles.addReaction} /> <CustomIcon name='add-reaction' size={21} color={themes[theme].tintColor} />
</View> </View>
</Touchable> </Touchable>
)); ));
const Reaction = React.memo(({ const Reaction = React.memo(({
reaction, user, onReactionLongPress, onReactionPress, baseUrl, getCustomEmoji reaction, user, onReactionLongPress, onReactionPress, baseUrl, getCustomEmoji, theme
}) => { }) => {
const reacted = reaction.usernames.findIndex(item => item === user.username) !== -1; const reacted = reaction.usernames.findIndex(item => item === user.username) !== -1;
return ( return (
@ -33,11 +35,11 @@ const Reaction = React.memo(({
onLongPress={onReactionLongPress} onLongPress={onReactionLongPress}
key={reaction.emoji} key={reaction.emoji}
testID={`message-reaction-${ reaction.emoji }`} testID={`message-reaction-${ reaction.emoji }`}
style={[styles.reactionButton, reacted && styles.reactionButtonReacted]} style={[styles.reactionButton, { backgroundColor: reacted ? themes[theme].bannerBackground : themes[theme].backgroundColor }]}
background={Touchable.Ripple('#fff')} background={Touchable.Ripple(themes[theme].bannerBackground)}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
> >
<View style={[styles.reactionContainer, reacted && styles.reactedContainer]}> <View style={[styles.reactionContainer, { borderColor: reacted ? themes[theme].tintColor : themes[theme].borderColor }]}>
<Emoji <Emoji
content={reaction.emoji} content={reaction.emoji}
standardEmojiStyle={styles.reactionEmoji} standardEmojiStyle={styles.reactionEmoji}
@ -45,14 +47,14 @@ const Reaction = React.memo(({
baseUrl={baseUrl} baseUrl={baseUrl}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
/> />
<Text style={styles.reactionCount}>{ reaction.usernames.length }</Text> <Text style={[styles.reactionCount, { color: themes[theme].tintColor }]}>{ reaction.usernames.length }</Text>
</View> </View>
</Touchable> </Touchable>
); );
}); });
const Reactions = React.memo(({ const Reactions = React.memo(({
reactions, user, baseUrl, onReactionPress, reactionInit, onReactionLongPress, getCustomEmoji reactions, user, baseUrl, onReactionPress, reactionInit, onReactionLongPress, getCustomEmoji, theme
}) => { }) => {
if (!reactions || reactions.length === 0) { if (!reactions || reactions.length === 0) {
return null; return null;
@ -68,9 +70,10 @@ const Reactions = React.memo(({
onReactionLongPress={onReactionLongPress} onReactionLongPress={onReactionLongPress}
onReactionPress={onReactionPress} onReactionPress={onReactionPress}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme}
/> />
))} ))}
<AddReaction reactionInit={reactionInit} /> <AddReaction reactionInit={reactionInit} theme={theme} />
</View> </View>
); );
}); });
@ -81,7 +84,8 @@ Reaction.propTypes = {
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
onReactionPress: PropTypes.func, onReactionPress: PropTypes.func,
onReactionLongPress: PropTypes.func, onReactionLongPress: PropTypes.func,
getCustomEmoji: PropTypes.func getCustomEmoji: PropTypes.func,
theme: PropTypes.string
}; };
Reaction.displayName = 'MessageReaction'; Reaction.displayName = 'MessageReaction';
@ -92,13 +96,15 @@ Reactions.propTypes = {
onReactionPress: PropTypes.func, onReactionPress: PropTypes.func,
reactionInit: PropTypes.func, reactionInit: PropTypes.func,
onReactionLongPress: PropTypes.func, onReactionLongPress: PropTypes.func,
getCustomEmoji: PropTypes.func getCustomEmoji: PropTypes.func,
theme: PropTypes.string
}; };
Reactions.displayName = 'MessageReactions'; Reactions.displayName = 'MessageReactions';
AddReaction.propTypes = { AddReaction.propTypes = {
reactionInit: PropTypes.func reactionInit: PropTypes.func,
theme: PropTypes.string
}; };
AddReaction.displayName = 'MessageAddReaction'; AddReaction.displayName = 'MessageAddReaction';
export default Reactions; export default withTheme(Reactions);

View File

@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { COLOR_PRIMARY } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import styles from './styles'; import styles from './styles';
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }) => { const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread, theme }) => {
if (isReadReceiptEnabled && !unread && unread !== null) { if (isReadReceiptEnabled && !unread && unread !== null) {
return <CustomIcon name='check' color={COLOR_PRIMARY} size={15} style={styles.readReceipt} />; return <CustomIcon name='check' color={themes[theme].tintColor} size={15} style={styles.readReceipt} />;
} }
return null; return null;
}); });
@ -15,7 +15,8 @@ ReadReceipt.displayName = 'MessageReadReceipt';
ReadReceipt.propTypes = { ReadReceipt.propTypes = {
isReadReceiptEnabled: PropTypes.bool, isReadReceiptEnabled: PropTypes.bool,
unread: PropTypes.bool unread: PropTypes.bool,
theme: PropTypes.bool
}; };
export default ReadReceipt; export default ReadReceipt;

View File

@ -7,9 +7,10 @@ import PropTypes from 'prop-types';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import DisclosureIndicator from '../DisclosureIndicator'; import DisclosureIndicator from '../DisclosureIndicator';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors';
const RepliedThread = React.memo(({ const RepliedThread = React.memo(({
tmid, tmsg, isHeader, fetchThreadName, id tmid, tmsg, isHeader, fetchThreadName, id, theme
}) => { }) => {
if (!tmid || !isHeader) { if (!tmid || !isHeader) {
return null; return null;
@ -25,9 +26,9 @@ const RepliedThread = React.memo(({
return ( return (
<View style={styles.repliedThread} testID={`message-thread-replied-on-${ msg }`}> <View style={styles.repliedThread} testID={`message-thread-replied-on-${ msg }`}>
<CustomIcon name='thread' size={20} style={styles.repliedThreadIcon} /> <CustomIcon name='thread' size={20} style={styles.repliedThreadIcon} color={themes[theme].tintColor} />
<Text style={styles.repliedThreadName} numberOfLines={1}>{msg}</Text> <Text style={[styles.repliedThreadName, { color: themes[theme].tintColor }]} numberOfLines={1}>{msg}</Text>
<DisclosureIndicator /> <DisclosureIndicator theme={theme} />
</View> </View>
); );
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {
@ -40,6 +41,9 @@ const RepliedThread = React.memo(({
if (prevProps.isHeader !== nextProps.isHeader) { if (prevProps.isHeader !== nextProps.isHeader) {
return false; return false;
} }
if (prevProps.theme !== nextProps.theme) {
return false;
}
return true; return true;
}); });
@ -48,6 +52,7 @@ RepliedThread.propTypes = {
tmsg: PropTypes.string, tmsg: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
isHeader: PropTypes.bool, isHeader: PropTypes.bool,
theme: PropTypes.string,
fetchThreadName: PropTypes.func fetchThreadName: PropTypes.func
}; };
RepliedThread.displayName = 'MessageRepliedThread'; RepliedThread.displayName = 'MessageRepliedThread';

View File

@ -8,7 +8,7 @@ import isEqual from 'deep-equal';
import Markdown from '../markdown'; import Markdown from '../markdown';
import openLink from '../../utils/openLink'; import openLink from '../../utils/openLink';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { COLOR_BACKGROUND_CONTAINER, COLOR_BORDER } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withSplit } from '../../split'; import { withSplit } from '../../split';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -18,8 +18,6 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
marginTop: 6, marginTop: 6,
alignSelf: 'flex-start', alignSelf: 'flex-start',
backgroundColor: COLOR_BACKGROUND_CONTAINER,
borderColor: COLOR_BORDER,
borderWidth: 1, borderWidth: 1,
borderRadius: 4 borderRadius: 4
}, },
@ -37,13 +35,11 @@ const styles = StyleSheet.create({
author: { author: {
flex: 1, flex: 1,
fontSize: 16, fontSize: 16,
...sharedStyles.textColorNormal,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
time: { time: {
fontSize: 12, fontSize: 12,
marginLeft: 10, marginLeft: 10,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular, ...sharedStyles.textRegular,
fontWeight: '300' fontWeight: '300'
}, },
@ -58,12 +54,10 @@ const styles = StyleSheet.create({
}, },
fieldTitle: { fieldTitle: {
fontSize: 14, fontSize: 14,
...sharedStyles.textColorNormal,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
fieldValue: { fieldValue: {
fontSize: 14, fontSize: 14,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
marginTop: { marginTop: {
@ -71,21 +65,21 @@ const styles = StyleSheet.create({
} }
}); });
const Title = React.memo(({ attachment, timeFormat }) => { const Title = React.memo(({ attachment, timeFormat, theme }) => {
if (!attachment.author_name) { if (!attachment.author_name) {
return null; return null;
} }
const time = attachment.ts ? moment(attachment.ts).format(timeFormat) : null; const time = attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
return ( return (
<View style={styles.authorContainer}> <View style={styles.authorContainer}>
{attachment.author_name ? <Text style={styles.author}>{attachment.author_name}</Text> : null} {attachment.author_name ? <Text style={[styles.author, { color: themes[theme].titleText }]}>{attachment.author_name}</Text> : null}
{time ? <Text style={styles.time}>{ time }</Text> : null} {time ? <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{ time }</Text> : null}
</View> </View>
); );
}, () => true); });
const Description = React.memo(({ const Description = React.memo(({
attachment, baseUrl, user, getCustomEmoji, useMarkdown attachment, baseUrl, user, getCustomEmoji, useMarkdown, theme
}) => { }) => {
const text = attachment.text || attachment.title; const text = attachment.text || attachment.title;
if (!text) { if (!text) {
@ -98,6 +92,7 @@ const Description = React.memo(({
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
useMarkdown={useMarkdown} useMarkdown={useMarkdown}
theme={theme}
/> />
); );
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {
@ -107,10 +102,13 @@ const Description = React.memo(({
if (prevProps.attachment.title !== nextProps.attachment.title) { if (prevProps.attachment.title !== nextProps.attachment.title) {
return false; return false;
} }
if (prevProps.theme !== nextProps.theme) {
return false;
}
return true; return true;
}); });
const Fields = React.memo(({ attachment }) => { const Fields = React.memo(({ attachment, theme }) => {
if (!attachment.fields) { if (!attachment.fields) {
return null; return null;
} }
@ -118,16 +116,16 @@ const Fields = React.memo(({ attachment }) => {
<View style={styles.fieldsContainer}> <View style={styles.fieldsContainer}>
{attachment.fields.map(field => ( {attachment.fields.map(field => (
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}> <View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
<Text style={styles.fieldTitle}>{field.title}</Text> <Text style={[styles.fieldTitle, { color: themes[theme].titleText }]}>{field.title}</Text>
<Text style={styles.fieldValue}>{field.value}</Text> <Text style={[styles.fieldValue, { color: themes[theme].titleText }]}>{field.value}</Text>
</View> </View>
))} ))}
</View> </View>
); );
}, (prevProps, nextProps) => isEqual(prevProps.attachment.fields, nextProps.attachment.fields)); }, (prevProps, nextProps) => isEqual(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme);
const Reply = React.memo(({ const Reply = React.memo(({
attachment, timeFormat, baseUrl, user, index, getCustomEmoji, useMarkdown, split attachment, timeFormat, baseUrl, user, index, getCustomEmoji, useMarkdown, split, theme
}) => { }) => {
if (!attachment) { if (!attachment) {
return null; return null;
@ -141,17 +139,25 @@ const Reply = React.memo(({
if (attachment.type === 'file') { if (attachment.type === 'file') {
url = `${ baseUrl }${ url }?rc_uid=${ user.id }&rc_token=${ user.token }`; url = `${ baseUrl }${ url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
} }
openLink(url); openLink(url, theme);
}; };
return ( return (
<Touchable <Touchable
onPress={onPress} onPress={onPress}
style={[styles.button, index > 0 && styles.marginTop, split && sharedStyles.tabletContent]} style={[
background={Touchable.Ripple('#fff')} styles.button,
index > 0 && styles.marginTop,
{
backgroundColor: themes[theme].chatComponentBackground,
borderColor: themes[theme].borderColor
},
split && sharedStyles.tabletContent
]}
background={Touchable.Ripple(themes[theme].bannerBackground)}
> >
<View style={styles.attachmentContainer}> <View style={styles.attachmentContainer}>
<Title attachment={attachment} timeFormat={timeFormat} /> <Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
<Description <Description
attachment={attachment} attachment={attachment}
timeFormat={timeFormat} timeFormat={timeFormat}
@ -159,12 +165,13 @@ const Reply = React.memo(({
user={user} user={user}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
useMarkdown={useMarkdown} useMarkdown={useMarkdown}
theme={theme}
/> />
<Fields attachment={attachment} /> <Fields attachment={attachment} theme={theme} />
</View> </View>
</Touchable> </Touchable>
); );
}, (prevProps, nextProps) => isEqual(prevProps.attachment, nextProps.attachment) && prevProps.split === nextProps.split); }, (prevProps, nextProps) => isEqual(prevProps.attachment, nextProps.attachment) && prevProps.split === nextProps.split && prevProps.theme === nextProps.theme);
Reply.propTypes = { Reply.propTypes = {
attachment: PropTypes.object, attachment: PropTypes.object,
@ -173,6 +180,7 @@ Reply.propTypes = {
user: PropTypes.object, user: PropTypes.object,
index: PropTypes.number, index: PropTypes.number,
useMarkdown: PropTypes.bool, useMarkdown: PropTypes.bool,
theme: PropTypes.string,
getCustomEmoji: PropTypes.func, getCustomEmoji: PropTypes.func,
split: PropTypes.bool split: PropTypes.bool
}; };
@ -180,7 +188,8 @@ Reply.displayName = 'MessageReply';
Title.propTypes = { Title.propTypes = {
attachment: PropTypes.object, attachment: PropTypes.object,
timeFormat: PropTypes.string timeFormat: PropTypes.string,
theme: PropTypes.string
}; };
Title.displayName = 'MessageReplyTitle'; Title.displayName = 'MessageReplyTitle';
@ -189,12 +198,14 @@ Description.propTypes = {
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
user: PropTypes.object, user: PropTypes.object,
useMarkdown: PropTypes.bool, useMarkdown: PropTypes.bool,
getCustomEmoji: PropTypes.func getCustomEmoji: PropTypes.func,
theme: PropTypes.string
}; };
Description.displayName = 'MessageReplyDescription'; Description.displayName = 'MessageReplyDescription';
Fields.propTypes = { Fields.propTypes = {
attachment: PropTypes.object attachment: PropTypes.object,
theme: PropTypes.string
}; };
Fields.displayName = 'MessageReplyFields'; Fields.displayName = 'MessageReplyFields';

View File

@ -6,9 +6,10 @@ import { formatLastMessage, formatMessageCount } from './utils';
import styles from './styles'; import styles from './styles';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { THREAD } from './constants'; import { THREAD } from './constants';
import { themes } from '../../constants/colors';
const Thread = React.memo(({ const Thread = React.memo(({
msg, tcount, tlm, customThreadTimeFormat, isThreadRoom msg, tcount, tlm, customThreadTimeFormat, isThreadRoom, theme
}) => { }) => {
if (!tlm || isThreadRoom || tcount === 0) { if (!tlm || isThreadRoom || tcount === 0) {
return null; return null;
@ -19,25 +20,29 @@ const Thread = React.memo(({
return ( return (
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<View <View
style={[styles.button, styles.smallButton]} style={[styles.button, styles.smallButton, { backgroundColor: themes[theme].tintColor }]}
testID={`message-thread-button-${ msg }`} testID={`message-thread-button-${ msg }`}
> >
<CustomIcon name='thread' size={20} style={styles.buttonIcon} /> <CustomIcon name='thread' size={20} style={[styles.buttonIcon, { color: themes[theme].buttonText }]} />
<Text style={styles.buttonText}>{buttonText}</Text> <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{buttonText}</Text>
</View> </View>
<Text style={styles.time}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View> </View>
); );
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {
if (prevProps.tcount !== nextProps.tcount) { if (prevProps.tcount !== nextProps.tcount) {
return false; return false;
} }
if (prevProps.theme !== nextProps.theme) {
return false;
}
return true; return true;
}); });
Thread.propTypes = { Thread.propTypes = {
msg: PropTypes.string, msg: PropTypes.string,
tcount: PropTypes.string, tcount: PropTypes.string,
theme: PropTypes.string,
tlm: PropTypes.string, tlm: PropTypes.string,
customThreadTimeFormat: PropTypes.string, customThreadTimeFormat: PropTypes.string,
isThreadRoom: PropTypes.bool isThreadRoom: PropTypes.bool

View File

@ -7,9 +7,8 @@ import isEqual from 'lodash/isEqual';
import openLink from '../../utils/openLink'; import openLink from '../../utils/openLink';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { import { themes } from '../../constants/colors';
COLOR_BACKGROUND_CONTAINER, COLOR_BORDER, COLOR_PRIMARY import { withTheme } from '../../theme';
} from '../../constants/colors';
import { withSplit } from '../../split'; import { withSplit } from '../../split';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -20,8 +19,6 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
borderRadius: 4, borderRadius: 4,
backgroundColor: COLOR_BACKGROUND_CONTAINER,
borderColor: COLOR_BORDER,
borderWidth: 1 borderWidth: 1
}, },
textContainer: { textContainer: {
@ -32,13 +29,11 @@ const styles = StyleSheet.create({
alignItems: 'flex-start' alignItems: 'flex-start'
}, },
title: { title: {
color: COLOR_PRIMARY,
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
description: { description: {
fontSize: 16, fontSize: 16,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
marginTop: { marginTop: {
@ -60,10 +55,10 @@ const UrlImage = React.memo(({ image, user, baseUrl }) => {
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />; return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
}, (prevProps, nextProps) => prevProps.image === nextProps.image); }, (prevProps, nextProps) => prevProps.image === nextProps.image);
const UrlContent = React.memo(({ title, description }) => ( const UrlContent = React.memo(({ title, description, theme }) => (
<View style={styles.textContainer}> <View style={styles.textContainer}>
{title ? <Text style={styles.title} numberOfLines={2}>{title}</Text> : null} {title ? <Text style={[styles.title, { color: themes[theme].tintColor }]} numberOfLines={2}>{title}</Text> : null}
{description ? <Text style={styles.description} numberOfLines={2}>{description}</Text> : null} {description ? <Text style={[styles.description, { color: themes[theme].auxiliaryText }]} numberOfLines={2}>{description}</Text> : null}
</View> </View>
), (prevProps, nextProps) => { ), (prevProps, nextProps) => {
if (prevProps.title !== nextProps.title) { if (prevProps.title !== nextProps.title) {
@ -72,43 +67,55 @@ const UrlContent = React.memo(({ title, description }) => (
if (prevProps.description !== nextProps.description) { if (prevProps.description !== nextProps.description) {
return false; return false;
} }
if (prevProps.theme !== nextProps.theme) {
return false;
}
return true; return true;
}); });
const Url = React.memo(({ const Url = React.memo(({
url, index, user, baseUrl, split url, index, user, baseUrl, split, theme
}) => { }) => {
if (!url) { if (!url) {
return null; return null;
} }
const onPress = () => openLink(url.url); const onPress = () => openLink(url.url, theme);
return ( return (
<Touchable <Touchable
onPress={onPress} onPress={onPress}
style={[styles.button, index > 0 && styles.marginTop, styles.container, split && sharedStyles.tabletContent]} style={[
background={Touchable.Ripple('#fff')} styles.button,
index > 0 && styles.marginTop,
styles.container,
{
backgroundColor: themes[theme].chatComponentBackground,
borderColor: themes[theme].borderColor
},
split && sharedStyles.tabletContent
]}
background={Touchable.Ripple(themes[theme].bannerBackground)}
> >
<> <>
<UrlImage image={url.image} user={user} baseUrl={baseUrl} /> <UrlImage image={url.image} user={user} baseUrl={baseUrl} />
<UrlContent title={url.title} description={url.description} /> <UrlContent title={url.title} description={url.description} theme={theme} />
</> </>
</Touchable> </Touchable>
); );
}, (oldProps, newProps) => isEqual(oldProps.url, newProps.url) && oldProps.split === newProps.split); }, (oldProps, newProps) => isEqual(oldProps.url, newProps.url) && oldProps.split === newProps.split && oldProps.theme === newProps.theme);
const Urls = React.memo(({ const Urls = React.memo(({
urls, user, baseUrl, split urls, user, baseUrl, split, theme
}) => { }) => {
if (!urls || urls.length === 0) { if (!urls || urls.length === 0) {
return null; return null;
} }
return urls.map((url, index) => ( return urls.map((url, index) => (
<Url url={url} key={url.url} index={index} user={user} baseUrl={baseUrl} split={split} /> <Url url={url} key={url.url} index={index} user={user} baseUrl={baseUrl} split={split} theme={theme} />
)); ));
}, (oldProps, newProps) => isEqual(oldProps.urls, newProps.urls) && oldProps.split === newProps.split); }, (oldProps, newProps) => isEqual(oldProps.urls, newProps.urls) && oldProps.split === newProps.split && oldProps.theme === newProps.theme);
UrlImage.propTypes = { UrlImage.propTypes = {
image: PropTypes.string, image: PropTypes.string,
@ -119,7 +126,8 @@ UrlImage.displayName = 'MessageUrlImage';
UrlContent.propTypes = { UrlContent.propTypes = {
title: PropTypes.string, title: PropTypes.string,
description: PropTypes.string description: PropTypes.string,
theme: PropTypes.string
}; };
UrlContent.displayName = 'MessageUrlContent'; UrlContent.displayName = 'MessageUrlContent';
@ -128,6 +136,7 @@ Url.propTypes = {
index: PropTypes.number, index: PropTypes.number,
user: PropTypes.object, user: PropTypes.object,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
theme: PropTypes.string,
split: PropTypes.bool split: PropTypes.bool
}; };
Url.displayName = 'MessageUrl'; Url.displayName = 'MessageUrl';
@ -136,8 +145,9 @@ Urls.propTypes = {
urls: PropTypes.array, urls: PropTypes.array,
user: PropTypes.object, user: PropTypes.object,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
theme: PropTypes.string,
split: PropTypes.bool split: PropTypes.bool
}; };
Urls.displayName = 'MessageUrls'; Urls.displayName = 'MessageUrls';
export default withSplit(Urls); export default withTheme(withSplit(Urls));

View File

@ -3,6 +3,9 @@ import PropTypes from 'prop-types';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, StyleSheet } from 'react-native';
import moment from 'moment'; import moment from 'moment';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import MessageError from './MessageError'; import MessageError from './MessageError';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import messageStyles from './styles'; import messageStyles from './styles';
@ -16,7 +19,6 @@ const styles = StyleSheet.create({
username: { username: {
fontSize: 16, fontSize: 16,
lineHeight: 22, lineHeight: 22,
...sharedStyles.textColorNormal,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
titleContainer: { titleContainer: {
@ -26,29 +28,28 @@ const styles = StyleSheet.create({
}, },
alias: { alias: {
fontSize: 14, fontSize: 14,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular ...sharedStyles.textRegular
} }
}); });
const User = React.memo(({ const User = React.memo(({
isHeader, useRealName, author, alias, ts, timeFormat, hasError, ...props isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, ...props
}) => { }) => {
if (isHeader || hasError) { if (isHeader || hasError) {
const username = (useRealName && author.name) || author.username; const username = (useRealName && author.name) || author.username;
const aliasUsername = alias ? (<Text style={styles.alias}> @{username}</Text>) : null; const aliasUsername = alias ? (<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>) : null;
const time = moment(ts).format(timeFormat); const time = moment(ts).format(timeFormat);
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<Text style={styles.username} numberOfLines={1}> <Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
{alias || username} {alias || username}
{aliasUsername} {aliasUsername}
</Text> </Text>
</View> </View>
<Text style={messageStyles.time}>{time}</Text> <Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
{ hasError && <MessageError hasError={hasError} {...props} /> } { hasError && <MessageError hasError={hasError} theme={theme} {...props} /> }
</View> </View>
); );
} }
@ -62,8 +63,9 @@ User.propTypes = {
author: PropTypes.object, author: PropTypes.object,
alias: PropTypes.string, alias: PropTypes.string,
ts: PropTypes.instanceOf(Date), ts: PropTypes.instanceOf(Date),
timeFormat: PropTypes.string timeFormat: PropTypes.string,
theme: PropTypes.string
}; };
User.displayName = 'MessageUser'; User.displayName = 'MessageUser';
export default User; export default withTheme(User);

View File

@ -9,6 +9,7 @@ import openLink from '../../utils/openLink';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { formatAttachmentUrl } from '../../lib/utils'; import { formatAttachmentUrl } from '../../lib/utils';
import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
@ -19,22 +20,14 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
borderRadius: 4, borderRadius: 4,
height: 150, height: 150,
backgroundColor: '#1f2329',
marginBottom: 6, marginBottom: 6,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
},
modal: {
margin: 0,
backgroundColor: '#000'
},
image: {
color: 'white'
} }
}); });
const Video = React.memo(({ const Video = React.memo(({
file, baseUrl, user, useMarkdown, onOpenFileModal, getCustomEmoji file, baseUrl, user, useMarkdown, onOpenFileModal, getCustomEmoji, theme
}) => { }) => {
if (!baseUrl) { if (!baseUrl) {
return null; return null;
@ -45,26 +38,26 @@ const Video = React.memo(({
return onOpenFileModal(file); return onOpenFileModal(file);
} }
const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl); const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
openLink(uri); openLink(uri, theme);
}; };
return ( return (
<> <>
<Touchable <Touchable
onPress={onPress} onPress={onPress}
style={[styles.button, isTablet && sharedStyles.tabletContent]} style={[styles.button, { backgroundColor: themes[theme].videoBackground }, isTablet && sharedStyles.tabletContent]}
background={Touchable.Ripple('#fff')} background={Touchable.Ripple(themes[theme].bannerBackground)}
> >
<CustomIcon <CustomIcon
name='play' name='play'
size={54} size={54}
style={styles.image} color={themes[theme].buttonText}
/> />
</Touchable> </Touchable>
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} /> <Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />
</> </>
); );
}, (prevProps, nextProps) => isEqual(prevProps.file, nextProps.file)); }, (prevProps, nextProps) => isEqual(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme);
Video.propTypes = { Video.propTypes = {
file: PropTypes.object, file: PropTypes.object,
@ -72,7 +65,8 @@ Video.propTypes = {
user: PropTypes.object, user: PropTypes.object,
useMarkdown: PropTypes.bool, useMarkdown: PropTypes.bool,
onOpenFileModal: PropTypes.func, onOpenFileModal: PropTypes.func,
getCustomEmoji: PropTypes.func getCustomEmoji: PropTypes.func,
theme: PropTypes.string
}; };
export default Video; export default Video;

View File

@ -6,8 +6,9 @@ import Message from './Message';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils'; import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
import messagesStatus from '../../constants/messagesStatus'; import messagesStatus from '../../constants/messagesStatus';
import { withTheme } from '../../theme';
export default class MessageContainer extends React.Component { class MessageContainer extends React.Component {
static propTypes = { static propTypes = {
item: PropTypes.object.isRequired, item: PropTypes.object.isRequired,
user: PropTypes.shape({ user: PropTypes.shape({
@ -42,13 +43,15 @@ export default class MessageContainer extends React.Component {
onOpenFileModal: PropTypes.func, onOpenFileModal: PropTypes.func,
onReactionLongPress: PropTypes.func, onReactionLongPress: PropTypes.func,
navToRoomInfo: PropTypes.func, navToRoomInfo: PropTypes.func,
callJitsi: PropTypes.func callJitsi: PropTypes.func,
theme: PropTypes.string
} }
static defaultProps = { static defaultProps = {
onLongPress: () => {}, onLongPress: () => {},
archived: false, archived: false,
broadcast: false broadcast: false,
theme: 'light'
} }
componentDidMount() { componentDidMount() {
@ -61,7 +64,11 @@ export default class MessageContainer extends React.Component {
} }
} }
shouldComponentUpdate() { shouldComponentUpdate(nextProps) {
const { theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
return false; return false;
} }
@ -205,7 +212,7 @@ export default class MessageContainer extends React.Component {
render() { render() {
const { const {
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, theme
} = this.props; } = this.props;
const { const {
id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
@ -272,7 +279,10 @@ export default class MessageContainer extends React.Component {
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
navToRoomInfo={navToRoomInfo} navToRoomInfo={navToRoomInfo}
callJitsi={callJitsi} callJitsi={callJitsi}
theme={theme}
/> />
); );
} }
} }
export default withTheme(MessageContainer);

View File

@ -1,9 +1,6 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import {
COLOR_BORDER, COLOR_PRIMARY, COLOR_WHITE
} from '../../constants/colors';
import { isTablet } from '../../utils/deviceInfo'; import { isTablet } from '../../utils/deviceInfo';
export default StyleSheet.create({ export default StyleSheet.create({
@ -26,6 +23,9 @@ export default StyleSheet.create({
messageContentWithError: { messageContentWithError: {
marginLeft: 0 marginLeft: 0
}, },
center: {
alignItems: 'center'
},
flex: { flex: {
flexDirection: 'row' flexDirection: 'row'
// flex: 1 // flex: 1
@ -44,27 +44,19 @@ export default StyleSheet.create({
marginBottom: 6, marginBottom: 6,
borderRadius: 2 borderRadius: 2
}, },
reactionButtonReacted: {
backgroundColor: '#e8f2ff'
},
reactionContainer: { reactionContainer: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
borderRadius: 2, borderRadius: 2,
borderWidth: 1, borderWidth: 1,
borderColor: COLOR_BORDER,
height: 28, height: 28,
minWidth: 46.3 minWidth: 46.3
}, },
reactedContainer: {
borderColor: COLOR_PRIMARY
},
reactionCount: { reactionCount: {
fontSize: 14, fontSize: 14,
marginLeft: 3, marginLeft: 3,
marginRight: 8.5, marginRight: 8.5,
color: COLOR_PRIMARY,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
reactionEmoji: { reactionEmoji: {
@ -82,9 +74,6 @@ export default StyleSheet.create({
avatarSmall: { avatarSmall: {
marginLeft: 16 marginLeft: 16
}, },
addReaction: {
color: COLOR_PRIMARY
},
errorButton: { errorButton: {
paddingLeft: 10, paddingLeft: 10,
paddingVertical: 5 paddingVertical: 5
@ -100,18 +89,15 @@ export default StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
backgroundColor: COLOR_PRIMARY,
borderRadius: 2 borderRadius: 2
}, },
smallButton: { smallButton: {
height: 30 height: 30
}, },
buttonIcon: { buttonIcon: {
color: COLOR_WHITE,
marginRight: 6 marginRight: 6
}, },
buttonText: { buttonText: {
color: COLOR_WHITE,
fontSize: 14, fontSize: 14,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
@ -125,7 +111,6 @@ export default StyleSheet.create({
// maxWidth: 400, // maxWidth: 400,
minHeight: isTablet ? 300 : 200, minHeight: isTablet ? 300 : 200,
borderRadius: 4, borderRadius: 4,
borderColor: COLOR_BORDER,
borderWidth: 1 borderWidth: 1
}, },
imagePressed: { imagePressed: {
@ -138,27 +123,23 @@ export default StyleSheet.create({
}, },
text: { text: {
fontSize: 16, fontSize: 16,
...sharedStyles.textColorNormal,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
textInfo: { textInfo: {
fontStyle: 'italic', fontStyle: 'italic',
fontSize: 16, fontSize: 16,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
startedDiscussion: { startedDiscussion: {
fontStyle: 'italic', fontStyle: 'italic',
fontSize: 16, fontSize: 16,
marginBottom: 6, marginBottom: 6,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
time: { time: {
fontSize: 12, fontSize: 12,
paddingLeft: 10, paddingLeft: 10,
lineHeight: 22, lineHeight: 22,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular, ...sharedStyles.textRegular,
fontWeight: '300' fontWeight: '300'
}, },
@ -170,14 +151,12 @@ export default StyleSheet.create({
marginBottom: 12 marginBottom: 12
}, },
repliedThreadIcon: { repliedThreadIcon: {
color: COLOR_PRIMARY,
marginRight: 10, marginRight: 10,
marginLeft: 16 marginLeft: 16
}, },
repliedThreadName: { repliedThreadName: {
fontSize: 16, fontSize: 16,
flex: 1, flex: 1,
color: COLOR_PRIMARY,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
readReceipt: { readReceipt: {

View File

@ -95,6 +95,7 @@ export default {
announcement: 'announcement', announcement: 'announcement',
Announcement: 'Announcement', Announcement: 'Announcement',
Apply_Your_Certificate: 'Apply Your Certificate', Apply_Your_Certificate: 'Apply Your Certificate',
Applying_a_theme_will_change_how_the_app_looks: 'Applying a theme will change how the app looks.',
ARCHIVE: 'ARCHIVE', ARCHIVE: 'ARCHIVE',
archive: 'archive', archive: 'archive',
are_typing: 'are typing', are_typing: 'are typing',
@ -102,11 +103,13 @@ export default {
Are_you_sure_you_want_to_leave_the_room: 'Are you sure you want to leave the room {{room}}?', Are_you_sure_you_want_to_leave_the_room: 'Are you sure you want to leave the room {{room}}?',
Audio: 'Audio', Audio: 'Audio',
Authenticating: 'Authenticating', Authenticating: 'Authenticating',
Automatic: 'Automatic',
Auto_Translate: 'Auto-Translate', Auto_Translate: 'Auto-Translate',
Avatar_changed_successfully: 'Avatar changed successfully!', Avatar_changed_successfully: 'Avatar changed successfully!',
Avatar_Url: 'Avatar URL', Avatar_Url: 'Avatar URL',
Away: 'Away', Away: 'Away',
Back: 'Back', Back: 'Back',
Black: 'Black',
Block_user: 'Block user', Block_user: 'Block user',
Broadcast_channel_Description: 'Only authorized users can write new messages, but the other users will be able to reply', Broadcast_channel_Description: 'Only authorized users can write new messages, but the other users will be able to reply',
Broadcast_Channel: 'Broadcast Channel', Broadcast_Channel: 'Broadcast Channel',
@ -148,6 +151,8 @@ export default {
Created_snippet: 'Created a snippet', Created_snippet: 'Created a snippet',
Create_a_new_workspace: 'Create a new workspace', Create_a_new_workspace: 'Create a new workspace',
Create: 'Create', Create: 'Create',
Dark: 'Dark',
Dark_level: 'Dark Level',
Default: 'Default', Default: 'Default',
Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.', Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.',
delete: 'delete', delete: 'delete',
@ -212,6 +217,7 @@ export default {
leaving_room: 'leaving room', leaving_room: 'leaving room',
leave: 'leave', leave: 'leave',
Legal: 'Legal', Legal: 'Legal',
Light: 'Light',
License: 'License', License: 'License',
Livechat: 'Livechat', Livechat: 'Livechat',
Login: 'Login', Login: 'Login',

View File

@ -99,16 +99,19 @@ export default {
and: 'e', and: 'e',
announcement: 'anúncio', announcement: 'anúncio',
Announcement: 'Anúncio', Announcement: 'Anúncio',
Applying_a_theme_will_change_how_the_app_looks: 'Aplicar um tema mudará a aparência do app.',
ARCHIVE: 'ARQUIVAR', ARCHIVE: 'ARQUIVAR',
archive: 'arquivar', archive: 'arquivar',
are_typing: 'estão digitando', are_typing: 'estão digitando',
Are_you_sure_question_mark: 'Você tem certeza?', Are_you_sure_question_mark: 'Você tem certeza?',
Are_you_sure_you_want_to_leave_the_room: 'Tem certeza de que deseja sair da sala {{room}}?', Are_you_sure_you_want_to_leave_the_room: 'Tem certeza de que deseja sair da sala {{room}}?',
Authenticating: 'Autenticando', Authenticating: 'Autenticando',
Automatic: 'Automático',
Avatar_changed_successfully: 'Avatar alterado com sucesso!', Avatar_changed_successfully: 'Avatar alterado com sucesso!',
Avatar_Url: 'Avatar URL', Avatar_Url: 'Avatar URL',
Away: 'Ausente', Away: 'Ausente',
Back: 'Voltar', Back: 'Voltar',
Black: 'Preto',
Block_user: 'Bloquear usuário', Block_user: 'Bloquear usuário',
Broadcast_channel_Description: 'Somente usuários autorizados podem escrever novas mensagens, mas os outros usuários poderão responder', Broadcast_channel_Description: 'Somente usuários autorizados podem escrever novas mensagens, mas os outros usuários poderão responder',
Broadcast_Channel: 'Canal de Transmissão', Broadcast_Channel: 'Canal de Transmissão',
@ -147,6 +150,8 @@ export default {
Created_snippet: 'Criou um snippet', Created_snippet: 'Criou um snippet',
Create_a_new_workspace: 'Criar nova área de trabalho', Create_a_new_workspace: 'Criar nova área de trabalho',
Create: 'Criar', Create: 'Criar',
Dark: 'Escuro',
Dark_level: 'Nível escuro',
Delete_Room_Warning: 'A exclusão de uma sala irá apagar todas as mensagens postadas na sala. Isso não pode ser desfeito.', Delete_Room_Warning: 'A exclusão de uma sala irá apagar todas as mensagens postadas na sala. Isso não pode ser desfeito.',
delete: 'excluir', delete: 'excluir',
Delete: 'Excluir', Delete: 'Excluir',
@ -200,6 +205,7 @@ export default {
leaving_room: 'saindo do canal', leaving_room: 'saindo do canal',
leave: 'sair', leave: 'sair',
Legal: 'Legal', Legal: 'Legal',
Light: 'Claro',
Livechat: 'Livechat', Livechat: 'Livechat',
Login: 'Entrar', Login: 'Entrar',
Login_error: 'Suas credenciais foram rejeitadas. Tente novamente por favor!', Login_error: 'Suas credenciais foram rejeitadas. Tente novamente por favor!',
@ -337,6 +343,7 @@ export default {
Take_a_photo: 'Tirar uma foto', Take_a_photo: 'Tirar uma foto',
Take_a_video: 'Gravar um vídeo', Take_a_video: 'Gravar um vídeo',
Terms_of_Service: ' Termos de Serviço ', Terms_of_Service: ' Termos de Serviço ',
Theme: 'Tema',
The_URL_is_invalid: 'A URL fornecida é inválida ou incapaz de estabelecer uma conexão segura.\n{{contact}}', The_URL_is_invalid: 'A URL fornecida é inválida ou incapaz de estabelecer uma conexão segura.\n{{contact}}',
There_was_an_error_while_action: 'Aconteceu um erro {{action}}!', There_was_an_error_while_action: 'Aconteceu um erro {{action}}!',
This_room_is_blocked: 'Este quarto está bloqueado', This_room_is_blocked: 'Este quarto está bloqueado',

View File

@ -3,11 +3,19 @@ import { View, Linking, BackHandler } from 'react-native';
import { createAppContainer, createSwitchNavigator } from 'react-navigation'; import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack'; import { createStackNavigator } from 'react-navigation-stack';
import { createDrawerNavigator } from 'react-navigation-drawer'; import { createDrawerNavigator } from 'react-navigation-drawer';
import { AppearanceProvider } from 'react-native-appearance';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import RNUserDefaults from 'rn-user-defaults';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
import KeyCommands, { KeyCommandsEmitter } from 'react-native-keycommands'; import KeyCommands, { KeyCommandsEmitter } from 'react-native-keycommands';
import {
defaultTheme,
newThemeState,
subscribeTheme,
unsubscribeTheme
} from './utils/theme';
import EventEmitter from './utils/events'; import EventEmitter from './utils/events';
import { appInit } from './actions'; import { appInit } from './actions';
import { deepLinkingOpen } from './actions/deepLinking'; import { deepLinkingOpen } from './actions/deepLinking';
@ -17,13 +25,14 @@ import parseQuery from './lib/methods/helpers/parseQuery';
import { initializePushNotifications, onNotification } from './notifications/push'; import { initializePushNotifications, onNotification } from './notifications/push';
import store from './lib/createStore'; import store from './lib/createStore';
import NotificationBadge from './notifications/inApp'; import NotificationBadge from './notifications/inApp';
import { defaultHeader, onNavigationStateChange } from './utils/navigation'; import { defaultHeader, onNavigationStateChange, cardStyle } from './utils/navigation';
import { loggerConfig, analytics } from './utils/log'; import { loggerConfig, analytics } from './utils/log';
import Toast from './containers/Toast'; import Toast from './containers/Toast';
import RocketChat from './lib/rocketchat'; import { ThemeContext } from './theme';
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
import { MIN_WIDTH_SPLIT_LAYOUT } from './constants/tablet'; import { MIN_WIDTH_SPLIT_LAYOUT } from './constants/tablet';
import { import {
isTablet, isSplited, isIOS, setWidth isTablet, isSplited, isIOS, setWidth, supportSystemTheme
} from './utils/deviceInfo'; } from './utils/deviceInfo';
import { KEY_COMMAND } from './commands'; import { KEY_COMMAND } from './commands';
import Tablet, { initTabletNav } from './tablet'; import Tablet, { initTabletNav } from './tablet';
@ -74,7 +83,8 @@ const OutsideStack = createStackNavigator({
getScreen: () => require('./views/LegalView').default getScreen: () => require('./views/LegalView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const AuthenticationWebViewStack = createStackNavigator({ const AuthenticationWebViewStack = createStackNavigator({
@ -82,7 +92,8 @@ const AuthenticationWebViewStack = createStackNavigator({
getScreen: () => require('./views/AuthenticationWebView').default getScreen: () => require('./views/AuthenticationWebView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const OutsideStackModal = createStackNavigator({ const OutsideStackModal = createStackNavigator({
@ -91,7 +102,8 @@ const OutsideStackModal = createStackNavigator({
}, },
{ {
mode: 'modal', mode: 'modal',
headerMode: 'none' headerMode: 'none',
cardStyle
}); });
const RoomRoutes = { const RoomRoutes = {
@ -146,14 +158,16 @@ const ChatsStack = createStackNavigator({
}, },
...RoomRoutes ...RoomRoutes
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
// Inside // Inside
const RoomStack = createStackNavigator({ const RoomStack = createStackNavigator({
...RoomRoutes ...RoomRoutes
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
ChatsStack.navigationOptions = ({ navigation }) => { ChatsStack.navigationOptions = ({ navigation }) => {
@ -171,7 +185,8 @@ const ProfileStack = createStackNavigator({
getScreen: () => require('./views/ProfileView').default getScreen: () => require('./views/ProfileView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
ProfileStack.navigationOptions = ({ navigation }) => { ProfileStack.navigationOptions = ({ navigation }) => {
@ -190,9 +205,13 @@ const SettingsStack = createStackNavigator({
}, },
LanguageView: { LanguageView: {
getScreen: () => require('./views/LanguageView').default getScreen: () => require('./views/LanguageView').default
},
ThemeView: {
getScreen: () => require('./views/ThemeView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const AdminPanelStack = createStackNavigator({ const AdminPanelStack = createStackNavigator({
@ -200,7 +219,8 @@ const AdminPanelStack = createStackNavigator({
getScreen: () => require('./views/AdminPanelView').default getScreen: () => require('./views/AdminPanelView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
SettingsStack.navigationOptions = ({ navigation }) => { SettingsStack.navigationOptions = ({ navigation }) => {
@ -234,7 +254,8 @@ const NewMessageStack = createStackNavigator({
getScreen: () => require('./views/CreateChannelView').default getScreen: () => require('./views/CreateChannelView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const InsideStackModal = createStackNavigator({ const InsideStackModal = createStackNavigator({
@ -246,13 +267,17 @@ const InsideStackModal = createStackNavigator({
}, },
{ {
mode: 'modal', mode: 'modal',
headerMode: 'none' headerMode: 'none',
cardStyle
}); });
const SetUsernameStack = createStackNavigator({ const SetUsernameStack = createStackNavigator({
SetUsernameView: { SetUsernameView: {
getScreen: () => require('./views/SetUsernameView').default getScreen: () => require('./views/SetUsernameView').default
} }
},
{
cardStyle
}); });
class CustomInsideStack extends React.Component { class CustomInsideStack extends React.Component {
@ -305,7 +330,8 @@ const MessagesStack = createStackNavigator({
getScreen: () => require('./views/CreateChannelView').default getScreen: () => require('./views/CreateChannelView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const DirectoryStack = createStackNavigator({ const DirectoryStack = createStackNavigator({
@ -313,7 +339,8 @@ const DirectoryStack = createStackNavigator({
getScreen: () => require('./views/DirectoryView').default getScreen: () => require('./views/DirectoryView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const SidebarStack = createStackNavigator({ const SidebarStack = createStackNavigator({
@ -327,7 +354,8 @@ const SidebarStack = createStackNavigator({
getScreen: () => require('./views/AdminPanelView').default getScreen: () => require('./views/AdminPanelView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const RoomActionsStack = createStackNavigator({ const RoomActionsStack = createStackNavigator({
@ -362,7 +390,8 @@ const RoomActionsStack = createStackNavigator({
getScreen: () => require('./views/NotificationPreferencesView').default getScreen: () => require('./views/NotificationPreferencesView').default
} }
}, { }, {
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
@ -431,12 +460,13 @@ class CustomNotificationStack extends React.Component {
static router = InsideStackModal.router; static router = InsideStackModal.router;
static propTypes = { static propTypes = {
navigation: PropTypes.object navigation: PropTypes.object,
screenProps: PropTypes.object
} }
render() { render() {
const { navigation } = this.props; const { navigation, screenProps } = this.props;
return <NotificationBadge navigation={navigation} />; return <NotificationBadge navigation={navigation} screenProps={screenProps} />;
} }
} }
@ -468,7 +498,12 @@ export default class Root extends React.Component {
this.state = { this.state = {
split: false, split: false,
inside: false, inside: false,
showModal: false showModal: false,
theme: defaultTheme(),
themePreferences: {
currentTheme: supportSystemTheme() ? 'automatic' : 'light',
darkLevel: 'dark'
}
}; };
if (isTablet) { if (isTablet) {
this.initTablet(); this.initTablet();
@ -500,12 +535,19 @@ export default class Root extends React.Component {
componentWillUnmount() { componentWillUnmount() {
clearTimeout(this.listenerTimeout); clearTimeout(this.listenerTimeout);
unsubscribeTheme();
if (this.onKeyCommands && this.onKeyCommands.remove) { if (this.onKeyCommands && this.onKeyCommands.remove) {
this.onKeyCommands.remove(); this.onKeyCommands.remove();
} }
} }
init = async() => { init = async() => {
if (isIOS) {
await RNUserDefaults.setName('group.ios.chat.rocket');
}
RNUserDefaults.objectForKey(THEME_PREFERENCES_KEY).then(this.setTheme);
const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]); const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]);
const parsedDeepLinkingURL = parseDeepLinking(deepLinking); const parsedDeepLinkingURL = parseDeepLinking(deepLinking);
if (notification) { if (notification) {
@ -517,6 +559,15 @@ export default class Root extends React.Component {
} }
} }
setTheme = (newTheme = {}) => {
// change theme state
this.setState(prevState => newThemeState(prevState, newTheme), () => {
const { themePreferences } = this.state;
// subscribe to Appearance changes
subscribeTheme(themePreferences, this.setTheme);
});
}
initTablet = async() => { initTablet = async() => {
initTabletNav(args => this.setState(args)); initTabletNav(args => this.setState(args));
await KeyCommands.setKeyCommands([]); await KeyCommands.setKeyCommands([]);
@ -547,14 +598,14 @@ export default class Root extends React.Component {
closeModal = () => this.setState({ showModal: false }); closeModal = () => this.setState({ showModal: false });
render() { render() {
const { split } = this.state; const { split, themePreferences, theme } = this.state;
let content = ( let content = (
<App <App
ref={(navigatorRef) => { ref={(navigatorRef) => {
Navigation.setTopLevelNavigator(navigatorRef); Navigation.setTopLevelNavigator(navigatorRef);
}} }}
screenProps={{ split }} screenProps={{ split, theme }}
onNavigationStateChange={onNavigationStateChange} onNavigationStateChange={onNavigationStateChange}
/> />
); );
@ -564,6 +615,7 @@ export default class Root extends React.Component {
content = ( content = (
<SplitContext.Provider value={{ split }}> <SplitContext.Provider value={{ split }}>
<Tablet <Tablet
theme={theme}
tablet={split} tablet={split}
inside={inside} inside={inside}
showModal={showModal} showModal={showModal}
@ -576,9 +628,19 @@ export default class Root extends React.Component {
); );
} }
return ( return (
<AppearanceProvider>
<Provider store={store}> <Provider store={store}>
<ThemeContext.Provider
value={{
theme,
themePreferences,
setTheme: this.setTheme
}}
>
{content} {content}
</ThemeContext.Provider>
</Provider> </Provider>
</AppearanceProvider>
); );
} }
} }

View File

@ -51,6 +51,7 @@ import I18n from '../i18n';
const TOKEN_KEY = 'reactnativemeteor_usertoken'; const TOKEN_KEY = 'reactnativemeteor_usertoken';
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY'; const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
export const MARKDOWN_KEY = 'RC_MARKDOWN_KEY'; export const MARKDOWN_KEY = 'RC_MARKDOWN_KEY';
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY'; export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
const returnAnArray = obj => obj || []; const returnAnArray = obj => obj || [];
const MIN_ROCKETCHAT_VERSION = '0.70.0'; const MIN_ROCKETCHAT_VERSION = '0.70.0';

View File

@ -10,11 +10,12 @@ import Touchable from 'react-native-platform-touchable';
import { isNotch, isIOS, isTablet } from '../../utils/deviceInfo'; import { isNotch, isIOS, isTablet } from '../../utils/deviceInfo';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { COLOR_BACKGROUND_NOTIFICATION, COLOR_SEPARATOR, COLOR_TEXT } from '../../constants/colors'; import { themes } from '../../constants/colors';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import { removeNotification as removeNotificationAction } from '../../actions/notification'; import { removeNotification as removeNotificationAction } from '../../actions/notification';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { ROW_HEIGHT } from '../../presentation/RoomItem'; import { ROW_HEIGHT } from '../../presentation/RoomItem';
import { withTheme } from '../../theme';
const AVATAR_SIZE = 48; const AVATAR_SIZE = 48;
const ANIMATION_DURATION = 300; const ANIMATION_DURATION = 300;
@ -38,10 +39,8 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
position: 'absolute', position: 'absolute',
zIndex: 2, zIndex: 2,
backgroundColor: COLOR_BACKGROUND_NOTIFICATION,
width: '100%', width: '100%',
borderBottomWidth: StyleSheet.hairlineWidth, borderBottomWidth: StyleSheet.hairlineWidth
borderColor: COLOR_SEPARATOR
}, },
content: { content: {
flex: 1, flex: 1,
@ -57,17 +56,14 @@ const styles = StyleSheet.create({
roomName: { roomName: {
fontSize: 17, fontSize: 17,
lineHeight: 20, lineHeight: 20,
...sharedStyles.textColorNormal,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
message: { message: {
fontSize: 14, fontSize: 14,
lineHeight: 17, lineHeight: 17,
...sharedStyles.textRegular, ...sharedStyles.textRegular
...sharedStyles.textColorNormal
}, },
close: { close: {
color: COLOR_TEXT,
marginLeft: 10 marginLeft: 10
} }
}); });
@ -80,7 +76,8 @@ class NotificationBadge extends React.Component {
userId: PropTypes.string, userId: PropTypes.string,
notification: PropTypes.object, notification: PropTypes.object,
window: PropTypes.object, window: PropTypes.object,
removeNotification: PropTypes.func removeNotification: PropTypes.func,
theme: PropTypes.string
} }
constructor(props) { constructor(props) {
@ -91,8 +88,11 @@ class NotificationBadge extends React.Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
const { notification: nextNotification } = nextProps; const { notification: nextNotification } = nextProps;
const { const {
notification: { payload }, window notification: { payload }, window, theme
} = this.props; } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (!equal(nextNotification.payload, payload)) { if (!equal(nextNotification.payload, payload)) {
return true; return true;
} }
@ -173,7 +173,7 @@ class NotificationBadge extends React.Component {
render() { render() {
const { const {
baseUrl, token, userId, notification, window baseUrl, token, userId, notification, window, theme
} = this.props; } = this.props;
const { message, payload } = notification; const { message, payload } = notification;
const { type } = payload; const { type } = payload;
@ -194,7 +194,16 @@ class NotificationBadge extends React.Component {
outputRange: [-top - ROW_HEIGHT, top] outputRange: [-top - ROW_HEIGHT, top]
}); });
return ( return (
<Animated.View style={[styles.container, { transform: [{ translateY }] }]}> <Animated.View
style={[
styles.container,
{
transform: [{ translateY }],
backgroundColor: themes[theme].focusedBackground,
borderColor: themes[theme].separatorColor
}
]}
>
<Touchable <Touchable
style={styles.content} style={styles.content}
onPress={this.goToRoom} onPress={this.goToRoom}
@ -204,13 +213,13 @@ class NotificationBadge extends React.Component {
<> <>
<Avatar text={name} size={AVATAR_SIZE} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} /> <Avatar text={name} size={AVATAR_SIZE} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} />
<View style={styles.inner}> <View style={styles.inner}>
<Text style={styles.roomName} numberOfLines={1}>{name}</Text> <Text style={[styles.roomName, { color: themes[theme].titleText }]} numberOfLines={1}>{name}</Text>
<Text style={styles.message} numberOfLines={1}>{message}</Text> <Text style={[styles.message, { color: themes[theme].titleText }]} numberOfLines={1}>{message}</Text>
</View> </View>
</> </>
</Touchable> </Touchable>
<TouchableOpacity onPress={this.hide}> <TouchableOpacity onPress={this.hide}>
<CustomIcon name='circle-cross' style={styles.close} size={20} /> <CustomIcon name='circle-cross' style={[styles.close, { color: themes[theme].titleText }]} size={20} />
</TouchableOpacity> </TouchableOpacity>
</Animated.View> </Animated.View>
); );
@ -228,4 +237,4 @@ const mapDispatchToProps = dispatch => ({
removeNotification: () => dispatch(removeNotificationAction()) removeNotification: () => dispatch(removeNotificationAction())
}); });
export default responsive(connect(mapStateToProps, mapDispatchToProps)(NotificationBadge)); export default responsive(connect(mapStateToProps, mapDispatchToProps)(withTheme(NotificationBadge)));

View File

@ -2,25 +2,31 @@ import React from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Avatar from '../../containers/Avatar';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import Avatar from '../../containers/Avatar';
import RoomTypeIcon from '../../containers/RoomTypeIcon'; import RoomTypeIcon from '../../containers/RoomTypeIcon';
import styles, { ROW_HEIGHT } from './styles'; import styles, { ROW_HEIGHT } from './styles';
import { themes } from '../../constants/colors';
export { ROW_HEIGHT }; export { ROW_HEIGHT };
const DirectoryItemLabel = React.memo(({ text }) => { const DirectoryItemLabel = React.memo(({ text, theme }) => {
if (!text) { if (!text) {
return null; return null;
} }
return <Text style={styles.directoryItemLabel}>{text}</Text>; return <Text style={[styles.directoryItemLabel, { color: themes[theme].auxiliaryText }]}>{text}</Text>;
}); });
const DirectoryItem = ({ const DirectoryItem = ({
title, description, avatar, onPress, testID, style, baseUrl, user, rightLabel, type title, description, avatar, onPress, testID, style, baseUrl, user, rightLabel, type, theme
}) => ( }) => (
<Touch onPress={onPress} style={styles.directoryItemButton} testID={testID}> <Touch
<View style={[styles.directoryItemContainer, style]}> onPress={onPress}
style={{ backgroundColor: themes[theme].backgroundColor }}
testID={testID}
theme={theme}
>
<View style={[styles.directoryItemContainer, styles.directoryItemButton, style]}>
<Avatar <Avatar
text={avatar} text={avatar}
size={30} size={30}
@ -32,12 +38,12 @@ const DirectoryItem = ({
/> />
<View style={styles.directoryItemTextContainer}> <View style={styles.directoryItemTextContainer}>
<View style={styles.directoryItemTextTitle}> <View style={styles.directoryItemTextTitle}>
<RoomTypeIcon type={type} /> <RoomTypeIcon type={type} theme={theme} />
<Text style={styles.directoryItemName} numberOfLines={1}>{title}</Text> <Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</Text>
</View> </View>
{ description ? <Text style={styles.directoryItemUsername} numberOfLines={1}>{description}</Text> : null } { description ? <Text style={[styles.directoryItemUsername, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{description}</Text> : null }
</View> </View>
<DirectoryItemLabel text={rightLabel} /> <DirectoryItemLabel text={rightLabel} theme={theme} />
</View> </View>
</Touch> </Touch>
); );
@ -55,11 +61,13 @@ DirectoryItem.propTypes = {
onPress: PropTypes.func.isRequired, onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired, testID: PropTypes.string.isRequired,
style: PropTypes.any, style: PropTypes.any,
rightLabel: PropTypes.string rightLabel: PropTypes.string,
theme: PropTypes.string
}; };
DirectoryItemLabel.propTypes = { DirectoryItemLabel.propTypes = {
text: PropTypes.string text: PropTypes.string,
theme: PropTypes.string
}; };
export default DirectoryItem; export default DirectoryItem;

View File

@ -1,14 +1,12 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { COLOR_WHITE } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
export const ROW_HEIGHT = 54; export const ROW_HEIGHT = 54;
export default StyleSheet.create({ export default StyleSheet.create({
directoryItemButton: { directoryItemButton: {
height: ROW_HEIGHT, height: ROW_HEIGHT
backgroundColor: COLOR_WHITE
}, },
directoryItemContainer: { directoryItemContainer: {
flex: 1, flex: 1,
@ -32,18 +30,15 @@ export default StyleSheet.create({
directoryItemName: { directoryItemName: {
flex: 1, flex: 1,
fontSize: 17, fontSize: 17,
...sharedStyles.textMedium, ...sharedStyles.textMedium
...sharedStyles.textColorNormal
}, },
directoryItemUsername: { directoryItemUsername: {
fontSize: 14, fontSize: 14,
...sharedStyles.textRegular, ...sharedStyles.textRegular
...sharedStyles.textColorDescription
}, },
directoryItemLabel: { directoryItemLabel: {
fontSize: 14, fontSize: 14,
paddingLeft: 10, paddingLeft: 10,
...sharedStyles.textRegular, ...sharedStyles.textRegular
...sharedStyles.textColorDescription
} }
}); });

View File

@ -6,9 +6,10 @@ import PropTypes from 'prop-types';
import I18n from '../../i18n'; import I18n from '../../i18n';
import styles, { ACTION_WIDTH, LONG_SWIPE } from './styles'; import styles, { ACTION_WIDTH, LONG_SWIPE } from './styles';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { themes } from '../../constants/colors';
export const LeftActions = React.memo(({ export const LeftActions = React.memo(({
transX, isRead, width, onToggleReadPress theme, transX, isRead, width, onToggleReadPress
}) => { }) => {
const translateX = transX.interpolate({ const translateX = transX.interpolate({
inputRange: [0, ACTION_WIDTH], inputRange: [0, ACTION_WIDTH],
@ -30,7 +31,8 @@ export const LeftActions = React.memo(({
{ {
right: width - ACTION_WIDTH, right: width - ACTION_WIDTH,
width, width,
transform: [{ translateX }] transform: [{ translateX }],
backgroundColor: themes[theme].tintColor
} }
]} ]}
> >
@ -46,7 +48,7 @@ export const LeftActions = React.memo(({
<RectButton style={styles.actionButton} onPress={onToggleReadPress}> <RectButton style={styles.actionButton} onPress={onToggleReadPress}>
<> <>
<CustomIcon size={20} name={isRead ? 'flag' : 'check'} color='white' /> <CustomIcon size={20} name={isRead ? 'flag' : 'check'} color='white' />
<Text style={styles.actionText}>{I18n.t(isRead ? 'Unread' : 'Read')}</Text> <Text style={[styles.actionText, { color: themes[theme].buttonText }]}>{I18n.t(isRead ? 'Unread' : 'Read')}</Text>
</> </>
</RectButton> </RectButton>
</Animated.View> </Animated.View>
@ -56,7 +58,7 @@ export const LeftActions = React.memo(({
}); });
export const RightActions = React.memo(({ export const RightActions = React.memo(({
transX, favorite, width, toggleFav, onHidePress transX, favorite, width, toggleFav, onHidePress, theme
}) => { }) => {
const translateXFav = transX.interpolate({ const translateXFav = transX.interpolate({
inputRange: [-width / 2, -ACTION_WIDTH * 2, 0], inputRange: [-width / 2, -ACTION_WIDTH * 2, 0],
@ -82,14 +84,15 @@ export const RightActions = React.memo(({
styles.actionRightButtonContainer, styles.actionRightButtonContainer,
{ {
width, width,
transform: [{ translateX: translateXFav }] transform: [{ translateX: translateXFav }],
backgroundColor: themes[theme].hideBackground
} }
]} ]}
> >
<RectButton style={[styles.actionButton, { backgroundColor: '#ffbb00' }]} onPress={toggleFav}> <RectButton style={[styles.actionButton, { backgroundColor: themes[theme].favoriteBackground }]} onPress={toggleFav}>
<> <>
<CustomIcon size={20} name={favorite ? 'Star-filled' : 'star'} color='white' /> <CustomIcon size={20} name={favorite ? 'Star-filled' : 'star'} color={themes[theme].buttonText} />
<Text style={styles.actionText}>{I18n.t(favorite ? 'Unfavorite' : 'Favorite')}</Text> <Text style={[styles.actionText, { color: themes[theme].buttonText }]}>{I18n.t(favorite ? 'Unfavorite' : 'Favorite')}</Text>
</> </>
</RectButton> </RectButton>
</Animated.View> </Animated.View>
@ -102,10 +105,10 @@ export const RightActions = React.memo(({
} }
]} ]}
> >
<RectButton style={[styles.actionButton, { backgroundColor: '#54585e' }]} onPress={onHidePress}> <RectButton style={[styles.actionButton, { backgroundColor: themes[theme].hideBackground }]} onPress={onHidePress}>
<> <>
<CustomIcon size={20} name='eye-off' color='white' /> <CustomIcon size={20} name='eye-off' color={themes[theme].buttonText} />
<Text style={styles.actionText}>{I18n.t('Hide')}</Text> <Text style={[styles.actionText, { color: themes[theme].buttonText }]}>{I18n.t('Hide')}</Text>
</> </>
</RectButton> </RectButton>
</Animated.View> </Animated.View>
@ -114,6 +117,7 @@ export const RightActions = React.memo(({
}); });
LeftActions.propTypes = { LeftActions.propTypes = {
theme: PropTypes.string,
transX: PropTypes.object, transX: PropTypes.object,
isRead: PropTypes.bool, isRead: PropTypes.bool,
width: PropTypes.number, width: PropTypes.number,
@ -121,6 +125,7 @@ LeftActions.propTypes = {
}; };
RightActions.propTypes = { RightActions.propTypes = {
theme: PropTypes.string,
transX: PropTypes.object, transX: PropTypes.object,
favorite: PropTypes.bool, favorite: PropTypes.bool,
width: PropTypes.number, width: PropTypes.number,

View File

@ -6,6 +6,7 @@ import _ from 'lodash';
import I18n from '../../i18n'; import I18n from '../../i18n';
import styles from './styles'; import styles from './styles';
import Markdown from '../../containers/markdown'; import Markdown from '../../containers/markdown';
import { themes } from '../../constants/colors';
const formatMsg = ({ const formatMsg = ({
lastMessage, type, showLastMessage, username lastMessage, type, showLastMessage, username
@ -45,20 +46,22 @@ const formatMsg = ({
const arePropsEqual = (oldProps, newProps) => _.isEqual(oldProps, newProps); const arePropsEqual = (oldProps, newProps) => _.isEqual(oldProps, newProps);
const LastMessage = React.memo(({ const LastMessage = React.memo(({
lastMessage, type, showLastMessage, username, alert lastMessage, type, showLastMessage, username, alert, theme
}) => ( }) => (
<Markdown <Markdown
msg={formatMsg({ msg={formatMsg({
lastMessage, type, showLastMessage, username lastMessage, type, showLastMessage, username
})} })}
style={[styles.markdownText, alert && styles.markdownTextAlert]} style={[styles.markdownText, { color: alert ? themes[theme].bodyText : themes[theme].auxiliaryText }]}
customEmojis={false} customEmojis={false}
numberOfLines={2} numberOfLines={2}
preview preview
theme={theme}
/> />
), arePropsEqual); ), arePropsEqual);
LastMessage.propTypes = { LastMessage.propTypes = {
theme: PropTypes.string,
lastMessage: PropTypes.object, lastMessage: PropTypes.object,
type: PropTypes.string, type: PropTypes.string,
showLastMessage: PropTypes.bool, showLastMessage: PropTypes.bool,

View File

@ -1,16 +1,15 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Animated } from 'react-native'; import { Animated } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Touch from '../../utils/touch';
import { import {
RectButton,
PanGestureHandler,
State
} from 'react-native-gesture-handler';
import styles, {
ACTION_WIDTH, ACTION_WIDTH,
SMALL_SWIPE, SMALL_SWIPE,
LONG_SWIPE LONG_SWIPE
} from './styles'; } from './styles';
import { themes } from '../../constants/colors';
import { LeftActions, RightActions } from './Actions'; import { LeftActions, RightActions } from './Actions';
class Touchable extends React.Component { class Touchable extends React.Component {
@ -25,7 +24,8 @@ class Touchable extends React.Component {
toggleFav: PropTypes.func, toggleFav: PropTypes.func,
toggleRead: PropTypes.func, toggleRead: PropTypes.func,
hideChannel: PropTypes.func, hideChannel: PropTypes.func,
children: PropTypes.element children: PropTypes.element,
theme: PropTypes.string
} }
constructor(props) { constructor(props) {
@ -167,7 +167,7 @@ class Touchable extends React.Component {
render() { render() {
const { const {
testID, isRead, width, favorite, children testID, isRead, width, favorite, children, theme
} = this.props; } = this.props;
return ( return (
@ -183,6 +183,7 @@ class Touchable extends React.Component {
isRead={isRead} isRead={isRead}
width={width} width={width}
onToggleReadPress={this.onToggleReadPress} onToggleReadPress={this.onToggleReadPress}
theme={theme}
/> />
<RightActions <RightActions
transX={this.transX} transX={this.transX}
@ -190,21 +191,23 @@ class Touchable extends React.Component {
width={width} width={width}
toggleFav={this.toggleFav} toggleFav={this.toggleFav}
onHidePress={this.onHidePress} onHidePress={this.onHidePress}
theme={theme}
/> />
<Animated.View <Animated.View
style={{ style={{
transform: [{ translateX: this.transX }] transform: [{ translateX: this.transX }]
}} }}
> >
<RectButton <Touch
onPress={this.onPress} onPress={this.onPress}
activeOpacity={0.8} theme={theme}
underlayColor='#e1e5e8'
testID={testID} testID={testID}
style={styles.button} style={{
backgroundColor: themes[theme].backgroundColor
}}
> >
{children} {children}
</RectButton> </Touch>
</Animated.View> </Animated.View>
</Animated.View> </Animated.View>

View File

@ -5,14 +5,17 @@ import Status from '../../containers/Status/Status';
import RoomTypeIcon from '../../containers/RoomTypeIcon'; import RoomTypeIcon from '../../containers/RoomTypeIcon';
import styles from './styles'; import styles from './styles';
const TypeIcon = React.memo(({ type, prid, status }) => { const TypeIcon = React.memo(({
theme, type, prid, status
}) => {
if (type === 'd') { if (type === 'd') {
return <Status style={styles.status} size={10} status={status} />; return <Status style={styles.status} size={10} status={status} />;
} }
return <RoomTypeIcon type={prid ? 'discussion' : type} />; return <RoomTypeIcon theme={theme} type={prid ? 'discussion' : type} />;
}); });
TypeIcon.propTypes = { TypeIcon.propTypes = {
theme: PropTypes.string,
type: PropTypes.string, type: PropTypes.string,
status: PropTypes.string, status: PropTypes.string,
prid: PropTypes.string prid: PropTypes.string

View File

@ -3,8 +3,11 @@ import PropTypes from 'prop-types';
import { View, Text } from 'react-native'; import { View, Text } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors';
const UnreadBadge = React.memo(({ unread, userMentions, type }) => { const UnreadBadge = React.memo(({
theme, unread, userMentions, type
}) => {
if (!unread || unread <= 0) { if (!unread || unread <= 0) {
return; return;
} }
@ -14,13 +17,25 @@ const UnreadBadge = React.memo(({ unread, userMentions, type }) => {
const mentioned = userMentions > 0 && type !== 'd'; const mentioned = userMentions > 0 && type !== 'd';
return ( return (
<View style={[styles.unreadNumberContainer, mentioned && styles.unreadMentionedContainer]}> <View
<Text style={[styles.unreadText, mentioned && styles.unreadMentionedText]}>{ unread }</Text> style={[
styles.unreadNumberContainer,
{ backgroundColor: mentioned ? themes[theme].tintColor : themes[theme].borderColor }
]}
>
<Text
style={[
styles.unreadText,
{ color: mentioned ? themes[theme].buttonText : themes[theme].bodyText }
]}
>{ unread }
</Text>
</View> </View>
); );
}); });
UnreadBadge.propTypes = { UnreadBadge.propTypes = {
theme: PropTypes.string,
unread: PropTypes.number, unread: PropTypes.number,
userMentions: PropTypes.number, userMentions: PropTypes.number,
type: PropTypes.string type: PropTypes.string

View File

@ -11,6 +11,7 @@ import TypeIcon from './TypeIcon';
import LastMessage from './LastMessage'; import LastMessage from './LastMessage';
import { capitalize, formatDate } from '../../utils/room'; import { capitalize, formatDate } from '../../utils/room';
import Touchable from './Touchable'; import Touchable from './Touchable';
import { themes } from '../../constants/colors';
export { ROW_HEIGHT }; export { ROW_HEIGHT };
@ -24,7 +25,8 @@ const attrs = [
'width', 'width',
'isRead', 'isRead',
'favorite', 'favorite',
'status' 'status',
'theme'
]; ];
const arePropsEqual = (oldProps, newProps) => { const arePropsEqual = (oldProps, newProps) => {
@ -37,7 +39,7 @@ const arePropsEqual = (oldProps, newProps) => {
}; };
const RoomItem = React.memo(({ const RoomItem = React.memo(({
onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar onPress, width, favorite, toggleFav, isRead, rid, toggleRead, hideChannel, testID, unread, userMentions, name, _updatedAt, alert, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, hideUnreadStatus, lastMessage, status, avatar, theme
}) => { }) => {
const date = formatDate(_updatedAt); const date = formatDate(_updatedAt);
@ -68,6 +70,7 @@ const RoomItem = React.memo(({
hideChannel={hideChannel} hideChannel={hideChannel}
testID={testID} testID={testID}
type={type} type={type}
theme={theme}
> >
<View <View
style={styles.container} style={styles.container}
@ -82,18 +85,27 @@ const RoomItem = React.memo(({
userId={userId} userId={userId}
token={token} token={token}
/> />
<View style={styles.centerContainer}> <View
style={[
styles.centerContainer,
{
borderColor: themes[theme].separatorColor
}
]}
>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<TypeIcon <TypeIcon
type={type} type={type}
id={id} id={id}
prid={prid} prid={prid}
status={status} status={status}
theme={theme}
/> />
<Text <Text
style={[ style={[
styles.title, styles.title,
alert && !hideUnreadStatus && styles.alert alert && !hideUnreadStatus && styles.alert,
{ color: themes[theme].titleText }
]} ]}
ellipsizeMode='tail' ellipsizeMode='tail'
numberOfLines={1} numberOfLines={1}
@ -104,7 +116,19 @@ const RoomItem = React.memo(({
<Text <Text
style={[ style={[
styles.date, styles.date,
alert && !hideUnreadStatus && styles.updateAlert {
color:
themes[theme]
.auxiliaryText
},
alert && !hideUnreadStatus && [
styles.updateAlert,
{
color:
themes[theme]
.tintColor
}
]
]} ]}
ellipsizeMode='tail' ellipsizeMode='tail'
numberOfLines={1} numberOfLines={1}
@ -120,11 +144,13 @@ const RoomItem = React.memo(({
showLastMessage={showLastMessage} showLastMessage={showLastMessage}
username={username} username={username}
alert={alert && !hideUnreadStatus} alert={alert && !hideUnreadStatus}
theme={theme}
/> />
<UnreadBadge <UnreadBadge
unread={unread} unread={unread}
userMentions={userMentions} userMentions={userMentions}
type={type} type={type}
theme={theme}
/> />
</View> </View>
</View> </View>
@ -160,7 +186,8 @@ RoomItem.propTypes = {
toggleRead: PropTypes.func, toggleRead: PropTypes.func,
hideChannel: PropTypes.func, hideChannel: PropTypes.func,
avatar: PropTypes.bool, avatar: PropTypes.bool,
hideUnreadStatus: PropTypes.bool hideUnreadStatus: PropTypes.bool,
theme: PropTypes.string
}; };
RoomItem.defaultProps = { RoomItem.defaultProps = {
@ -169,7 +196,10 @@ RoomItem.defaultProps = {
}; };
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
status: state.meteor.connected && ownProps.type === 'd' ? state.activeUsers[ownProps.id] : 'offline' status:
state.meteor.connected && ownProps.type === 'd'
? state.activeUsers[ownProps.id]
: 'offline'
}); });
export default connect(mapStateToProps)(RoomItem); export default connect(mapStateToProps)(RoomItem);

View File

@ -1,9 +1,6 @@
import { StyleSheet, PixelRatio } from 'react-native'; import { StyleSheet, PixelRatio } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import {
COLOR_SEPARATOR, COLOR_PRIMARY, COLOR_WHITE, COLOR_UNREAD, COLOR_TEXT
} from '../../constants/colors';
export const ROW_HEIGHT = 75 * PixelRatio.getFontScale(); export const ROW_HEIGHT = 75 * PixelRatio.getFontScale();
export const ACTION_WIDTH = 80; export const ACTION_WIDTH = 80;
@ -17,21 +14,16 @@ export default StyleSheet.create({
paddingLeft: 14, paddingLeft: 14,
height: ROW_HEIGHT height: ROW_HEIGHT
}, },
button: {
backgroundColor: COLOR_WHITE
},
centerContainer: { centerContainer: {
flex: 1, flex: 1,
paddingVertical: 10, paddingVertical: 10,
paddingRight: 14, paddingRight: 14,
borderBottomWidth: StyleSheet.hairlineWidth, borderBottomWidth: StyleSheet.hairlineWidth
borderColor: COLOR_SEPARATOR
}, },
title: { title: {
flex: 1, flex: 1,
fontSize: 17, fontSize: 17,
lineHeight: 20, lineHeight: 20,
...sharedStyles.textColorNormal,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
alert: { alert: {
@ -51,11 +43,9 @@ export default StyleSheet.create({
date: { date: {
fontSize: 13, fontSize: 13,
marginLeft: 4, marginLeft: 4,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
updateAlert: { updateAlert: {
color: COLOR_PRIMARY,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
unreadNumberContainer: { unreadNumberContainer: {
@ -64,25 +54,17 @@ export default StyleSheet.create({
paddingVertical: 3, paddingVertical: 3,
paddingHorizontal: 5, paddingHorizontal: 5,
borderRadius: 10.5, borderRadius: 10.5,
backgroundColor: COLOR_UNREAD,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginLeft: 10 marginLeft: 10
}, },
unreadMentionedContainer: {
backgroundColor: COLOR_PRIMARY
},
unreadText: { unreadText: {
color: COLOR_TEXT,
overflow: 'hidden', overflow: 'hidden',
fontSize: 13, fontSize: 13,
...sharedStyles.textMedium, ...sharedStyles.textMedium,
letterSpacing: 0.56, letterSpacing: 0.56,
textAlign: 'center' textAlign: 'center'
}, },
unreadMentionedText: {
color: COLOR_WHITE
},
status: { status: {
marginRight: 7, marginRight: 7,
marginTop: 3 marginTop: 3
@ -91,11 +73,7 @@ export default StyleSheet.create({
flex: 1, flex: 1,
fontSize: 14, fontSize: 14,
lineHeight: 17, lineHeight: 17,
...sharedStyles.textRegular, ...sharedStyles.textRegular
...sharedStyles.textColorDescription
},
markdownTextAlert: {
...sharedStyles.textColorNormal
}, },
avatar: { avatar: {
marginRight: 10 marginRight: 10
@ -110,20 +88,14 @@ export default StyleSheet.create({
height: ROW_HEIGHT height: ROW_HEIGHT
}, },
actionText: { actionText: {
color: COLOR_WHITE,
fontSize: 15, fontSize: 15,
backgroundColor: 'transparent',
justifyContent: 'center', justifyContent: 'center',
marginTop: 4, marginTop: 4,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
actionLeftContainer: {
backgroundColor: COLOR_PRIMARY
},
actionLeftButtonContainer: { actionLeftButtonContainer: {
position: 'absolute', position: 'absolute',
height: ROW_HEIGHT, height: ROW_HEIGHT,
backgroundColor: COLOR_PRIMARY,
justifyContent: 'center', justifyContent: 'center',
top: 0 top: 0
}, },
@ -131,8 +103,7 @@ export default StyleSheet.create({
position: 'absolute', position: 'absolute',
height: ROW_HEIGHT, height: ROW_HEIGHT,
justifyContent: 'center', justifyContent: 'center',
top: 0, top: 0
backgroundColor: '#54585e'
}, },
actionButton: { actionButton: {
width: ACTION_WIDTH, width: ACTION_WIDTH,

View File

@ -2,17 +2,23 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, Text } from 'react-native'; import { View, Text } from 'react-native';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import { RectButton } from 'react-native-gesture-handler';
import Touch from '../../utils/touch';
import Check from '../../containers/Check'; import Check from '../../containers/Check';
import styles, { ROW_HEIGHT } from './styles'; import styles, { ROW_HEIGHT } from './styles';
import { themes } from '../../constants/colors';
export { ROW_HEIGHT }; export { ROW_HEIGHT };
const ServerItem = React.memo(({ const ServerItem = React.memo(({
server, item, onPress, hasCheck server, item, onPress, hasCheck, theme
}) => ( }) => (
<RectButton onPress={onPress} style={styles.serverItem} testID={`rooms-list-header-server-${ item.id }`}> <Touch
onPress={onPress}
style={[styles.serverItem, { backgroundColor: themes[theme].backgroundColor }]}
testID={`rooms-list-header-server-${ item.id }`}
theme={theme}
>
<View style={styles.serverItemContainer}> <View style={styles.serverItemContainer}>
{item.iconURL {item.iconURL
? ( ? (
@ -34,19 +40,20 @@ const ServerItem = React.memo(({
) )
} }
<View style={styles.serverTextContainer}> <View style={styles.serverTextContainer}>
<Text style={styles.serverName}>{item.name || item.id}</Text> <Text style={[styles.serverName, { color: themes[theme].titleText }]}>{item.name || item.id}</Text>
<Text style={styles.serverUrl}>{item.id}</Text> <Text style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}>{item.id}</Text>
</View> </View>
{item.id === server && hasCheck ? <Check /> : null} {item.id === server && hasCheck ? <Check theme={theme} /> : null}
</View> </View>
</RectButton> </Touch>
)); ));
ServerItem.propTypes = { ServerItem.propTypes = {
onPress: PropTypes.func.isRequired, onPress: PropTypes.func.isRequired,
item: PropTypes.object.isRequired, item: PropTypes.object.isRequired,
hasCheck: PropTypes.bool, hasCheck: PropTypes.bool,
server: PropTypes.string server: PropTypes.string,
theme: PropTypes.string
}; };
export default ServerItem; export default ServerItem;

View File

@ -1,14 +1,12 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { COLOR_WHITE } from '../../constants/colors';
export const ROW_HEIGHT = 56; export const ROW_HEIGHT = 56;
export default StyleSheet.create({ export default StyleSheet.create({
serverItem: { serverItem: {
height: ROW_HEIGHT, height: ROW_HEIGHT,
backgroundColor: COLOR_WHITE,
justifyContent: 'center' justifyContent: 'center'
}, },
serverItemContainer: { serverItemContainer: {
@ -28,12 +26,10 @@ export default StyleSheet.create({
}, },
serverName: { serverName: {
fontSize: 18, fontSize: 18,
...sharedStyles.textColorNormal,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
serverUrl: { serverUrl: {
fontSize: 15, fontSize: 15,
...sharedStyles.textColorDescription,
...sharedStyles.textRegular ...sharedStyles.textRegular
} }
}); });

View File

@ -0,0 +1,22 @@
import React from 'react';
import { TextInput } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../constants/colors';
const ThemedTextInput = React.forwardRef(({ style, theme, ...props }, ref) => (
<TextInput
ref={ref}
style={[{ color: themes[theme].titleText }, style]}
placeholderTextColor={themes[theme].auxiliaryText}
keyboardAppearance={theme === 'light' ? 'light' : 'dark'}
{...props}
/>
));
ThemedTextInput.propTypes = {
style: PropTypes.object,
theme: PropTypes.string
};
export default ThemedTextInput;

View File

@ -1,17 +1,17 @@
import React from 'react'; import React from 'react';
import { Text, View, StyleSheet } from 'react-native'; import { Text, View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
import Avatar from '../containers/Avatar'; import Avatar from '../containers/Avatar';
import Touch from '../utils/touch';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { COLOR_PRIMARY, COLOR_WHITE } from '../constants/colors'; import { themes } from '../constants/colors';
import Touch from '../utils/touch';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
button: { button: {
height: 54, height: 54
backgroundColor: COLOR_WHITE
}, },
container: { container: {
flexDirection: 'row' flexDirection: 'row'
@ -27,35 +27,50 @@ const styles = StyleSheet.create({
}, },
name: { name: {
fontSize: 17, fontSize: 17,
...sharedStyles.textMedium, ...sharedStyles.textMedium
...sharedStyles.textColorNormal
}, },
username: { username: {
fontSize: 14, fontSize: 14,
...sharedStyles.textRegular, ...sharedStyles.textRegular
...sharedStyles.textColorDescription
}, },
icon: { icon: {
marginHorizontal: 15, marginHorizontal: 15,
alignSelf: 'center', alignSelf: 'center'
color: COLOR_PRIMARY
} }
}); });
const UserItem = ({ const UserItem = ({
name, username, onPress, testID, onLongPress, style, icon, baseUrl, user name, username, onPress, testID, onLongPress, style, icon, baseUrl, user, theme
}) => ( }) => {
<Touch onPress={onPress} onLongPress={onLongPress} style={styles.button} testID={testID}> const longPress = ({ nativeEvent }) => {
<View style={[styles.container, style]}> if (nativeEvent.state === State.ACTIVE) {
onLongPress();
}
};
return (
<LongPressGestureHandler
onHandlerStateChange={longPress}
minDurationMs={800}
>
<Touch
onPress={onPress}
style={{ backgroundColor: themes[theme].backgroundColor }}
testID={testID}
theme={theme}
>
<View style={[styles.container, styles.button, style]}>
<Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} userId={user.id} token={user.token} /> <Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} userId={user.id} token={user.token} />
<View style={styles.textContainer}> <View style={styles.textContainer}>
<Text style={styles.name}>{name}</Text> <Text style={[styles.name, { color: themes[theme].titleText }]}>{name}</Text>
<Text style={styles.username}>@{username}</Text> <Text style={[styles.username, { color: themes[theme].auxiliaryText }]}>@{username}</Text>
</View> </View>
{icon ? <CustomIcon name={icon} size={22} style={styles.icon} /> : null} {icon ? <CustomIcon name={icon} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null}
</View> </View>
</Touch> </Touch>
</LongPressGestureHandler>
); );
};
UserItem.propTypes = { UserItem.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
@ -69,7 +84,8 @@ UserItem.propTypes = {
testID: PropTypes.string.isRequired, testID: PropTypes.string.isRequired,
onLongPress: PropTypes.func, onLongPress: PropTypes.func,
style: PropTypes.any, style: PropTypes.any,
icon: PropTypes.string icon: PropTypes.string,
theme: PropTypes.string
}; };
export default UserItem; export default UserItem;

View File

@ -10,7 +10,6 @@ import database from '../lib/database';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import { appStart } from '../actions'; import { appStart } from '../actions';
import { isIOS } from '../utils/deviceInfo';
const roomTypes = { const roomTypes = {
channel: 'c', direct: 'd', group: 'p' channel: 'c', direct: 'd', group: 'p'
@ -33,10 +32,6 @@ const handleOpen = function* handleOpen({ params }) {
return; return;
} }
if (isIOS) {
yield RNUserDefaults.setName('group.ios.chat.rocket');
}
let { host } = params; let { host } = params;
if (!/^(http|https)/.test(host)) { if (!/^(http|https)/.test(host)) {
host = `https://${ params.host }`; host = `https://${ params.host }`;

View File

@ -24,7 +24,6 @@ const restore = function* restore() {
try { try {
let hasMigration; let hasMigration;
if (isIOS) { if (isIOS) {
yield RNUserDefaults.setName('group.ios.chat.rocket');
hasMigration = yield AsyncStorage.getItem('hasMigration'); hasMigration = yield AsyncStorage.getItem('hasMigration');
} }

View File

@ -1,16 +1,24 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { createAppContainer, createSwitchNavigator } from 'react-navigation'; import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { AppearanceProvider } from 'react-native-appearance';
import { createStackNavigator } from 'react-navigation-stack'; import { createStackNavigator } from 'react-navigation-stack';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import RNUserDefaults from 'rn-user-defaults'; import RNUserDefaults from 'rn-user-defaults';
import {
defaultTheme,
newThemeState,
subscribeTheme,
unsubscribeTheme
} from './utils/theme';
import Navigation from './lib/ShareNavigation'; import Navigation from './lib/ShareNavigation';
import store from './lib/createStore'; import store from './lib/createStore';
import sharedStyles from './views/Styles'; import sharedStyles from './views/Styles';
import { isNotch, isIOS } from './utils/deviceInfo'; import { isNotch, isIOS, supportSystemTheme } from './utils/deviceInfo';
import { defaultHeader, onNavigationStateChange } from './utils/navigation'; import { defaultHeader, onNavigationStateChange, cardStyle } from './utils/navigation';
import RocketChat from './lib/rocketchat'; import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
import { ThemeContext } from './theme';
const InsideNavigator = createStackNavigator({ const InsideNavigator = createStackNavigator({
ShareListView: { ShareListView: {
@ -24,7 +32,8 @@ const InsideNavigator = createStackNavigator({
} }
}, { }, {
initialRouteName: 'ShareListView', initialRouteName: 'ShareListView',
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const OutsideNavigator = createStackNavigator({ const OutsideNavigator = createStackNavigator({
@ -33,7 +42,8 @@ const OutsideNavigator = createStackNavigator({
} }
}, { }, {
initialRouteName: 'WithoutServersView', initialRouteName: 'WithoutServersView',
defaultNavigationOptions: defaultHeader defaultNavigationOptions: defaultHeader,
cardStyle
}); });
const AppContainer = createAppContainer(createSwitchNavigator({ const AppContainer = createAppContainer(createSwitchNavigator({
@ -51,15 +61,25 @@ class Root extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
isLandscape: false isLandscape: false,
theme: defaultTheme(),
themePreferences: {
currentTheme: supportSystemTheme() ? 'automatic' : 'light',
darkLevel: 'dark'
}
}; };
this.init(); this.init();
} }
componentWillUnmount() {
unsubscribeTheme();
}
init = async() => { init = async() => {
if (isIOS) { if (isIOS) {
await RNUserDefaults.setName('group.ios.chat.rocket'); await RNUserDefaults.setName('group.ios.chat.rocket');
} }
RNUserDefaults.objectForKey(THEME_PREFERENCES_KEY).then(this.setTheme);
const currentServer = await RNUserDefaults.get('currentServer'); const currentServer = await RNUserDefaults.get('currentServer');
const token = await RNUserDefaults.get(RocketChat.TOKEN_KEY); const token = await RNUserDefaults.get(RocketChat.TOKEN_KEY);
@ -71,27 +91,41 @@ class Root extends React.Component {
} }
} }
setTheme = (newTheme = {}) => {
// change theme state
this.setState(prevState => newThemeState(prevState, newTheme), () => {
const { themePreferences } = this.state;
// subscribe to Appearance changes
subscribeTheme(themePreferences, this.setTheme);
});
}
handleLayout = (event) => { handleLayout = (event) => {
const { width, height } = event.nativeEvent.layout; const { width, height } = event.nativeEvent.layout;
this.setState({ isLandscape: width > height }); this.setState({ isLandscape: width > height });
} }
render() { render() {
const { isLandscape } = this.state; const { isLandscape, theme } = this.state;
return ( return (
<AppearanceProvider>
<View <View
style={[sharedStyles.container, isLandscape && isNotch ? sharedStyles.notchLandscapeContainer : {}]} style={[sharedStyles.container, isLandscape && isNotch ? sharedStyles.notchLandscapeContainer : {}]}
onLayout={this.handleLayout} onLayout={this.handleLayout}
> >
<Provider store={store}> <Provider store={store}>
<ThemeContext.Provider value={{ theme }}>
<AppContainer <AppContainer
ref={(navigatorRef) => { ref={(navigatorRef) => {
Navigation.setTopLevelNavigator(navigatorRef); Navigation.setTopLevelNavigator(navigatorRef);
}} }}
onNavigationStateChange={onNavigationStateChange} onNavigationStateChange={onNavigationStateChange}
screenProps={{ theme }}
/> />
</ThemeContext.Provider>
</Provider> </Provider>
</View> </View>
</AppearanceProvider>
); );
} }
} }

View File

@ -12,6 +12,7 @@ import {
import { MAX_SIDEBAR_WIDTH } from './constants/tablet'; import { MAX_SIDEBAR_WIDTH } from './constants/tablet';
import ModalNavigation from './lib/ModalNavigation'; import ModalNavigation from './lib/ModalNavigation';
import { keyCommands, defaultCommands } from './commands'; import { keyCommands, defaultCommands } from './commands';
import { themes } from './constants/colors';
import sharedStyles from './views/Styles'; import sharedStyles from './views/Styles';
@ -144,15 +145,15 @@ export const initTabletNav = (setState) => {
}; };
const Split = ({ const Split = ({
split, tablet, showModal, closeModal, setModalRef split, tablet, showModal, closeModal, setModalRef, theme
}) => { }) => {
if (split) { if (split) {
return ( return (
<> <>
<View style={[sharedStyles.container, sharedStyles.separatorLeft]}> <View style={[sharedStyles.container, sharedStyles.separatorLeft, { borderColor: themes[theme].separatorColor }]}>
<RoomContainer ref={ref => roomRef = ref} screenProps={{ split: tablet }} /> <RoomContainer ref={ref => roomRef = ref} screenProps={{ split: tablet, theme }} />
</View> </View>
<ModalContainer showModal={showModal} closeModal={closeModal} ref={setModalRef} screenProps={{ split: tablet }} /> <ModalContainer showModal={showModal} closeModal={closeModal} ref={setModalRef} screenProps={{ split: tablet, theme }} />
</> </>
); );
} }
@ -160,7 +161,7 @@ const Split = ({
}; };
const Tablet = ({ const Tablet = ({
children, tablet, inside, showModal, closeModal, onLayout children, tablet, theme, inside, showModal, closeModal, onLayout
}) => { }) => {
const setModalRef = (ref) => { const setModalRef = (ref) => {
modalRef = ref; modalRef = ref;
@ -173,8 +174,8 @@ const Tablet = ({
<View style={[sharedStyles.container, split && { maxWidth: MAX_SIDEBAR_WIDTH }]}> <View style={[sharedStyles.container, split && { maxWidth: MAX_SIDEBAR_WIDTH }]}>
{children} {children}
</View> </View>
<Split split={split} tablet={tablet} showModal={showModal} closeModal={closeModal} setModalRef={setModalRef} /> <Split split={split} tablet={tablet} theme={theme} showModal={showModal} closeModal={closeModal} setModalRef={setModalRef} />
<NotificationContainer ref={ref => notificationRef = ref} /> <NotificationContainer ref={ref => notificationRef = ref} screenProps={{ theme }} />
</View> </View>
); );
}; };
@ -184,7 +185,8 @@ Split.propTypes = {
tablet: PropTypes.bool, tablet: PropTypes.bool,
showModal: PropTypes.bool, showModal: PropTypes.bool,
closeModal: PropTypes.func, closeModal: PropTypes.func,
setModalRef: PropTypes.func setModalRef: PropTypes.func,
theme: PropTypes.string
}; };
Tablet.propTypes = { Tablet.propTypes = {
@ -193,7 +195,8 @@ Tablet.propTypes = {
inside: PropTypes.bool, inside: PropTypes.bool,
showModal: PropTypes.bool, showModal: PropTypes.bool,
closeModal: PropTypes.func, closeModal: PropTypes.func,
onLayout: PropTypes.func onLayout: PropTypes.func,
theme: PropTypes.string
}; };
export default Tablet; export default Tablet;

14
app/theme.js Normal file
View File

@ -0,0 +1,14 @@
import React from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
export const ThemeContext = React.createContext(null);
export function withTheme(Component) {
const ThemedComponent = props => (
<ThemeContext.Consumer>
{contexts => <Component {...props} {...contexts} />}
</ThemeContext.Consumer>
);
hoistNonReactStatics(ThemedComponent, Component);
return ThemedComponent;
}

View File

@ -12,6 +12,12 @@ export const getReadableVersion = DeviceInfo.getReadableVersion();
export const getBundleId = DeviceInfo.getBundleId(); export const getBundleId = DeviceInfo.getBundleId();
export const getDeviceModel = DeviceInfo.getModel(); export const getDeviceModel = DeviceInfo.getModel();
// Theme is supported by system on iOS 13+ or Android 10+
export const supportSystemTheme = () => {
const systemVersion = parseInt(DeviceInfo.getSystemVersion(), 10);
return systemVersion >= (isIOS ? 13 : 10);
};
// Tablet info // Tablet info
export const isTablet = DeviceInfo.isTablet(); export const isTablet = DeviceInfo.isTablet();

Some files were not shown because too many files have changed in this diff Show More