[NEW] Themes (#1298)
This commit is contained in:
parent
ea296d1007
commit
7b51df377d
|
@ -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 |
|
@ -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'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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'
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 => ({
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
{children}
|
return (
|
||||||
</MarkdownTable>
|
<MarkdownTable numColumns={numColumns} theme={theme}>
|
||||||
);
|
{children}
|
||||||
|
</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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
114
app/index.js
114
app/index.js
|
@ -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 (
|
||||||
<Provider store={store}>
|
<AppearanceProvider>
|
||||||
{content}
|
<Provider store={store}>
|
||||||
</Provider>
|
<ThemeContext.Provider
|
||||||
|
value={{
|
||||||
|
theme,
|
||||||
|
themePreferences,
|
||||||
|
setTheme: this.setTheme
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
</Provider>
|
||||||
|
</AppearanceProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
|
@ -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) {
|
||||||
<Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} userId={user.id} token={user.token} />
|
onLongPress();
|
||||||
<View style={styles.textContainer}>
|
}
|
||||||
<Text style={styles.name}>{name}</Text>
|
};
|
||||||
<Text style={styles.username}>@{username}</Text>
|
|
||||||
</View>
|
return (
|
||||||
{icon ? <CustomIcon name={icon} size={22} style={styles.icon} /> : null}
|
<LongPressGestureHandler
|
||||||
</View>
|
onHandlerStateChange={longPress}
|
||||||
</Touch>
|
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} />
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<Text style={[styles.name, { color: themes[theme].titleText }]}>{name}</Text>
|
||||||
|
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]}>@{username}</Text>
|
||||||
|
</View>
|
||||||
|
{icon ? <CustomIcon name={icon} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null}
|
||||||
|
</View>
|
||||||
|
</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;
|
||||||
|
|
|
@ -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 }`;
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
app/share.js
74
app/share.js
|
@ -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 (
|
||||||
<View
|
<AppearanceProvider>
|
||||||
style={[sharedStyles.container, isLandscape && isNotch ? sharedStyles.notchLandscapeContainer : {}]}
|
<View
|
||||||
onLayout={this.handleLayout}
|
style={[sharedStyles.container, isLandscape && isNotch ? sharedStyles.notchLandscapeContainer : {}]}
|
||||||
>
|
onLayout={this.handleLayout}
|
||||||
<Provider store={store}>
|
>
|
||||||
<AppContainer
|
<Provider store={store}>
|
||||||
ref={(navigatorRef) => {
|
<ThemeContext.Provider value={{ theme }}>
|
||||||
Navigation.setTopLevelNavigator(navigatorRef);
|
<AppContainer
|
||||||
}}
|
ref={(navigatorRef) => {
|
||||||
onNavigationStateChange={onNavigationStateChange}
|
Navigation.setTopLevelNavigator(navigatorRef);
|
||||||
/>
|
}}
|
||||||
</Provider>
|
onNavigationStateChange={onNavigationStateChange}
|
||||||
</View>
|
screenProps={{ theme }}
|
||||||
|
/>
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
</Provider>
|
||||||
|
</View>
|
||||||
|
</AppearanceProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
Loading…
Reference in New Issue