Merge 4.27.0 into single-server (#4138)

This commit is contained in:
Diego Mello 2022-04-28 15:44:40 -03:00 committed by GitHub
parent b3c37ede7e
commit 8f6100d450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
460 changed files with 4434 additions and 3876 deletions

View File

@ -3,7 +3,7 @@ defaults: &defaults
macos: &macos
macos:
xcode: "12.5.0"
xcode: "13.3.0"
resource_class: large
bash-env: &bash-env
@ -339,7 +339,7 @@ jobs:
lint-testunit:
<<: *defaults
docker:
- image: circleci/node:15
- image: cimg/node:16.14
resource_class: large
environment:
CODECOV_TOKEN: caa771ab-3d45-4756-8e2a-e1f25996fef6
@ -372,7 +372,7 @@ jobs:
android-build-experimental:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
environment:
<<: *android-env
<<: *bash-env
@ -383,7 +383,7 @@ jobs:
android-build-official:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
environment:
<<: *android-env
<<: *bash-env
@ -394,7 +394,7 @@ jobs:
android-internal-app-sharing-experimental:
<<: *defaults
docker:
- image: circleci/android:api-28-node
- image: cimg/android:2022.03.1-node
steps:
- upload-to-internal-app-sharing
@ -402,7 +402,7 @@ jobs:
android-google-play-beta-experimental:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
steps:
- upload-to-google-play-beta:
@ -411,14 +411,14 @@ jobs:
android-google-play-production-experimental:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
steps:
- upload-to-google-play-production
android-google-play-beta-official:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
steps:
- upload-to-google-play-beta:

View File

@ -17,7 +17,7 @@ module.exports = {
legacyDecorators: true
}
},
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest'],
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest', 'react-hooks'],
env: {
browser: true,
commonjs: true,
@ -148,7 +148,9 @@ module.exports = {
'no-async-promise-executor': [0],
'max-classes-per-file': [0],
'no-multiple-empty-lines': [0],
'no-sequences': 'off'
'no-sequences': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
},
globals: {
__DEV__: true
@ -237,7 +239,9 @@ module.exports = {
}
],
'new-cap': 'off',
'lines-between-class-members': 'off'
'lines-between-class-members': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
},
globals: {
JSX: true

View File

@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.26.2"
versionName "4.27.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
@ -277,14 +277,17 @@ android {
dependencies {
addUnimodulesDependencies()
implementation project(':@react-native-community_viewpager')
playImplementation project(':reactnativenotifications')
playImplementation project(':react-native-notifications')
playImplementation 'com.google.firebase:firebase-core:16.0.0'
playImplementation project(':@react-native-firebase_app')
playImplementation project(':@react-native-firebase_analytics')
playImplementation project(':@react-native-firebase_crashlytics')
implementation(project(':react-native-jitsi-meet')) { // https://github.com/skrafft/react-native-jitsi-meet#side-note
exclude group: 'com.facebook.react',module:'react-native-svg'
}
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
playImplementation "com.google.firebase:firebase-messaging:18.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'

View File

@ -3,7 +3,6 @@ package chat.rocket.reactnative;
import android.app.Application;
import com.facebook.react.ReactPackage;
import com.wix.reactnativenotifications.RNNotificationsPackage;
import java.util.Arrays;
import java.util.List;
@ -17,8 +16,7 @@ public class AdditionalModules {
return Arrays.<ReactPackage>asList(
new ReactNativeFirebaseAnalyticsPackage(),
new ReactNativeFirebaseAppPackage(),
new ReactNativeFirebaseCrashlyticsPackage(),
new RNNotificationsPackage(application)
new ReactNativeFirebaseCrashlyticsPackage()
);
}
}

View File

@ -2,8 +2,6 @@ apply from: '../node_modules/react-native-unimodules/gradle.groovy'
includeUnimodulesProjects()
rootProject.name = 'RocketChatRN'
include ':reactnativenotifications'
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android/app')
include ':@react-native-community_viewpager'
project(':@react-native-community_viewpager').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/viewpager/android')
include ':@react-native-firebase_app'

View File

@ -1,10 +1,10 @@
import React from 'react';
import React, { useContext, memo, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { connect } from 'react-redux';
import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes';
import Navigation from './lib/Navigation';
import Navigation from './lib/navigation/appNavigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
import { RootEnum } from './definitions';
// Stacks
@ -27,21 +27,23 @@ const SetUsernameStack = () => (
// App
const Stack = createStackNavigator<StackParamList>();
const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => {
const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => {
const { theme } = useContext(ThemeContext);
useEffect(() => {
if (root) {
const state = Navigation.navigationRef.current?.getRootState();
const currentRouteName = getActiveRouteName(state);
Navigation.routeNameRef.current = currentRouteName;
setCurrentScreen(currentRouteName);
}
}, [root]);
if (!root) {
return null;
}
const { theme } = React.useContext(ThemeContext);
const navTheme = navigationTheme(theme);
React.useEffect(() => {
const state = Navigation.navigationRef.current?.getRootState();
const currentRouteName = getActiveRouteName(state);
Navigation.routeNameRef.current = currentRouteName;
setCurrentScreen(currentRouteName);
}, []);
return (
<NavigationContainer
theme={navTheme}

View File

@ -25,6 +25,7 @@ interface ILoginFailure extends Action {
interface ILogout extends Action {
forcedByServer: boolean;
message: string;
}
interface ISetUser extends Action {
@ -79,10 +80,11 @@ export function loginFailure(err: Record<string, any>): ILoginFailure {
};
}
export function logout(forcedByServer = false): ILogout {
export function logout(forcedByServer = false, message = ''): ILogout {
return {
type: types.LOGOUT,
forcedByServer
forcedByServer,
message
};
}

View File

@ -1,8 +1,7 @@
import { Action } from 'redux';
import { MESSAGES } from './actionsTypes';
type IMessage = Record<string, string>;
import { IMessage } from '../definitions';
interface IReplyBroadcast extends Action {
message: IMessage;

View File

@ -7,7 +7,7 @@ import Animated, { Easing, Extrapolate, interpolateNode, Value } from 'react-nat
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { useDimensions, useOrientation } from '../../dimensions';
import I18n from '../../i18n';
import { useTheme } from '../../theme';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { View } from 'react-native';
import styles from './styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
export const Handle = React.memo(() => {

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Text, View } from 'react-native';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
import { useTheme } from '../../theme';
import { Button } from './Button';

View File

@ -2,7 +2,7 @@ import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
import ActionSheet from './ActionSheet';
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void };
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void; danger?: boolean };
export type TActionSheetOptions = {
options: TActionSheetOptionsItem[];

View File

@ -2,7 +2,7 @@ import React from 'react';
import { ActivityIndicator, ActivityIndicatorProps, StyleSheet } from 'react-native';
import { useTheme } from '../theme';
import { themes } from '../constants/colors';
import { themes } from '../lib/constants';
interface IActivityIndicator extends ActivityIndicatorProps {
absolute?: boolean;

View File

@ -1,10 +1,11 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { themes } from '../constants/colors';
import { themes } from '../lib/constants';
import sharedStyles from '../views/Styles';
import { getReadableVersion } from '../utils/deviceInfo';
import I18n from '../i18n';
import { TSupportedThemes } from '../theme';
const styles = StyleSheet.create({
container: {
@ -20,7 +21,7 @@ const styles = StyleSheet.create({
}
});
const AppVersion = React.memo(({ theme }: { theme: string }) => (
const AppVersion = React.memo(({ theme }: { theme: TSupportedThemes }) => (
<View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>
{I18n.t('Version_no', { version: '' })}

View File

@ -28,14 +28,15 @@ const Avatar = React.memo(
text,
size = 25,
borderRadius = 4,
type = SubscriptionType.DIRECT
type = SubscriptionType.DIRECT,
externalProviderUrl
}: IAvatar) => {
const { theme } = useTheme();
if ((!text && !avatar && !emoji && !rid) || !server) {
return null;
}
const { theme } = useTheme();
const avatarStyle = {
width: size,
height: size,
@ -67,7 +68,8 @@ const Avatar = React.memo(
avatarETag,
serverVersion,
rid,
blockUnauthenticatedAccess
blockUnauthenticatedAccess,
externalProviderUrl
});
}

View File

@ -32,7 +32,10 @@ class AvatarContainer extends React.Component<IAvatar, any> {
shouldComponentUpdate(nextProps: IAvatar, nextState: { avatarETag: string }) {
const { avatarETag } = this.state;
const { text, type } = this.props;
const { text, type, size, externalProviderUrl } = this.props;
if (nextProps.externalProviderUrl !== externalProviderUrl) {
return true;
}
if (nextState.avatarETag !== avatarETag) {
return true;
}
@ -42,6 +45,10 @@ class AvatarContainer extends React.Component<IAvatar, any> {
if (nextProps.type !== type) {
return true;
}
if (nextProps.size !== size) {
return true;
}
return false;
}
@ -100,6 +107,7 @@ const mapStateToProps = (state: IApplicationState) => ({
blockUnauthenticatedAccess:
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
true
true,
externalProviderUrl: state.settings.Accounts_AvatarExternalProviderUrl as string
});
export default connect(mapStateToProps)(AvatarContainer);

View File

@ -23,4 +23,5 @@ export interface IAvatar {
rid?: string;
blockUnauthenticatedAccess?: boolean;
serverVersion: string | null;
externalProviderUrl?: string;
}

View File

@ -3,7 +3,7 @@ import { ActivityIndicator, ImageBackground, StyleSheet, Text, View } from 'reac
import { useTheme } from '../../theme';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
interface IBackgroundContainer {
text?: string;

View File

@ -2,7 +2,8 @@ import React from 'react';
import { StyleSheet, Text } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import { themes } from '../../constants/colors';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import sharedStyles from '../../views/Styles';
import ActivityIndicator from '../ActivityIndicator';
@ -13,7 +14,7 @@ interface IButtonProps {
disabled: boolean;
backgroundColor: string;
loading: boolean;
theme: string;
theme: TSupportedThemes;
color: string;
fontSize: any;
style: any;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { StyleSheet } from 'react-native';
import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors';
import { themes } from '../lib/constants';
import { useTheme } from '../theme';
const styles = StyleSheet.create({

View File

@ -0,0 +1,74 @@
import React from 'react';
import { Text, View, ViewStyle } from 'react-native';
import Touch from '../../utils/touch';
import Avatar from '../Avatar';
import RoomTypeIcon from '../RoomTypeIcon';
import styles, { ROW_HEIGHT } from './styles';
import { themes } from '../../lib/constants';
import { TSupportedThemes, useTheme } from '../../theme';
export { ROW_HEIGHT };
interface IDirectoryItemLabel {
text?: string;
theme: TSupportedThemes;
}
interface IDirectoryItem {
title: string;
description: string;
avatar: string;
type: string;
onPress(): void;
testID: string;
style?: ViewStyle;
rightLabel?: string;
rid?: string;
teamMain?: boolean;
}
const DirectoryItemLabel = React.memo(({ text, theme }: IDirectoryItemLabel) => {
if (!text) {
return null;
}
return <Text style={[styles.directoryItemLabel, { color: themes[theme].auxiliaryText }]}>{text}</Text>;
});
const DirectoryItem = ({
title,
description,
avatar,
onPress,
testID,
style,
rightLabel,
type,
rid,
teamMain
}: IDirectoryItem): React.ReactElement => {
const { theme } = useTheme();
return (
<Touch onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }} testID={testID} theme={theme}>
<View style={[styles.directoryItemContainer, styles.directoryItemButton, style]}>
<Avatar text={avatar} size={30} type={type} rid={rid} style={styles.directoryItemAvatar} />
<View style={styles.directoryItemTextContainer}>
<View style={styles.directoryItemTextTitle}>
<RoomTypeIcon type={type} teamMain={teamMain} />
<Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>
{title}
</Text>
</View>
{description ? (
<Text style={[styles.directoryItemUsername, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
{description}
</Text>
) : null}
</View>
<DirectoryItemLabel text={rightLabel} theme={theme} />
</View>
</Touch>
);
};
export default DirectoryItem;

View File

@ -8,6 +8,7 @@ const CustomEmoji = React.memo(
<FastImage
style={style}
source={{
// @ts-ignore
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
priority: FastImage.priority.high
}}

View File

@ -56,6 +56,7 @@ class EmojiCategory extends React.Component<Partial<IEmojiCategory>> {
contentContainerStyle={{ marginHorizontal }}
// rerender FlatList in case of width changes
key={`emoji-category-${width}`}
// @ts-ignore
keyExtractor={item => (item && item.isCustom && item.content) || item}
data={emojis}
extraData={this.props}

View File

@ -2,14 +2,15 @@ import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import styles from './styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { TSupportedThemes } from '../../theme';
interface ITabBarProps {
goToPage: Function;
activeTab: number;
tabs: [];
tabEmojiStyle: object;
theme: string;
theme: TSupportedThemes;
}
export default class TabBar extends React.Component<Partial<ITabBarProps>> {

View File

@ -15,8 +15,8 @@ import { emojisByCategory } from '../../emojis';
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
import shortnameToUnicode from '../../utils/shortnameToUnicode';
import log from '../../utils/log';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { themes } from '../../lib/constants';
import { TSupportedThemes, withTheme } from '../../theme';
import { IEmoji } from '../../definitions/IEmoji';
const scrollProps = {
@ -30,7 +30,7 @@ interface IEmojiPickerProps {
baseUrl: string;
customEmojis?: any;
style: object;
theme?: string;
theme: TSupportedThemes;
onEmojiSelected?: ((emoji: any) => void) | ((keyboardId: any, params?: any) => void);
tabEmojiStyle?: object;
}
@ -109,6 +109,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord: any;
try {
// @ts-ignore
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
} catch (error) {
// Do nothing
@ -185,7 +186,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
/* @ts-ignore*/
contentProps={scrollProps}
style={{ backgroundColor: themes[theme!].focusedBackground }}>
style={{ backgroundColor: themes[theme].focusedBackground }}>
{categories.tabs.map((tab, i) =>
i === 0 && frequentlyUsed.length === 0
? null // when no frequentlyUsed don't show the tab

View File

@ -1,10 +1,10 @@
import React from 'react';
import { ScrollView, ScrollViewProps, StyleSheet, View } from 'react-native';
import { themes } from '../constants/colors';
import { themes } from '../lib/constants';
import sharedStyles from '../views/Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import KeyboardView from '../presentation/KeyboardView';
import KeyboardView from './KeyboardView';
import { useTheme } from '../theme';
import StatusBar from './StatusBar';
import AppVersion from './AppVersion';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StyleSheet, View } from 'react-native';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { themedHeader } from '../../utils/navigation';
import { isIOS, isTablet } from '../../utils/deviceInfo';
import { useTheme } from '../../theme';

View File

@ -4,7 +4,7 @@ import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../../lib/Icons';
import { useTheme } from '../../theme';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import sharedStyles from '../../views/Styles';
interface IHeaderButtonItem {

View File

@ -1,7 +1,7 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import UnreadBadge from '../../presentation/UnreadBadge';
import UnreadBadge from '../UnreadBadge';
const styles = StyleSheet.create({
badgeContainer: {

View File

@ -8,11 +8,11 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Avatar from '../Avatar';
import { CustomIcon } from '../../lib/Icons';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import { ROW_HEIGHT } from '../../presentation/RoomItem';
import { ROW_HEIGHT } from '../RoomItem';
import { goRoom } from '../../utils/goRoom';
import Navigation from '../../lib/Navigation';
import Navigation from '../../lib/navigation/appNavigation';
import { useOrientation } from '../../dimensions';
import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions';

View File

@ -5,7 +5,7 @@ import { dequal } from 'dequal';
import NotifierComponent, { INotifierComponent } from './NotifierComponent';
import EventEmitter from '../../utils/events';
import Navigation from '../../lib/Navigation';
import Navigation from '../../lib/navigation/appNavigation';
import { getActiveRoute } from '../../utils/navigation';
import { IApplicationState } from '../../definitions';
import { IRoom } from '../../reducers/room';

View File

@ -0,0 +1,24 @@
import React from 'react';
import { KeyboardAwareScrollView, KeyboardAwareScrollViewProps } from '@codler/react-native-keyboard-aware-scroll-view';
import scrollPersistTaps from '../utils/scrollPersistTaps';
interface IKeyboardViewProps extends KeyboardAwareScrollViewProps {
keyboardVerticalOffset?: number;
scrollEnabled?: boolean;
children: React.ReactElement[] | React.ReactElement;
}
const KeyboardView = ({ style, contentContainerStyle, scrollEnabled, keyboardVerticalOffset, children }: IKeyboardViewProps) => (
<KeyboardAwareScrollView
{...scrollPersistTaps}
style={style}
contentContainerStyle={contentContainerStyle}
scrollEnabled={scrollEnabled}
alwaysBounceVertical={false}
extraHeight={keyboardVerticalOffset}>
{children}
</KeyboardAwareScrollView>
);
export default KeyboardView;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import I18n from '../../i18n';
import { useTheme } from '../../theme';
import { PADDING_HORIZONTAL } from './constants';

View File

@ -1,16 +1,17 @@
import React from 'react';
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
import { useTheme } from '../../theme';
import { ICON_SIZE } from './constants';
interface IListIcon {
name: string;
color?: string;
color?: string | null;
style?: StyleProp<ViewStyle>;
testID?: string;
size?: number;
}
const styles = StyleSheet.create({
@ -20,12 +21,12 @@ const styles = StyleSheet.create({
}
});
const ListIcon = React.memo(({ name, color, style, testID }: IListIcon) => {
const ListIcon = React.memo(({ name, color, style, testID, size }: IListIcon) => {
const { theme } = useTheme();
return (
<View style={[styles.icon, style]}>
<CustomIcon name={name} color={color ?? themes[theme].auxiliaryText} size={ICON_SIZE} testID={testID} />
<CustomIcon name={name} color={color ?? themes[theme].auxiliaryText} size={size ?? ICON_SIZE} testID={testID} />
</View>
);
});

View File

@ -2,7 +2,7 @@ import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import { PADDING_HORIZONTAL } from './constants';
import I18n from '../../i18n';

View File

@ -1,10 +1,10 @@
import React from 'react';
import { I18nManager, StyleSheet, Text, View } from 'react-native';
import { I18nManager, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
import Touch from '../../utils/touch';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import sharedStyles from '../../views/Styles';
import { useTheme } from '../../theme';
import { TSupportedThemes, useTheme } from '../../theme';
import I18n from '../../i18n';
import { Icon } from '.';
import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
@ -59,13 +59,15 @@ interface IListItemContent {
left?: () => JSX.Element | null;
right?: () => JSX.Element | null;
disabled?: boolean;
theme: string;
theme: TSupportedThemes;
testID?: string;
color?: string;
translateTitle?: boolean;
translateSubtitle?: boolean;
showActionIndicator?: boolean;
alert?: boolean;
heightContainer?: number;
styleTitle?: StyleProp<TextStyle>;
}
const Content = React.memo(
@ -81,17 +83,21 @@ const Content = React.memo(
translateTitle = true,
translateSubtitle = true,
showActionIndicator = false,
theme
theme,
heightContainer,
styleTitle
}: IListItemContent) => {
const { fontScale } = useDimensions();
return (
<View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]} testID={testID}>
<View
style={[styles.container, disabled && styles.disabled, { height: (heightContainer || BASE_HEIGHT) * fontScale }]}
testID={testID}>
{left ? <View style={styles.leftContainer}>{left()}</View> : null}
<View style={styles.textContainer}>
<View style={styles.textAlertContainer}>
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>
{translateTitle ? I18n.t(title) : title}
<Text style={[styles.title, styleTitle, { color: color || themes[theme].titleText }]} numberOfLines={1}>
{translateTitle && title ? I18n.t(title) : title}
</Text>
{alert ? (
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
@ -121,7 +127,7 @@ interface IListButtonPress extends IListItemButton {
interface IListItemButton {
title?: string;
disabled?: boolean;
theme: string;
theme: TSupportedThemes;
backgroundColor?: string;
underlayColor?: string;
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import { StyleSheet, View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
const styles = StyleSheet.create({

View File

@ -1,8 +1,8 @@
import React from 'react';
import { Animated, Modal, StyleSheet, View } from 'react-native';
import { withTheme } from '../theme';
import { themes } from '../constants/colors';
import { TSupportedThemes, withTheme } from '../theme';
import { themes } from '../lib/constants';
const styles = StyleSheet.create({
container: {
@ -19,7 +19,7 @@ const styles = StyleSheet.create({
interface ILoadingProps {
visible: boolean;
theme?: string;
theme?: TSupportedThemes;
}
interface ILoadingState {

View File

@ -1,13 +1,13 @@
import React from 'react';
import { Animated, Easing, Linking, StyleSheet, Text, View } from 'react-native';
import { Animated, Easing, Linking, StyleSheet, Text, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { Base64 } from 'js-base64';
import * as AppleAuthentication from 'expo-apple-authentication';
import { StackNavigationProp } from '@react-navigation/stack';
import { withTheme } from '../theme';
import { TSupportedThemes, withTheme } from '../theme';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import { themes } from '../lib/constants';
import Button from './Button';
import OrSeparator from './OrSeparator';
import Touch from '../utils/touch';
@ -100,7 +100,7 @@ interface ILoginServicesProps {
CAS_enabled: boolean;
CAS_login_url: string;
separator: boolean;
theme: string;
theme: TSupportedThemes;
}
interface ILoginServicesState {
@ -410,7 +410,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
const { servicesHeight } = this.state;
const { services, separator } = this.props;
const { length } = Object.values(services);
const style = {
const style: Animated.AnimatedProps<ViewStyle> = {
overflow: 'hidden',
height: servicesHeight
};

View File

@ -1,8 +1,8 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { withTheme } from '../../theme';
import { themes } from '../../constants/colors';
import { TSupportedThemes, useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
import shortnameToUnicode from '../../utils/shortnameToUnicode';
import CustomEmoji from '../EmojiPicker/CustomEmoji';
@ -10,27 +10,31 @@ import database from '../../lib/database';
import { Button } from '../ActionSheet';
import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles';
import { IEmoji } from '../../definitions/IEmoji';
import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
import { TAnyMessageModel } from '../../definitions';
import { IEmoji } from '../../definitions/IEmoji';
interface IHeader {
handleReaction: Function;
type TItem = TFrequentlyUsedEmojiModel | string;
export interface IHeader {
handleReaction: (emoji: TItem, message: TAnyMessageModel) => void;
server: string;
message: object;
message: TAnyMessageModel;
isMasterDetail: boolean;
theme?: string;
}
type TOnReaction = ({ emoji }: { emoji: TItem }) => void;
interface THeaderItem {
item: IEmoji;
onReaction: Function;
item: TItem;
onReaction: TOnReaction;
server: string;
theme: string;
theme: TSupportedThemes;
}
interface THeaderFooter {
onReaction: any;
theme: string;
onReaction: TOnReaction;
theme: TSupportedThemes;
}
export const HEADER_HEIGHT = 36;
@ -62,25 +66,32 @@ const styles = StyleSheet.create({
}
});
const keyExtractor = (item: any) => item?.id || item;
const keyExtractor = (item: TItem) => {
const emojiModel = item as TFrequentlyUsedEmojiModel;
return (emojiModel.id ? emojiModel.content : item) as string;
};
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
const HeaderItem = React.memo(({ item, onReaction, server, theme }: THeaderItem) => (
<Button
testID={`message-actions-emoji-${item.content || item}`}
onPress={() => onReaction({ emoji: `:${item.content || item}:` })}
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
theme={theme}>
{item?.isCustom ? (
<CustomEmoji style={styles.customEmoji} emoji={item} baseUrl={server} />
) : (
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${item.content || item}:`)}</Text>
)}
</Button>
));
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
const emojiModel = item as TFrequentlyUsedEmojiModel;
const emoji = (emojiModel.id ? emojiModel.content : item) as string;
return (
<Button
testID={`message-actions-emoji-${emoji}`}
onPress={() => onReaction({ emoji: `:${emoji}:` })}
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
theme={theme}>
{emojiModel?.isCustom ? (
<CustomEmoji style={styles.customEmoji} emoji={emojiModel as IEmoji} baseUrl={server} />
) : (
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text>
)}
</Button>
);
};
const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
<Button
testID='add-reaction'
onPress={onReaction}
@ -88,17 +99,19 @@ const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
theme={theme}>
<CustomIcon name='reaction-add' size={24} color={themes[theme].bodyText} />
</Button>
));
);
const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
const [items, setItems] = useState<(TFrequentlyUsedEmojiModel | string)[]>([]);
const { width, height }: any = useDimensions();
const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => {
const [items, setItems] = useState<TItem[]>([]);
const { width, height } = useDimensions();
const { theme } = useTheme();
// TODO: create custom hook to re-render based on screen size
const setEmojis = async () => {
try {
const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojis: (TFrequentlyUsedEmojiModel | string)[] = await freqEmojiCollection.query().fetch();
let freqEmojis: TItem[] = await freqEmojiCollection.query().fetch();
const isLandscape = width > height;
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
@ -115,22 +128,21 @@ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, th
setEmojis();
}, []);
const onReaction = ({ emoji }: { emoji: IEmoji }) => handleReaction(emoji, message);
const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message);
const renderItem = useCallback(
({ item }) => <HeaderItem item={item} onReaction={onReaction} server={server} theme={theme!} />,
[]
const renderItem = ({ item }: { item: TItem }) => (
<HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />
);
const renderFooter = useCallback(() => <HeaderFooter onReaction={onReaction} theme={theme!} />, []);
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
return (
<View style={[styles.container, { backgroundColor: themes[theme!].focusedBackground }]}>
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
<FlatList
data={items}
renderItem={renderItem}
ListFooterComponent={renderFooter}
style={{ backgroundColor: themes[theme!].focusedBackground }}
style={{ backgroundColor: themes[theme].focusedBackground }}
keyExtractor={keyExtractor}
showsHorizontalScrollIndicator={false}
scrollEnabled={false}
@ -140,4 +152,4 @@ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, th
);
});
export default withTheme(Header);
export default Header;

View File

@ -8,38 +8,38 @@ import RocketChat from '../../lib/rocketchat';
import database from '../../lib/database';
import I18n from '../../i18n';
import log, { logEvent } from '../../utils/log';
import Navigation from '../../lib/Navigation';
import Navigation from '../../lib/navigation/appNavigation';
import { getMessageTranslation } from '../message/utils';
import { LISTENER } from '../Toast';
import EventEmitter from '../../utils/events';
import { showConfirmationAlert } from '../../utils/info';
import { useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT } from './Header';
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT, IHeader } from './Header';
import events from '../../utils/log/events';
import { ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
export interface IMessageActions {
room: TSubscriptionModel;
tmid?: string;
user: Pick<ILoggedUser, 'id'>;
editInit: Function;
reactionInit: Function;
onReactionPress: Function;
replyInit: Function;
editInit: (message: TAnyMessageModel) => void;
reactionInit: (message: TAnyMessageModel) => void;
onReactionPress: (shortname: string, messageId: string) => void;
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
isMasterDetail: boolean;
isReadOnly: boolean;
Message_AllowDeleting: boolean;
Message_AllowDeleting_BlockDeleteInMinutes: number;
Message_AllowEditing: boolean;
Message_AllowEditing_BlockEditInMinutes: number;
Message_AllowPinning: boolean;
Message_AllowStarring: boolean;
Message_Read_Receipt_Store_Users: boolean;
Message_AllowDeleting?: boolean;
Message_AllowDeleting_BlockDeleteInMinutes?: number;
Message_AllowEditing?: boolean;
Message_AllowEditing_BlockEditInMinutes?: number;
Message_AllowPinning?: boolean;
Message_AllowStarring?: boolean;
Message_Read_Receipt_Store_Users?: boolean;
server: string;
editMessagePermission: [];
deleteMessagePermission: [];
forceDeleteMessagePermission: [];
pinMessagePermission: [];
editMessagePermission?: string[];
deleteMessagePermission?: string[];
forceDeleteMessagePermission?: string[];
pinMessagePermission?: string[];
}
const MessageActions = React.memo(
@ -69,9 +69,14 @@ const MessageActions = React.memo(
pinMessagePermission
}: IMessageActions,
ref
): any => {
let permissions: any = {};
const { showActionSheet, hideActionSheet }: any = useActionSheet();
) => {
let permissions = {
hasEditPermission: false,
hasDeletePermission: false,
hasForceDeletePermission: false,
hasPinPermission: false
};
const { showActionSheet, hideActionSheet } = useActionSheet();
const getPermissions = async () => {
try {
@ -88,9 +93,9 @@ const MessageActions = React.memo(
}
};
const isOwn = (message: any) => message.u && message.u._id === user.id;
const isOwn = (message: TAnyMessageModel) => message.u && message.u._id === user.id;
const allowEdit = (message: any) => {
const allowEdit = (message: TAnyMessageModel) => {
if (isReadOnly) {
return false;
}
@ -105,7 +110,7 @@ const MessageActions = React.memo(
if (message.ts != null) {
msgTs = moment(message.ts);
}
let currentTsDiff: any;
let currentTsDiff = 0;
if (msgTs != null) {
currentTsDiff = moment().diff(msgTs, 'minutes');
}
@ -114,7 +119,7 @@ const MessageActions = React.memo(
return true;
};
const allowDelete = (message: any) => {
const allowDelete = (message: TAnyMessageModel) => {
if (isReadOnly) {
return false;
}
@ -136,7 +141,7 @@ const MessageActions = React.memo(
if (message.ts != null) {
msgTs = moment(message.ts);
}
let currentTsDiff: any;
let currentTsDiff = 0;
if (msgTs != null) {
currentTsDiff = moment().diff(msgTs, 'minutes');
}
@ -145,19 +150,19 @@ const MessageActions = React.memo(
return true;
};
const getPermalink = (message: any) => RocketChat.getPermalinkMessage(message);
const getPermalink = (message: TAnyMessageModel) => RocketChat.getPermalinkMessage(message);
const handleReply = (message: any) => {
const handleReply = (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_REPLY);
replyInit(message, true);
};
const handleEdit = (message: any) => {
const handleEdit = (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_EDIT);
editInit(message);
};
const handleCreateDiscussion = (message: any) => {
const handleCreateDiscussion = (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_DISCUSSION);
const params = { message, channel: room, showCloseModal: true };
if (isMasterDetail) {
@ -167,7 +172,7 @@ const MessageActions = React.memo(
}
};
const handleUnread = async (message: any) => {
const handleUnread = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_UNREAD);
const { id: messageId, ts } = message;
const { rid } = room;
@ -179,7 +184,7 @@ const MessageActions = React.memo(
const subRecord = await subCollection.find(rid);
await db.write(async () => {
try {
await subRecord.update(sub => (sub.lastOpen = ts));
await subRecord.update(sub => (sub.lastOpen = ts as Date)); // TODO: reevaluate IMessage
} catch {
// do nothing
}
@ -192,42 +197,44 @@ const MessageActions = React.memo(
}
};
const handlePermalink = async (message: any) => {
const handlePermalink = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_PERMALINK);
try {
const permalink: any = await getPermalink(message);
Clipboard.setString(permalink);
const permalink = await getPermalink(message);
Clipboard.setString(permalink ?? '');
EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') });
} catch {
logEvent(events.ROOM_MSG_ACTION_PERMALINK_F);
}
};
const handleCopy = async (message: any) => {
const handleCopy = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_COPY);
await Clipboard.setString(message?.attachments?.[0]?.description || message.msg);
await Clipboard.setString((message?.attachments?.[0]?.description || message.msg) ?? '');
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
};
const handleShare = async (message: any) => {
const handleShare = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_SHARE);
try {
const permalink: any = await getPermalink(message);
Share.share({ message: permalink });
const permalink = await getPermalink(message);
if (permalink) {
Share.share({ message: permalink });
}
} catch {
logEvent(events.ROOM_MSG_ACTION_SHARE_F);
}
};
const handleQuote = (message: any) => {
const handleQuote = (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_QUOTE);
replyInit(message, false);
};
const handleStar = async (message: any) => {
const handleStar = async (message: TAnyMessageModel) => {
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
try {
await RocketChat.toggleStarMessage(message.id, message.starred);
await RocketChat.toggleStarMessage(message.id, message.starred as boolean); // TODO: reevaluate `message.starred` type on IMessage
EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') });
} catch (e) {
logEvent(events.ROOM_MSG_ACTION_STAR_F);
@ -235,20 +242,21 @@ const MessageActions = React.memo(
}
};
const handlePin = async (message: any) => {
const handlePin = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_PIN);
try {
await RocketChat.togglePinMessage(message.id, message.pinned);
await RocketChat.togglePinMessage(message.id, message.pinned as boolean); // TODO: reevaluate `message.pinned` type on IMessage
} catch (e) {
logEvent(events.ROOM_MSG_ACTION_PIN_F);
log(e);
}
};
const handleReaction = (shortname: any, message: any) => {
const handleReaction: IHeader['handleReaction'] = (shortname, message) => {
logEvent(events.ROOM_MSG_ACTION_REACTION);
if (shortname) {
onReactionPress(shortname, message.id);
// TODO: evaluate unification with IEmoji
onReactionPress(shortname as any, message.id);
} else {
reactionInit(message);
}
@ -256,7 +264,7 @@ const MessageActions = React.memo(
hideActionSheet();
};
const handleReadReceipt = (message: any) => {
const handleReadReceipt = (message: TAnyMessageModel) => {
if (isMasterDetail) {
Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } });
} else {
@ -291,7 +299,7 @@ const MessageActions = React.memo(
}
};
const handleReport = async (message: any) => {
const handleReport = async (message: TAnyMessageModel) => {
logEvent(events.ROOM_MSG_ACTION_REPORT);
try {
await RocketChat.reportMessage(message.id);
@ -302,14 +310,14 @@ const MessageActions = React.memo(
}
};
const handleDelete = (message: any) => {
const handleDelete = (message: TAnyMessageModel) => {
showConfirmationAlert({
message: I18n.t('You_will_not_be_able_to_recover_this_message'),
confirmationText: I18n.t('Delete'),
onPress: async () => {
try {
logEvent(events.ROOM_MSG_ACTION_DELETE);
await RocketChat.deleteMessage(message.id, message.subscription.id);
await RocketChat.deleteMessage(message.id, message.subscription ? message.subscription.id : '');
} catch (e) {
logEvent(events.ROOM_MSG_ACTION_DELETE_F);
log(e);
@ -319,7 +327,7 @@ const MessageActions = React.memo(
};
const getOptions = (message: TAnyMessageModel) => {
let options: any = [];
let options: TActionSheetOptionsItem[] = [];
// Reply
if (!isReadOnly) {
@ -463,16 +471,15 @@ const MessageActions = React.memo(
}
)
);
const mapStateToProps = (state: any) => ({
const mapStateToProps = (state: IApplicationState) => ({
server: state.server.server,
Message_AllowDeleting: state.settings.Message_AllowDeleting,
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes,
Message_AllowEditing: state.settings.Message_AllowEditing,
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes,
Message_AllowPinning: state.settings.Message_AllowPinning,
Message_AllowStarring: state.settings.Message_AllowStarring,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users,
Message_AllowDeleting: state.settings.Message_AllowDeleting as boolean,
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes as number,
Message_AllowEditing: state.settings.Message_AllowEditing as boolean,
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes as number,
Message_AllowPinning: state.settings.Message_AllowPinning as boolean,
Message_AllowStarring: state.settings.Message_AllowStarring as boolean,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users as boolean,
isMasterDetail: state.app.isMasterDetail,
editMessagePermission: state.permissions['edit-message'],
deleteMessagePermission: state.permissions['delete-message'],

View File

@ -1,12 +1,13 @@
import FastImage from '@rocket.chat/react-native-fast-image';
import React, { useContext, useState } from 'react';
import { TouchableOpacity } from 'react-native';
import FastImage from '@rocket.chat/react-native-fast-image';
import styles from '../styles';
import { themes } from '../../../lib/constants';
import { CustomIcon } from '../../../lib/Icons';
import { themes } from '../../../constants/colors';
import MessageboxContext from '../Context';
import { useTheme } from '../../../theme';
import ActivityIndicator from '../../ActivityIndicator';
import MessageboxContext from '../Context';
import styles from '../styles';
interface IMessageBoxCommandsPreviewItem {
item: {
@ -14,13 +15,13 @@ interface IMessageBoxCommandsPreviewItem {
id: string;
value: string;
};
theme?: string;
}
const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
const Item = ({ item }: IMessageBoxCommandsPreviewItem) => {
const context = useContext(MessageboxContext);
const { onPressCommandPreview } = context;
const [loading, setLoading] = useState(true);
const { theme } = useTheme();
return (
<TouchableOpacity
@ -37,7 +38,7 @@ const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
{loading ? <ActivityIndicator /> : null}
</FastImage>
) : (
<CustomIcon name='attach' size={36} color={themes[theme!].actionTintColor} />
<CustomIcon name='attach' size={36} color={themes[theme].actionTintColor} />
)}
</TouchableOpacity>
);

View File

@ -1,30 +1,32 @@
import { dequal } from 'dequal';
import React from 'react';
import { FlatList } from 'react-native';
import { dequal } from 'dequal';
import Item from './Item';
import styles from '../styles';
import { themes } from '../../../constants/colors';
import { withTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
import { IPreviewItem } from '../../../definitions';
import { useTheme } from '../../../theme';
import styles from '../styles';
import Item from './Item';
interface IMessageBoxCommandsPreview {
commandPreview: IPreviewItem[];
showCommandPreview: boolean;
theme?: string;
}
const CommandsPreview = React.memo(
({ theme, commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
({ commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
const { theme } = useTheme();
if (!showCommandPreview) {
return null;
}
return (
<FlatList
testID='commandbox-container'
style={[styles.mentionList, { backgroundColor: themes[theme!].messageboxBackground }]}
style={[styles.mentionList, { backgroundColor: themes[theme].messageboxBackground }]}
data={commandPreview}
renderItem={({ item }) => <Item item={item} theme={theme} />}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={(item: any) => item.id}
keyboardShouldPersistTaps='always'
horizontal
@ -33,9 +35,6 @@ const CommandsPreview = React.memo(
);
},
(prevProps, nextProps) => {
if (prevProps.theme !== nextProps.theme) {
return false;
}
if (prevProps.showCommandPreview !== nextProps.showCommandPreview) {
return false;
}
@ -46,4 +45,4 @@ const CommandsPreview = React.memo(
}
);
export default withTheme(CommandsPreview);
export default CommandsPreview;

View File

@ -1,5 +1,4 @@
import React from 'react';
// @ts-ignore
const MessageboxContext = React.createContext<any>();
const MessageboxContext = React.createContext<any>(null);
export default MessageboxContext;

View File

@ -2,14 +2,15 @@ import React from 'react';
import { View } from 'react-native';
import { KeyboardRegistry } from 'react-native-ui-lib/keyboard';
import { store } from '../../lib/auxStore';
import { store } from '../../lib/store/auxStore';
import EmojiPicker from '../EmojiPicker';
import styles from './styles';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { themes } from '../../lib/constants';
import { TSupportedThemes, withTheme } from '../../theme';
import { IEmoji } from '../../definitions/IEmoji';
interface IMessageBoxEmojiKeyboard {
theme: string;
theme: TSupportedThemes;
}
export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiKeyboard, any> {
@ -21,7 +22,7 @@ export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiK
this.baseUrl = state.share.server.server || state.server.server;
}
onEmojiSelected = (emoji: any) => {
onEmojiSelected = (emoji: IEmoji) => {
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
};

View File

@ -3,7 +3,6 @@ import React from 'react';
import { CancelEditingButton, ToggleEmojiButton } from './buttons';
interface IMessageBoxLeftButtons {
theme: string;
showEmojiKeyboard: boolean;
openEmoji(): void;
closeEmoji(): void;
@ -11,13 +10,11 @@ interface IMessageBoxLeftButtons {
editCancel(): void;
}
const LeftButtons = React.memo(
({ theme, showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} theme={theme} />;
}
return <ToggleEmojiButton show={showEmojiKeyboard} open={openEmoji} close={closeEmoji} theme={theme} />;
const LeftButtons = React.memo(({ showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} />;
}
);
return <ToggleEmojiButton show={showEmojiKeyboard} open={openEmoji} close={closeEmoji} />;
});
export default LeftButtons;

View File

@ -5,23 +5,20 @@ import { ActionsButton, CancelEditingButton } from './buttons';
import styles from './styles';
interface IMessageBoxLeftButtons {
theme: string;
showMessageBoxActions(): void;
editing: boolean;
editCancel(): void;
isActionsEnabled: boolean;
}
const LeftButtons = React.memo(
({ theme, showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} theme={theme} />;
}
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
}
return <View style={styles.buttonsWhitespace} />;
const LeftButtons = React.memo(({ showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => {
if (editing) {
return <CancelEditingButton onPress={editCancel} />;
}
);
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} />;
}
return <View style={styles.buttonsWhitespace} />;
});
export default LeftButtons;

View File

@ -3,31 +3,34 @@ import { Text, TouchableOpacity } from 'react-native';
import styles from '../styles';
import I18n from '../../../i18n';
import { themes } from '../../../constants/colors';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
interface IMessageBoxFixedMentionItem {
item: {
username: string;
};
onPress: Function;
theme: string;
}
const FixedMentionItem = ({ item, onPress, theme }: IMessageBoxFixedMentionItem) => (
<TouchableOpacity
style={[
styles.mentionItem,
{
backgroundColor: themes[theme].auxiliaryBackground,
borderTopColor: themes[theme].separatorColor
}
]}
onPress={() => onPress(item)}>
<Text style={[styles.fixedMentionAvatar, { color: themes[theme].titleText }]}>{item.username}</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>
);
const FixedMentionItem = ({ item, onPress }: IMessageBoxFixedMentionItem) => {
const { theme } = useTheme();
return (
<TouchableOpacity
style={[
styles.mentionItem,
{
backgroundColor: themes[theme].auxiliaryBackground,
borderTopColor: themes[theme].separatorColor
}
]}
onPress={() => onPress(item)}>
<Text style={[styles.fixedMentionAvatar, { color: themes[theme].titleText }]}>{item.username}</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>
);
};
export default FixedMentionItem;

View File

@ -1,12 +1,11 @@
import React, { useContext } from 'react';
import { Text } from 'react-native';
import PropTypes from 'prop-types';
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
import styles from '../styles';
import MessageboxContext from '../Context';
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
import { IEmoji } from '../../../definitions/IEmoji';
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
import MessageboxContext from '../Context';
import styles from '../styles';
interface IMessageBoxMentionEmoji {
item: IEmoji;
@ -22,8 +21,4 @@ const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
};
MentionEmoji.propTypes = {
item: PropTypes.object
};
export default MentionEmoji;

View File

@ -1,16 +1,23 @@
import React, { useContext } from 'react';
import { View, Text, ActivityIndicator, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
import { MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
import styles from '../styles';
import sharedStyles from '../../../views/Styles';
import { themes } from '../../../lib/constants';
import I18n from '../../../i18n';
import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';
import sharedStyles from '../../../views/Styles';
import MessageboxContext from '../Context';
import styles from '../styles';
import { MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
const MentionHeaderList = ({ trackingType, hasMentions, theme, loading }) => {
interface IMentionHeaderList {
trackingType: string;
hasMentions: boolean;
loading: boolean;
}
const MentionHeaderList = ({ trackingType, hasMentions, loading }: IMentionHeaderList) => {
const { theme } = useTheme();
const context = useContext(MessageboxContext);
const { onPressNoMatchCanned } = context;
@ -39,11 +46,4 @@ const MentionHeaderList = ({ trackingType, hasMentions, theme, loading }) => {
return null;
};
MentionHeaderList.propTypes = {
trackingType: PropTypes.string,
hasMentions: PropTypes.bool,
theme: PropTypes.string,
loading: PropTypes.bool
};
export default MentionHeaderList;

View File

@ -1,14 +1,15 @@
import React, { useContext } from 'react';
import { Text, TouchableOpacity } from 'react-native';
import styles from '../styles';
import { themes } from '../../../lib/constants';
import { IEmoji } from '../../../definitions/IEmoji';
import { useTheme } from '../../../theme';
import Avatar from '../../Avatar';
import { MENTIONS_TRACKING_TYPE_CANNED, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_EMOJIS } from '../constants';
import MessageboxContext from '../Context';
import styles from '../styles';
import FixedMentionItem from './FixedMentionItem';
import MentionEmoji from './MentionEmoji';
import { MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
import { themes } from '../../../constants/colors';
import { IEmoji } from '../../../definitions/IEmoji';
interface IMessageBoxMentionItem {
item: {
@ -21,11 +22,48 @@ interface IMessageBoxMentionItem {
text: string;
} & IEmoji;
trackingType: string;
theme: string;
}
const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
const MentionItemContent = React.memo(({ trackingType, item }: IMessageBoxMentionItem) => {
const { theme } = useTheme();
switch (trackingType) {
case MENTIONS_TRACKING_TYPE_EMOJIS:
return (
<>
<MentionEmoji item={item} />
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>:{item.name || item}:</Text>
</>
);
case MENTIONS_TRACKING_TYPE_COMMANDS:
return (
<>
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
</>
);
case MENTIONS_TRACKING_TYPE_CANNED:
return (
<>
<Text style={[styles.cannedItem, { color: themes[theme].titleText }]}>!{item.shortcut}</Text>
<Text numberOfLines={1} style={[styles.cannedMentionText, { color: themes[theme].auxiliaryTintColor }]}>
{item.text}
</Text>
</>
);
default:
return (
<>
<Avatar style={styles.avatar} text={item.username || item.name} size={30} type={item.t} />
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.username || item.name || item}</Text>
</>
);
}
});
const MentionItem = ({ item, trackingType }: IMessageBoxMentionItem) => {
const context = useContext(MessageboxContext);
const { theme } = useTheme();
const { onPressMention } = context;
const defineTestID = (type: string) => {
@ -44,43 +82,7 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
const testID = defineTestID(trackingType);
if (item.username === 'all' || item.username === 'here') {
return <FixedMentionItem item={item} onPress={onPressMention} theme={theme} />;
}
let content = (
<>
<Avatar style={styles.avatar} text={item.username || item.name} size={30} type={item.t} />
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.username || item.name || item}</Text>
</>
);
if (trackingType === MENTIONS_TRACKING_TYPE_EMOJIS) {
content = (
<>
<MentionEmoji item={item} />
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>:{item.name || item}:</Text>
</>
);
}
if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) {
content = (
<>
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
</>
);
}
if (trackingType === MENTIONS_TRACKING_TYPE_CANNED) {
content = (
<>
<Text style={[styles.cannedItem, { color: themes[theme].titleText }]}>!{item.shortcut}</Text>
<Text numberOfLines={1} style={[styles.cannedMentionText, { color: themes[theme].auxiliaryTintColor }]}>
{item.text}
</Text>
</>
);
return <FixedMentionItem item={item} onPress={onPressMention} />;
}
return (
@ -94,7 +96,7 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
]}
onPress={() => onPressMention(item)}
testID={testID}>
{content}
<MentionItemContent item={item} trackingType={trackingType} />
</TouchableOpacity>
);
};

View File

@ -5,30 +5,33 @@ import { dequal } from 'dequal';
import MentionHeaderList from './MentionHeaderList';
import styles from '../styles';
import MentionItem from './MentionItem';
import { themes } from '../../../constants/colors';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
interface IMessageBoxMentions {
mentions: any[];
trackingType: string;
theme: string;
loading: boolean;
}
const Mentions = React.memo(
({ mentions, trackingType, theme, loading }: IMessageBoxMentions) => {
({ mentions, trackingType, loading }: IMessageBoxMentions) => {
const { theme } = useTheme();
if (!trackingType) {
return null;
}
return (
<View testID='messagebox-container'>
<FlatList
style={[styles.mentionList, { backgroundColor: themes[theme].auxiliaryBackground }]}
ListHeaderComponent={() => (
<MentionHeaderList trackingType={trackingType} hasMentions={mentions.length > 0} theme={theme} loading={loading} />
<MentionHeaderList trackingType={trackingType} hasMentions={mentions.length > 0} loading={loading} />
)}
data={mentions}
extraData={mentions}
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} theme={theme} />}
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} />}
keyExtractor={item => item.rid || item.name || item.command || item.shortcut || item}
keyboardShouldPersistTaps='always'
/>
@ -39,9 +42,6 @@ const Mentions = React.memo(
if (prevProps.loading !== nextProps.loading) {
return false;
}
if (prevProps.theme !== nextProps.theme) {
return false;
}
if (prevProps.trackingType !== nextProps.trackingType) {
return false;
}

View File

@ -7,12 +7,13 @@ import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
import styles from './styles';
import I18n from '../../i18n';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
import { events, logEvent } from '../../utils/log';
import { TSupportedThemes } from '../../theme';
interface IMessageBoxRecordAudioProps {
theme: string;
theme: TSupportedThemes;
permissionToUpload: boolean;
recordingCallback: Function;
onFinish: Function;
@ -47,23 +48,17 @@ const RECORDING_MODE = {
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX
};
const formatTime = function (seconds: any) {
let minutes: any = Math.floor(seconds / 60);
seconds %= 60;
if (minutes < 10) {
minutes = `0${minutes}`;
}
if (seconds < 10) {
seconds = `0${seconds}`;
}
return `${minutes}:${seconds}`;
const formatTime = function (time: number) {
const minutes = Math.floor(time / 60);
const seconds = time % 60;
const min = minutes < 10 ? `0${minutes}` : minutes;
const sec = seconds < 10 ? `0${seconds}` : seconds;
return `${min}:${sec}`;
};
export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAudioProps, any> {
private isRecorderBusy: boolean;
private recording: any;
private recording!: Audio.Recording;
private LastDuration: number;
constructor(props: IMessageBoxRecordAudioProps) {
@ -112,7 +107,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
return false;
};
onRecordingStatusUpdate = (status: any) => {
onRecordingStatusUpdate = (status: Audio.RecordingStatus) => {
this.setState({
isRecording: status.isRecording,
recordingDurationMillis: status.durationMillis
@ -157,7 +152,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
await this.recording.stopAndUnloadAsync();
const fileURI = this.recording.getURI();
const fileData = await getInfoAsync(fileURI);
const fileData = await getInfoAsync(fileURI as string);
const fileInfo = {
name: `${Date.now()}.m4a`,
mime: 'audio/aac',
@ -200,14 +195,10 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
}
if (!isRecording && !isRecorderActive) {
return (
<BorderlessButton
onPress={this.startRecordingAudio}
style={styles.actionButton}
testID='messagebox-send-audio'
// @ts-ignore
accessibilityLabel={I18n.t('Send_audio_message')}
accessibilityTraits='button'>
<CustomIcon name='microphone' size={24} color={themes[theme].auxiliaryTintColor} />
<BorderlessButton onPress={this.startRecordingAudio} style={styles.actionButton} testID='messagebox-send-audio'>
<View accessible accessibilityLabel={I18n.t('Send_audio_message')} accessibilityRole='button'>
<CustomIcon name='microphone' size={24} color={themes[theme].auxiliaryTintColor} />
</View>
</BorderlessButton>
);
}
@ -216,23 +207,17 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
return (
<View style={styles.recordingContent}>
<View style={styles.textArea}>
<BorderlessButton
onPress={this.cancelRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Cancel_recording')}
accessibilityTraits='button'
style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
<BorderlessButton onPress={this.cancelRecordingAudio} style={styles.actionButton}>
<View accessible accessibilityLabel={I18n.t('Cancel_recording')} accessibilityRole='button'>
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
</View>
</BorderlessButton>
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.GetLastDuration}</Text>
</View>
<BorderlessButton
onPress={this.finishRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Finish_recording')}
accessibilityTraits='button'
style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
<BorderlessButton onPress={this.finishRecordingAudio} style={styles.actionButton}>
<View accessible accessibilityLabel={I18n.t('Finish_recording')} accessibilityRole='button'>
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
</View>
</BorderlessButton>
</View>
);
@ -241,24 +226,18 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
return (
<View style={styles.recordingContent}>
<View style={styles.textArea}>
<BorderlessButton
onPress={this.cancelRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Cancel_recording')}
accessibilityTraits='button'
style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
<BorderlessButton onPress={this.cancelRecordingAudio} style={styles.actionButton}>
<View accessible accessibilityLabel={I18n.t('Cancel_recording')} accessibilityRole='button'>
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
</View>
</BorderlessButton>
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.duration}</Text>
<CustomIcon size={24} color={themes[theme].dangerColor} name='record' />
</View>
<BorderlessButton
onPress={this.finishRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Finish_recording')}
accessibilityTraits='button'
style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
<BorderlessButton onPress={this.finishRecordingAudio} style={styles.actionButton}>
<View accessible accessibilityLabel={I18n.t('Finish_recording')} accessibilityRole='button'>
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
</View>
</BorderlessButton>
</View>
);

View File

@ -6,8 +6,10 @@ import { connect } from 'react-redux';
import { MarkdownPreview } from '../markdown';
import { CustomIcon } from '../../lib/Icons';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { IMessage } from '../../definitions/IMessage';
import { useTheme } from '../../theme';
import { IApplicationState } from '../../definitions';
const styles = StyleSheet.create({
container: {
@ -49,12 +51,13 @@ interface IMessageBoxReplyPreview {
baseUrl: string;
username: string;
getCustomEmoji: Function;
theme: string;
useRealName: boolean;
}
const ReplyPreview = React.memo(
({ message, Message_TimeFormat, replying, close, theme, useRealName }: IMessageBoxReplyPreview) => {
({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => {
const { theme } = useTheme();
if (!replying) {
return null;
}
@ -75,16 +78,14 @@ const ReplyPreview = React.memo(
</View>
);
},
(prevProps: any, nextProps: any) =>
prevProps.replying === nextProps.replying &&
prevProps.theme === nextProps.theme &&
prevProps.message.id === nextProps.message.id
(prevProps: IMessageBoxReplyPreview, nextProps: IMessageBoxReplyPreview) =>
prevProps.replying === nextProps.replying && prevProps.message.id === nextProps.message.id
);
const mapStateToProps = (state: any) => ({
Message_TimeFormat: state.settings.Message_TimeFormat,
const mapStateToProps = (state: IApplicationState) => ({
Message_TimeFormat: state.settings.Message_TimeFormat as string,
baseUrl: state.server.server,
useRealName: state.settings.UI_Use_Real_Name
useRealName: state.settings.UI_Use_Real_Name as boolean
});
export default connect(mapStateToProps)(ReplyPreview);

View File

@ -5,24 +5,20 @@ import { ActionsButton, SendButton } from './buttons';
import styles from './styles';
interface IMessageBoxRightButtons {
theme: string;
showSend: boolean;
submit(): void;
showMessageBoxActions(): void;
isActionsEnabled: boolean;
}
const RightButtons = React.memo(
({ theme, showSend, submit, showMessageBoxActions, isActionsEnabled }: IMessageBoxRightButtons) => {
if (showSend) {
return <SendButton onPress={submit} theme={theme} />;
}
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
}
return <View style={styles.buttonsWhitespace} />;
const RightButtons = React.memo(({ showSend, submit, showMessageBoxActions, isActionsEnabled }: IMessageBoxRightButtons) => {
if (showSend) {
return <SendButton onPress={submit} />;
}
);
if (isActionsEnabled) {
return <ActionsButton onPress={showMessageBoxActions} />;
}
return <View style={styles.buttonsWhitespace} />;
});
export default RightButtons;

View File

@ -3,16 +3,15 @@ import React from 'react';
import { SendButton } from './buttons';
interface IMessageBoxRightButtons {
theme: string;
showSend: boolean;
submit(): void;
}
const RightButtons = React.memo(({ theme, showSend, submit }: IMessageBoxRightButtons) => {
const RightButtons = ({ showSend, submit }: IMessageBoxRightButtons) => {
if (showSend) {
return <SendButton theme={theme} onPress={submit} />;
return <SendButton onPress={submit} />;
}
return null;
});
};
export default RightButtons;

View File

@ -3,12 +3,11 @@ import React from 'react';
import BaseButton from './BaseButton';
interface IActionsButton {
theme: string;
onPress(): void;
}
const ActionsButton = React.memo(({ theme, onPress }: IActionsButton) => (
<BaseButton onPress={onPress} testID='messagebox-actions' accessibilityLabel='Message_actions' icon='add' theme={theme} />
));
const ActionsButton = ({ onPress }: IActionsButton) => (
<BaseButton onPress={onPress} testID='messagebox-actions' accessibilityLabel='Message_actions' icon='add' />
);
export default ActionsButton;

View File

@ -1,13 +1,14 @@
import React from 'react';
import { BorderlessButton } from 'react-native-gesture-handler';
import React from 'react';
import { View } from 'react-native';
import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons';
import styles from '../styles';
import I18n from '../../../i18n';
import i18n from '../../../i18n';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
interface IBaseButton {
theme: string;
onPress(): void;
testID: string;
accessibilityLabel: string;
@ -15,16 +16,18 @@ interface IBaseButton {
color: string;
}
const BaseButton = React.memo(({ onPress, testID, accessibilityLabel, icon, theme, color }: Partial<IBaseButton>) => (
<BorderlessButton
onPress={onPress}
style={styles.actionButton}
testID={testID}
// @ts-ignore
accessibilityLabel={I18n.t(accessibilityLabel)}
accessibilityTraits='button'>
<CustomIcon name={icon} size={24} color={color ?? themes[theme!].auxiliaryTintColor} />
</BorderlessButton>
));
const BaseButton = ({ accessibilityLabel, icon, color, ...props }: Partial<IBaseButton>) => {
const { theme } = useTheme();
return (
<BorderlessButton {...props} style={styles.actionButton}>
<View
accessible
accessibilityLabel={accessibilityLabel ? i18n.t(accessibilityLabel) : accessibilityLabel}
accessibilityRole='button'>
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
</View>
</BorderlessButton>
);
};
export default BaseButton;

View File

@ -3,18 +3,11 @@ import React from 'react';
import BaseButton from './BaseButton';
interface ICancelEditingButton {
theme: string;
onPress(): void;
}
const CancelEditingButton = React.memo(({ theme, onPress }: ICancelEditingButton) => (
<BaseButton
onPress={onPress}
testID='messagebox-cancel-editing'
accessibilityLabel='Cancel_editing'
icon='close'
theme={theme}
/>
));
const CancelEditingButton = ({ onPress }: ICancelEditingButton) => (
<BaseButton onPress={onPress} testID='messagebox-cancel-editing' accessibilityLabel='Cancel_editing' icon='close' />
);
export default CancelEditingButton;

View File

@ -1,22 +1,24 @@
import React from 'react';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
import BaseButton from './BaseButton';
import { themes } from '../../../constants/colors';
interface ISendButton {
theme: string;
onPress(): void;
}
const SendButton = React.memo(({ theme, onPress }: ISendButton) => (
<BaseButton
onPress={onPress}
testID='messagebox-send-message'
accessibilityLabel='Send_message'
icon='send-filled'
theme={theme}
color={themes[theme].tintColor}
/>
));
const SendButton = ({ onPress }: ISendButton) => {
const { theme } = useTheme();
return (
<BaseButton
onPress={onPress}
testID='messagebox-send-message'
accessibilityLabel='Send_message'
icon='send-filled'
color={themes[theme].tintColor}
/>
);
};
export default SendButton;

View File

@ -3,33 +3,18 @@ import React from 'react';
import BaseButton from './BaseButton';
interface IToggleEmojiButton {
theme: string;
show: boolean;
open(): void;
close(): void;
}
const ToggleEmojiButton = React.memo(({ theme, show, open, close }: IToggleEmojiButton) => {
const ToggleEmojiButton = ({ show, open, close }: IToggleEmojiButton) => {
if (show) {
return (
<BaseButton
onPress={close}
testID='messagebox-close-emoji'
accessibilityLabel='Close_emoji_selector'
icon='keyboard'
theme={theme}
/>
<BaseButton onPress={close} testID='messagebox-close-emoji' accessibilityLabel='Close_emoji_selector' icon='keyboard' />
);
}
return (
<BaseButton
onPress={open}
testID='messagebox-open-emoji'
accessibilityLabel='Open_emoji_selector'
icon='emoji'
theme={theme}
/>
);
});
return <BaseButton onPress={open} testID='messagebox-open-emoji' accessibilityLabel='Open_emoji_selector' icon='emoji' />;
};
export default ToggleEmojiButton;

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker, { Image, ImageOrVideo } from 'react-native-image-crop-picker';
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
import { dequal } from 'dequal';
import DocumentPicker from 'react-native-document-picker';
import { Q } from '@nozbe/watermelondb';
@ -20,7 +20,7 @@ import RecordAudio from './RecordAudio';
import I18n from '../../i18n';
import ReplyPreview from './ReplyPreview';
import debounce from '../../utils/debounce';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
// @ts-ignore
// eslint-disable-next-line import/extensions,import/no-unresolved
import LeftButtons from './LeftButtons';
@ -44,13 +44,15 @@ import {
} from './constants';
import CommandsPreview from './CommandsPreview';
import { getUserSelector } from '../../selectors/login';
import Navigation from '../../lib/Navigation';
import Navigation from '../../lib/navigation/appNavigation';
import { withActionSheet } from '../ActionSheet';
import { sanitizeLikeString } from '../../lib/database/utils';
import { CustomIcon } from '../../lib/Icons';
import { IMessage } from '../../definitions/IMessage';
import { forceJpgExtension } from './forceJpgExtension';
import { IPreviewItem, IUser } from '../../definitions';
import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
import { TSupportedThemes } from '../../theme';
if (isAndroid) {
require('./EmojiKeyboard');
@ -63,18 +65,18 @@ const imagePickerConfig = {
forceJpg: true
};
const libraryPickerConfig = {
const libraryPickerConfig: Options = {
multiple: true,
compressVideoPreset: 'Passthrough',
mediaType: 'any',
forceJpg: true
};
const videoPickerConfig = {
const videoPickerConfig: Options = {
mediaType: 'video'
};
export interface IMessageBoxProps {
export interface IMessageBoxProps extends IBaseScreen<MasterDetailInsideStackParamList, any> {
rid: string;
baseUrl: string;
message: IMessage;
@ -94,10 +96,9 @@ export interface IMessageBoxProps {
editRequest: Function;
onSubmit: Function;
typing: Function;
theme: string;
theme: TSupportedThemes;
replyCancel(): void;
showSend: boolean;
navigation: any;
children: JSX.Element;
isMasterDetail: boolean;
showActionSheet: Function;
@ -118,7 +119,7 @@ interface IMessageBoxState {
commandPreview: IPreviewItem[];
showCommandPreview: boolean;
command: {
appId?: any;
appId?: string;
};
tshow: boolean;
mentionLoading: boolean;
@ -132,17 +133,15 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
private focused: boolean;
private options: any;
private imagePickerConfig: Options;
private imagePickerConfig: any;
private libraryPickerConfig: Options;
private libraryPickerConfig: any;
private videoPickerConfig: Options;
private videoPickerConfig: any;
private room!: TSubscriptionModel;
private room: any;
private thread: any;
private thread!: TThreadModel;
private unsubscribeFocus: any;
@ -681,7 +680,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (result.success) {
return true;
}
Alert.alert(I18n.t('Error_uploading'), I18n.t(result.error));
Alert.alert(I18n.t('Error_uploading'), result.error && I18n.isTranslated(result.error) ? I18n.t(result.error) : result.error);
return false;
};
@ -713,7 +712,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
chooseFromLibrary = async () => {
logEvent(events.ROOM_BOX_ACTION_LIBRARY);
try {
let attachments = (await ImagePicker.openPicker(this.libraryPickerConfig)) as ImageOrVideo[];
// The type can be video or photo, however the lib understands that it is just one of them.
let attachments = (await ImagePicker.openPicker(this.libraryPickerConfig)) as unknown as ImageOrVideo[];
attachments = attachments.map(att => forceJpgExtension(att));
this.openShareView(attachments);
} catch (e) {
@ -757,12 +757,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
openShareView = (attachments: any) => {
const { message, replyCancel, replyWithMention } = this.props;
// Start a thread with an attachment
let { thread } = this;
let value: TThreadModel | IMessage = this.thread;
if (replyWithMention) {
thread = message;
value = message;
replyCancel();
}
Navigation.navigate('ShareView', { room: this.room, thread, attachments });
Navigation.navigate('ShareView', { room: this.room, thread: value, attachments });
};
createDiscussion = () => {
@ -1060,7 +1060,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const commandsPreviewAndMentions = !recording ? (
<>
<CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} />
<Mentions mentions={mentions} trackingType={trackingType} theme={theme} loading={mentionLoading} />
<Mentions mentions={mentions} trackingType={trackingType} loading={mentionLoading} />
</>
) : null;
@ -1071,7 +1071,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
username={user.username}
replying={replying}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
) : null;

View File

@ -3,7 +3,8 @@ import { StyleSheet, Text, View } from 'react-native';
import I18n from '../i18n';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import { themes } from '../lib/constants';
import { TSupportedThemes } from '../theme';
const styles = StyleSheet.create({
container: {
@ -24,7 +25,7 @@ const styles = StyleSheet.create({
});
interface IOrSeparator {
theme: string;
theme: TSupportedThemes;
}
const OrSeparator = React.memo(({ theme }: IOrSeparator) => {

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Text } from 'react-native';
import styles from './styles';
import { themes } from '../../../constants/colors';
import { themes } from '../../../lib/constants';
import Touch from '../../../utils/touch';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';

View File

@ -3,7 +3,7 @@ import { View } from 'react-native';
import range from 'lodash/range';
import styles from './styles';
import { themes } from '../../../constants/colors';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
const SIZE_EMPTY = 12;

View File

@ -3,7 +3,7 @@ import { View } from 'react-native';
import { Row } from 'react-native-easy-grid';
import styles from './styles';
import { themes } from '../../../constants/colors';
import { themes } from '../../../lib/constants';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { Grid } from 'react-native-easy-grid';
import { themes } from '../../../constants/colors';
import { themes } from '../../../lib/constants';
import { resetAttempts } from '../../../utils/localAuthentication';
import { TYPE } from '../constants';
import { getDiff, getLockedUntil } from '../utils';

View File

@ -3,7 +3,7 @@ import { Text, View } from 'react-native';
import { Row } from 'react-native-easy-grid';
import styles from './styles';
import { themes } from '../../../constants/colors';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
interface IPasscodeSubtitle {

View File

@ -3,7 +3,7 @@ import { Text, View } from 'react-native';
import { Row } from 'react-native-easy-grid';
import styles from './styles';
import { themes } from '../../../constants/colors';
import { themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
interface IPasscodeTitle {

View File

@ -9,8 +9,7 @@ import styles from './styles';
import Button from './Button';
import Dots from './Dots';
import { TYPE } from '../constants';
import { themes } from '../../../constants/colors';
import { PASSCODE_LENGTH } from '../../../constants/localAuthentication';
import { PASSCODE_LENGTH, themes } from '../../../lib/constants';
import { useTheme } from '../../../theme';
import LockIcon from './LockIcon';
import Title from './Title';
@ -20,7 +19,7 @@ interface IPasscodeBase {
type: string;
previousPasscode?: string;
title: string;
subtitle?: string;
subtitle?: string | null;
showBiometry?: boolean;
onEndProcess: Function;
onError?: Function;

View File

@ -14,7 +14,7 @@ interface IPasscodeChoose {
const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => {
const chooseRef = useRef<IBase>(null);
const confirmRef = useRef<IBase>(null);
const [subtitle, setSubtitle] = useState(null);
const [subtitle, setSubtitle] = useState<string | null>(null);
const [status, setStatus] = useState(TYPE.CHOOSE);
const [previousPasscode, setPreviouPasscode] = useState('');

View File

@ -7,10 +7,10 @@ import { sha256 } from 'js-sha256';
import Base, { IBase } from './Base';
import Locked from './Base/Locked';
import { TYPE } from './constants';
import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../constants/localAuthentication';
import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../lib/constants';
import { biometryAuth, resetAttempts } from '../../utils/localAuthentication';
import { getDiff, getLockedUntil } from './utils';
import { useUserPreferences } from '../../lib/userPreferences';
import { useUserPreferences } from '../../lib/methods/userPreferences';
import I18n from '../../i18n';
interface IPasscodePasscodeEnter {

View File

@ -1,7 +1,7 @@
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
import { LOCKED_OUT_TIMER_KEY, TIME_TO_LOCK } from '../../constants/localAuthentication';
import { LOCKED_OUT_TIMER_KEY, TIME_TO_LOCK } from '../../lib/constants';
export const getLockedUntil = async () => {
const t = await AsyncStorage.getItem(LOCKED_OUT_TIMER_KEY);

View File

@ -7,8 +7,8 @@ import Emoji from './message/Emoji';
import I18n from '../i18n';
import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import { themes } from '../lib/constants';
import { TSupportedThemes, useTheme, withTheme } from '../theme';
import { TGetCustomEmoji } from '../definitions/IEmoji';
import { TMessageModel, ILoggedUser } from '../definitions';
import SafeAreaView from './SafeAreaView';
@ -61,38 +61,37 @@ const styles = StyleSheet.create({
const standardEmojiStyle = { fontSize: 20 };
const customEmojiStyle = { width: 20, height: 20 };
interface IItem {
interface ISharedFields {
user?: Pick<ILoggedUser, 'username'>;
baseUrl: string;
getCustomEmoji: TGetCustomEmoji;
}
interface IItem extends ISharedFields {
item: {
usernames: any;
usernames: string[];
emoji: string;
};
user?: Pick<ILoggedUser, 'username'>;
baseUrl?: string;
getCustomEmoji?: TGetCustomEmoji;
theme?: string;
}
interface IModalContent {
interface IModalContent extends ISharedFields {
message?: TMessageModel;
onClose: () => void;
theme: string;
theme: TSupportedThemes;
}
interface IReactionsModal {
message?: any;
user?: Pick<ILoggedUser, 'username'>;
interface IReactionsModal extends ISharedFields {
message?: TMessageModel;
isVisible: boolean;
onClose(): void;
baseUrl: string;
getCustomEmoji?: TGetCustomEmoji;
theme: string;
}
const Item = React.memo(({ item, user, baseUrl, getCustomEmoji, theme }: IItem) => {
const Item = React.memo(({ item, user, baseUrl, getCustomEmoji }: IItem) => {
const { theme } = useTheme();
const count = item.usernames.length;
let usernames = item.usernames
.slice(0, 3)
.map((username: any) => (username === user?.username ? I18n.t('you') : username))
.map((username: string) => (username === user?.username ? I18n.t('you') : username))
.join(', ');
if (count > 3) {
usernames = `${usernames} ${I18n.t('and_more')} ${count - 3}`;
@ -106,15 +105,15 @@ const Item = React.memo(({ item, user, baseUrl, getCustomEmoji, theme }: IItem)
content={item.emoji}
standardEmojiStyle={standardEmojiStyle}
customEmojiStyle={customEmojiStyle}
baseUrl={baseUrl!}
getCustomEmoji={getCustomEmoji!}
baseUrl={baseUrl}
getCustomEmoji={getCustomEmoji}
/>
</View>
<View style={styles.peopleItemContainer}>
<Text style={[styles.reactCount, { color: themes[theme!].buttonText }]}>
<Text style={[styles.reactCount, { color: themes[theme].buttonText }]}>
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
</Text>
<Text style={[styles.peopleReacted, { color: themes[theme!].buttonText }]}>{usernames}</Text>
<Text style={[styles.peopleReacted, { color: themes[theme].buttonText }]}>{usernames}</Text>
</View>
</View>
);
@ -143,18 +142,21 @@ const ModalContent = React.memo(({ message, onClose, ...props }: IModalContent)
});
const ReactionsModal = React.memo(
({ isVisible, onClose, theme, ...props }: IReactionsModal) => (
<Modal
isVisible={isVisible}
onBackdropPress={onClose}
onBackButtonPress={onClose}
backdropOpacity={0.8}
onSwipeComplete={onClose}
swipeDirection={['up', 'left', 'right', 'down']}>
<ModalContent onClose={onClose} theme={theme} {...props} />
</Modal>
),
(prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.theme === nextProps.theme
({ isVisible, onClose, ...props }: IReactionsModal) => {
const { theme } = useTheme();
return (
<Modal
isVisible={isVisible}
onBackdropPress={onClose}
onBackButtonPress={onClose}
backdropOpacity={0.8}
onSwipeComplete={onClose}
swipeDirection={['up', 'left', 'right', 'down']}>
<ModalContent onClose={onClose} theme={theme} {...props} />
</Modal>
);
},
(prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible
);
ReactionsModal.displayName = 'ReactionsModal';

View File

@ -2,13 +2,15 @@
import React from 'react';
import { Dimensions, View } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import { Provider } from 'react-redux';
import Header from '../Header';
import { longText } from '../../../storybook/utils';
import { ThemeContext } from '../../theme';
import { store } from '../../../storybook/stories';
import RoomHeaderComponent from './RoomHeader';
const stories = storiesOf('RoomHeader', module);
const stories = storiesOf('RoomHeader', module).addDecorator(story => <Provider store={store}>{story()}</Provider>);
// TODO: refactor after react-navigation v6
const HeaderExample = ({ title }) => (

View File

@ -3,11 +3,11 @@ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import I18n from '../../i18n';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { MarkdownPreview } from '../markdown';
import RoomTypeIcon from '../RoomTypeIcon';
import { withTheme } from '../../theme';
import { TUserStatus } from '../../definitions';
import { TUserStatus, IOmnichannelSource } from '../../definitions';
import { useTheme } from '../../theme';
const HIT_SLOP = {
top: 5,
@ -44,9 +44,8 @@ const styles = StyleSheet.create({
type TRoomHeaderSubTitle = {
usersTyping: [];
theme: string;
subtitle: string;
renderFunc: any;
renderFunc?: () => React.ReactElement;
scale: number;
};
@ -55,7 +54,6 @@ type TRoomHeaderHeaderTitle = {
tmid: string;
prid: string;
scale: number;
theme: string;
testID: string;
};
@ -69,15 +67,16 @@ interface IRoomHeader {
tmid: string;
teamMain: boolean;
status: TUserStatus;
theme?: string;
usersTyping: [];
isGroupChat: boolean;
parentTitle: string;
onPress: () => void;
testID: string;
sourceType?: IOmnichannelSource;
}
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, theme, scale }: TRoomHeaderSubTitle) => {
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
const { theme } = useTheme();
const fontSize = getSubTitleSize(scale);
// typing
if (usersTyping.length) {
@ -108,7 +107,8 @@ const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, theme, scale }
return null;
});
const HeaderTitle = React.memo(({ title, tmid, prid, scale, theme, testID }: TRoomHeaderHeaderTitle) => {
const HeaderTitle = React.memo(({ title, tmid, prid, scale, testID }: TRoomHeaderHeaderTitle) => {
const { theme } = useTheme();
const titleStyle = { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor };
if (!tmid && !prid) {
return (
@ -133,12 +133,13 @@ const Header = React.memo(
prid,
tmid,
onPress,
theme,
isGroupChat,
teamMain,
testID,
usersTyping = []
usersTyping = [],
sourceType
}: IRoomHeader) => {
const { theme } = useTheme();
const portrait = height > width;
let scale = 1;
@ -153,7 +154,7 @@ const Header = React.memo(
renderFunc = () => (
<View style={styles.titleContainer}>
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
<Text style={[styles.subtitle, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
{parentTitle}
</Text>
</View>
@ -168,25 +169,24 @@ const Header = React.memo(
accessibilityLabel={title}
onPress={handleOnPress}
style={styles.container}
// @ts-ignore
disabled={tmid}
disabled={!!tmid}
hitSlop={HIT_SLOP}>
<View style={styles.titleContainer}>
{tmid ? null : (
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
<RoomTypeIcon
type={prid ? 'discussion' : type}
isGroupChat={isGroupChat}
status={status}
teamMain={teamMain}
sourceType={sourceType}
/>
)}
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} theme={theme!} testID={testID} />
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} testID={testID} />
</View>
<SubTitle
usersTyping={tmid ? [] : usersTyping}
subtitle={subtitle}
theme={theme!}
renderFunc={renderFunc}
scale={scale}
/>
<SubTitle usersTyping={tmid ? [] : usersTyping} subtitle={subtitle} renderFunc={renderFunc} scale={scale} />
</TouchableOpacity>
);
}
);
export default withTheme(Header);
export default Header;

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@ import { dequal } from 'dequal';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { IApplicationState, TUserStatus } from '../../definitions';
import { IApplicationState, TUserStatus, IOmnichannelSource } from '../../definitions';
import { withDimensions } from '../../dimensions';
import I18n from '../../i18n';
import RoomHeader from './RoomHeader';
@ -27,12 +27,26 @@ interface IRoomHeaderContainerProps {
parentTitle: string;
isGroupChat: boolean;
testID: string;
sourceType?: IOmnichannelSource;
}
class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
shouldComponentUpdate(nextProps: IRoomHeaderContainerProps) {
const { type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height, teamMain } =
this.props;
const {
type,
title,
subtitle,
status,
statusText,
connecting,
connected,
onPress,
usersTyping,
width,
height,
teamMain,
sourceType
} = this.props;
if (nextProps.type !== type) {
return true;
}
@ -63,6 +77,9 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
if (!dequal(nextProps.usersTyping, usersTyping)) {
return true;
}
if (!dequal(nextProps.sourceType, sourceType)) {
return true;
}
if (nextProps.onPress !== onPress) {
return true;
}
@ -90,7 +107,8 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
height,
parentTitle,
isGroupChat,
testID
testID,
sourceType
} = this.props;
let subtitle;
@ -118,6 +136,7 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
isGroupChat={isGroupChat}
testID={testID}
onPress={onPress}
sourceType={sourceType}
/>
);
}

View File

@ -4,34 +4,15 @@ import { RectButton } from 'react-native-gesture-handler';
import { isRTL } from '../../i18n';
import { CustomIcon } from '../../lib/Icons';
import { themes } from '../../constants/colors';
import { DisplayMode } from '../../constants/constantDisplayMode';
import { DisplayMode, themes } from '../../lib/constants';
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
interface ILeftActions {
theme: string;
transX: any;
isRead: boolean;
width: number;
onToggleReadPress(): void;
displayMode: string;
}
interface IRightActions {
theme: string;
transX: any;
favorite: boolean;
width: number;
toggleFav(): void;
onHidePress(): void;
displayMode: string;
}
import { ILeftActionsProps, IRightActionsProps } from './interfaces';
const reverse = new Animated.Value(isRTL() ? -1 : 1);
const CONDENSED_ICON_SIZE = 24;
const EXPANDED_ICON_SIZE = 28;
export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress, displayMode }: ILeftActions) => {
export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress, displayMode }: ILeftActionsProps) => {
const translateX = Animated.multiply(
transX.interpolate({
inputRange: [0, ACTION_WIDTH],
@ -44,7 +25,7 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
return (
<View style={[styles.actionsContainer, styles.actionLeftContainer]} pointerEvents='box-none'>
<View style={[styles.actionsContainer, styles.actionsLeftContainer]} pointerEvents='box-none'>
<Animated.View
style={[
styles.actionLeftButtonContainer,
@ -71,7 +52,7 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
});
export const RightActions = React.memo(
({ transX, favorite, width, toggleFav, onHidePress, theme, displayMode }: IRightActions) => {
({ transX, favorite, width, toggleFav, onHidePress, theme, displayMode }: IRightActionsProps) => {
const translateXFav = Animated.multiply(
transX.interpolate({
inputRange: [-width / 2, -ACTION_WIDTH * 2, 0],

View File

@ -2,8 +2,8 @@ import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import Avatar from '../../containers/Avatar';
import { DisplayMode } from '../../constants/constantDisplayMode';
import Avatar from '../Avatar';
import { DisplayMode } from '../../lib/constants';
import TypeIcon from './TypeIcon';
import styles from './styles';
@ -18,7 +18,8 @@ const IconOrAvatar = ({
teamMain,
showLastMessage,
theme,
displayMode
displayMode,
sourceType
}) => {
if (showAvatar) {
return (
@ -38,6 +39,7 @@ const IconOrAvatar = ({
teamMain={teamMain}
size={24}
style={{ marginRight: 12 }}
sourceType={sourceType}
/>
</View>
);

View File

@ -3,28 +3,11 @@ import { dequal } from 'dequal';
import I18n from '../../i18n';
import styles from './styles';
import { MarkdownPreview } from '../../containers/markdown';
import { themes } from '../../constants/colors';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
import { MarkdownPreview } from '../markdown';
import { E2E_MESSAGE_TYPE, E2E_STATUS, themes } from '../../lib/constants';
import { ILastMessageProps } from './interfaces';
interface ILastMessage {
theme: string;
lastMessage: {
u: any;
pinned: boolean;
t: string;
attachments: any;
msg: string;
e2e: string;
};
type: string;
showLastMessage: boolean;
username: string;
useRealName: boolean;
alert: boolean;
}
const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessage>) => {
const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessageProps>) => {
if (!showLastMessage) {
return '';
}
@ -64,7 +47,7 @@ const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }
const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps);
const LastMessage = React.memo(
({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessage) => (
({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessageProps) => (
<MarkdownPreview
msg={formatMsg({
lastMessage,

View File

@ -11,57 +11,8 @@ import UpdatedAt from './UpdatedAt';
import Touchable from './Touchable';
import Tag from './Tag';
import I18n from '../../i18n';
import { DisplayMode } from '../../constants/constantDisplayMode';
import { TUserStatus } from '../../definitions';
interface IRoomItem {
rid: string;
type: string;
prid: string;
name: string;
avatar: string;
showLastMessage: boolean;
username: string;
avatarSize: number;
testID: string;
width: number;
status: TUserStatus;
useRealName: boolean;
theme: string;
isFocused: boolean;
isGroupChat: boolean;
isRead: boolean;
teamMain: boolean;
date: string;
accessibilityLabel: string;
lastMessage: {
u: any;
pinned: boolean;
t: string;
attachments: any;
msg: string;
e2e: string;
};
favorite: boolean;
alert: boolean;
hideUnreadStatus: boolean;
unread: number;
userMentions: number;
groupMentions: number;
tunread: [];
tunreadUser: [];
tunreadGroup: [];
swipeEnabled: boolean;
toggleFav(): void;
toggleRead(): void;
onPress(): void;
onLongPress(): void;
hideChannel(): void;
autoJoin: boolean;
size?: number;
showAvatar: boolean;
displayMode: string;
}
import { DisplayMode } from '../../lib/constants';
import { IRoomItemProps } from './interfaces';
const RoomItem = ({
rid,
@ -70,7 +21,6 @@ const RoomItem = ({
name,
avatar,
width,
avatarSize = 48,
username,
showLastMessage,
status = 'offline',
@ -101,8 +51,9 @@ const RoomItem = ({
teamMain,
autoJoin,
showAvatar,
displayMode
}: IRoomItem) => (
displayMode,
sourceType
}: IRoomItemProps) => (
<Touchable
onPress={onPress}
onLongPress={onLongPress}
@ -122,7 +73,6 @@ const RoomItem = ({
<Wrapper
accessibilityLabel={accessibilityLabel}
avatar={avatar}
avatarSize={avatarSize}
type={type}
theme={theme}
rid={rid}
@ -132,12 +82,20 @@ const RoomItem = ({
teamMain={teamMain}
displayMode={displayMode}
showAvatar={showAvatar}
showLastMessage={showLastMessage}>
showLastMessage={showLastMessage}
sourceType={sourceType}>
{showLastMessage && displayMode === DisplayMode.Expanded ? (
<>
<View style={styles.titleContainer}>
{showAvatar ? (
<TypeIcon type={type} prid={prid} status={status} isGroupChat={isGroupChat} theme={theme} teamMain={teamMain} />
<TypeIcon
type={type}
prid={prid}
status={status}
isGroupChat={isGroupChat}
teamMain={teamMain}
sourceType={sourceType}
/>
) : null}
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
{autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null}
@ -170,10 +128,10 @@ const RoomItem = ({
prid={prid}
status={status}
isGroupChat={isGroupChat}
theme={theme}
teamMain={teamMain}
size={22}
style={{ marginRight: 8 }}
sourceType={sourceType}
/>
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
{autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null}

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Text, View } from 'react-native';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import styles from './styles';
@ -11,7 +11,7 @@ interface ITag {
}
const Tag = React.memo(({ name, testID }: ITag) => {
const { theme }: any = useTheme();
const { theme } = useTheme();
return (
<View style={[styles.tagContainer, { backgroundColor: themes[theme].borderColor }]}>

View File

@ -2,16 +2,10 @@ import React from 'react';
import { Text } from 'react-native';
import styles from './styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { ITitleProps } from './interfaces';
interface ITitle {
name: string;
theme: string;
hideUnreadStatus: boolean;
alert: boolean;
}
const Title = React.memo(({ name, theme, hideUnreadStatus, alert }: ITitle) => (
const Title = React.memo(({ name, theme, hideUnreadStatus, alert }: ITitleProps) => (
<Text
style={[styles.title, alert && !hideUnreadStatus && styles.alert, { color: themes[theme].titleText }]}
ellipsizeMode='tail'

View File

@ -1,45 +1,28 @@
import React from 'react';
import { Animated } from 'react-native';
import { LongPressGestureHandler, PanGestureHandler, State } from 'react-native-gesture-handler';
import {
GestureEvent,
HandlerStateChangeEventPayload,
LongPressGestureHandler,
PanGestureHandler,
PanGestureHandlerEventPayload,
State
} from 'react-native-gesture-handler';
import Touch from '../../utils/touch';
import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles';
import { isRTL } from '../../i18n';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { LeftActions, RightActions } from './Actions';
interface ITouchableProps {
children: JSX.Element;
type: string;
onPress(): void;
onLongPress(): void;
testID: string;
width: number;
favorite: boolean;
isRead: boolean;
rid: string;
toggleFav: Function;
toggleRead: Function;
hideChannel: Function;
theme: string;
isFocused: boolean;
swipeEnabled: boolean;
displayMode: string;
}
import { ITouchableProps } from './interfaces';
class Touchable extends React.Component<ITouchableProps, any> {
private dragX: Animated.Value;
private rowOffSet: Animated.Value;
private reverse: Animated.Value;
private transX: Animated.AnimatedAddition;
private transXReverse: Animated.AnimatedMultiplication;
private _onGestureEvent: (...args: any[]) => void;
private _onGestureEvent: (event: GestureEvent<PanGestureHandlerEventPayload>) => void;
private _value: number;
constructor(props: ITouchableProps) {
@ -56,19 +39,19 @@ class Touchable extends React.Component<ITouchableProps, any> {
this._value = 0;
}
_onHandlerStateChange = ({ nativeEvent }: any) => {
_onHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload & PanGestureHandlerEventPayload }) => {
if (nativeEvent.oldState === State.ACTIVE) {
this._handleRelease(nativeEvent);
}
};
onLongPressHandlerStateChange = ({ nativeEvent }: any) => {
onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
if (nativeEvent.state === State.ACTIVE) {
this.onLongPress();
}
};
_handleRelease = (nativeEvent: any) => {
_handleRelease = (nativeEvent: PanGestureHandlerEventPayload) => {
const { translationX } = nativeEvent;
const { rowState } = this.state;
this._value += translationX;
@ -154,7 +137,7 @@ class Touchable extends React.Component<ITouchableProps, any> {
this._animateRow(toValue);
};
_animateRow = (toValue: any) => {
_animateRow = (toValue: number) => {
this.rowOffSet.setValue(this._value);
this._value = toValue;
this.dragX.setValue(0);

View File

@ -0,0 +1,17 @@
import React from 'react';
import RoomTypeIcon from '../RoomTypeIcon';
import { ITypeIconProps } from './interfaces';
const TypeIcon = React.memo(({ type, prid, status, isGroupChat, teamMain, size, style }: ITypeIconProps) => (
<RoomTypeIcon
type={prid ? 'discussion' : type}
isGroupChat={isGroupChat}
status={status}
teamMain={teamMain}
size={size}
style={style}
/>
));
export default TypeIcon;

View File

@ -2,17 +2,11 @@ import React from 'react';
import { Text } from 'react-native';
import styles from './styles';
import { themes } from '../../constants/colors';
import { themes } from '../../lib/constants';
import { capitalize } from '../../utils/room';
import { IUpdatedAtProps } from './interfaces';
interface IUpdatedAt {
date: string;
theme: string;
hideUnreadStatus: boolean;
alert: boolean;
}
const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdatedAt) => {
const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdatedAtProps) => {
if (!date) {
return null;
}

View File

@ -1,29 +1,12 @@
import React from 'react';
import { View } from 'react-native';
import { themes } from '../../constants/colors';
import { DisplayMode } from '../../constants/constantDisplayMode';
import { DisplayMode, themes } from '../../lib/constants';
import IconOrAvatar from './IconOrAvatar';
import { IWrapperProps } from './interfaces';
import styles from './styles';
interface IWrapper {
accessibilityLabel: string;
avatar: string;
avatarSize: number;
type: string;
theme: string;
rid: string;
children: JSX.Element;
displayMode: string;
prid: string;
showLastMessage: boolean;
status: string;
isGroupChat: boolean;
teamMain: boolean;
showAvatar: boolean;
}
const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapper) => (
const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapperProps): React.ReactElement => (
<View
style={[styles.container, displayMode === DisplayMode.Condensed && styles.containerCondensed]}
accessibilityLabel={accessibilityLabel}>

View File

@ -5,36 +5,10 @@ import I18n from '../../i18n';
import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles';
import { formatDate } from '../../utils/room';
import RoomItem from './RoomItem';
import { TUserStatus } from '../../definitions';
import { ISubscription, TUserStatus } from '../../definitions';
import { IRoomItemContainerProps } from './interfaces';
export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED };
interface IRoomItemContainerProps {
item: any;
showLastMessage: boolean;
id: string;
onPress: Function;
onLongPress: Function;
username: string;
avatarSize: number;
width: number;
status: TUserStatus;
toggleFav(): void;
toggleRead(): void;
hideChannel(): void;
useRealName: boolean;
getUserPresence: Function;
connected: boolean;
theme: string;
isFocused: boolean;
getRoomTitle: Function;
getRoomAvatar: Function;
getIsGroupChat: Function;
getIsRead: Function;
swipeEnabled: boolean;
autoJoin: boolean;
showAvatar: boolean;
displayMode: string;
}
const attrs = [
'width',
@ -50,12 +24,9 @@ const attrs = [
];
class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
private mounted: boolean;
private roomSubscription: any;
private roomSubscription: ISubscription | undefined;
static defaultProps: Partial<IRoomItemContainerProps> = {
avatarSize: 48,
status: 'offline',
getUserPresence: () => {},
getRoomTitle: () => 'title',
@ -67,24 +38,22 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
constructor(props: IRoomItemContainerProps) {
super(props);
this.mounted = false;
this.init();
}
componentDidMount() {
this.mounted = true;
const { connected, getUserPresence, id } = this.props;
if (connected && this.isDirect) {
getUserPresence(id);
}
}
shouldComponentUpdate(nextProps: any) {
const { props }: any = this;
shouldComponentUpdate(nextProps: IRoomItemContainerProps) {
const { props } = this;
return !attrs.every(key => props[key] === nextProps[key]);
}
componentDidUpdate(prevProps: any) {
componentDidUpdate(prevProps: IRoomItemContainerProps) {
const { connected, getUserPresence, id } = this.props;
if (prevProps.connected !== connected && connected && this.isDirect) {
getUserPresence(id);
@ -106,7 +75,7 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
const {
item: { t },
id
}: any = this.props;
} = this.props;
return t === 'd' && id && !this.isGroupChat;
}
@ -144,7 +113,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
hideChannel,
theme,
isFocused,
avatarSize,
status,
showLastMessage,
username,
@ -177,7 +145,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
}
return (
// @ts-ignore
<RoomItem
name={name}
avatar={avatar}
@ -197,7 +164,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
type={item.t}
theme={theme}
isFocused={isFocused}
size={avatarSize}
prid={item.prid}
status={status}
hideUnreadStatus={item.hideUnreadStatus}
@ -217,6 +183,7 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
autoJoin={autoJoin}
showAvatar={showAvatar}
displayMode={displayMode}
sourceType={item.source}
/>
);
}

View File

@ -0,0 +1,166 @@
import React from 'react';
import { Animated } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { TUserStatus, ILastMessage, SubscriptionType, IOmnichannelSource } from '../../definitions';
export interface ILeftActionsProps {
theme: TSupportedThemes;
transX: Animated.AnimatedAddition | Animated.AnimatedMultiplication;
isRead: boolean;
width: number;
onToggleReadPress(): void;
displayMode: string;
}
export interface IRightActionsProps {
theme: TSupportedThemes;
transX: Animated.AnimatedAddition | Animated.AnimatedMultiplication;
favorite: boolean;
width: number;
toggleFav(): void;
onHidePress(): void;
displayMode: string;
}
export interface ITitleProps {
name: string;
theme: TSupportedThemes;
hideUnreadStatus: boolean;
alert: boolean;
}
export interface IUpdatedAtProps {
date: string;
theme: TSupportedThemes;
hideUnreadStatus: boolean;
alert: boolean;
}
export interface IWrapperProps {
accessibilityLabel: string;
avatar: string;
type: string;
theme: TSupportedThemes;
rid: string;
children: React.ReactElement;
displayMode: string;
prid: string;
showLastMessage: boolean;
status: string;
isGroupChat: boolean;
teamMain: boolean;
showAvatar: boolean;
sourceType: IOmnichannelSource;
}
export interface ITypeIconProps {
type: string;
status: TUserStatus;
prid: string;
isGroupChat: boolean;
teamMain: boolean;
theme?: TSupportedThemes;
size?: number;
style?: object;
sourceType: IOmnichannelSource;
}
export interface IRoomItemContainerProps {
[key: string]: string | boolean | Function | number;
item: any;
showLastMessage: boolean;
id: string;
onPress: (item: any) => void;
onLongPress: (item: any) => Promise<void>;
username: string;
width: number;
status: TUserStatus;
toggleFav(): void;
toggleRead(): void;
hideChannel(): void;
useRealName: boolean;
getUserPresence: (uid: string) => void;
connected: boolean;
theme: TSupportedThemes;
isFocused: boolean;
getRoomTitle: (item: any) => string;
getRoomAvatar: (item: any) => string;
getIsGroupChat: (item: any) => boolean;
getIsRead: (item: any) => boolean;
swipeEnabled: boolean;
autoJoin: boolean;
showAvatar: boolean;
displayMode: string;
}
export interface IRoomItemProps {
rid: string;
type: SubscriptionType;
prid: string;
name: string;
avatar: string;
showLastMessage: boolean;
username: string;
testID: string;
width: number;
status: TUserStatus;
useRealName: boolean;
theme: TSupportedThemes;
isFocused: boolean;
isGroupChat: boolean;
isRead: boolean;
teamMain: boolean;
date: string;
accessibilityLabel: string;
lastMessage: ILastMessage;
favorite: boolean;
alert: boolean;
hideUnreadStatus: boolean;
unread: number;
userMentions: number;
groupMentions: number;
tunread: [];
tunreadUser: [];
tunreadGroup: [];
swipeEnabled: boolean;
toggleFav(): void;
toggleRead(): void;
onPress(): void;
onLongPress(): void;
hideChannel(): void;
autoJoin: boolean;
size?: number;
showAvatar: boolean;
displayMode: string;
sourceType: IOmnichannelSource;
}
export interface ILastMessageProps {
theme: TSupportedThemes;
lastMessage: ILastMessage;
type: SubscriptionType;
showLastMessage: boolean;
username: string;
useRealName: boolean;
alert: boolean;
}
export interface ITouchableProps {
children: JSX.Element;
type: string;
onPress(): void;
onLongPress(): void;
testID: string;
width: number;
favorite: boolean;
isRead: boolean;
rid: string;
toggleFav: Function;
toggleRead: Function;
hideChannel: Function;
theme: TSupportedThemes;
isFocused: boolean;
swipeEnabled: boolean;
displayMode: string;
}

View File

@ -8,7 +8,7 @@ export const ACTION_WIDTH = 80;
export const SMALL_SWIPE = ACTION_WIDTH / 2;
export const LONG_SWIPE = ACTION_WIDTH * 3;
export default StyleSheet.create<any>({
export default StyleSheet.create({
flex: {
flex: 1
},

View File

@ -0,0 +1,51 @@
import React from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import { SvgUri } from 'react-native-svg';
import { useSelector } from 'react-redux';
import { OmnichannelSourceType, IApplicationState, IOmnichannelSource } from '../../definitions';
import { STATUS_COLORS } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
const iconMap = {
widget: 'livechat-monochromatic',
email: 'mail',
sms: 'sms',
app: 'omnichannel',
api: 'omnichannel',
other: 'omnichannel'
};
interface IOmnichannelRoomIconProps {
size: number;
type: string;
style?: StyleProp<ViewStyle>;
status?: string;
sourceType?: IOmnichannelSource;
}
export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnichannelRoomIconProps) => {
const baseUrl = useSelector((state: IApplicationState) => state.server?.server);
const connected = useSelector((state: IApplicationState) => state.meteor?.connected);
if (sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon && connected) {
return (
<SvgUri
height={size}
width={size}
color={STATUS_COLORS[status || 'offline']}
uri={`${baseUrl}/api/apps/public/${sourceType.id}/get-sidebar-icon?icon=${sourceType.sidebarIcon}`}
style={style}
/>
);
}
return (
<CustomIcon
name={iconMap[sourceType?.type || 'other']}
size={size}
style={style}
color={STATUS_COLORS[status || 'offline']}
/>
);
};

View File

@ -1,11 +1,12 @@
import React from 'react';
import { StyleSheet, ViewStyle } from 'react-native';
import { CustomIcon } from '../lib/Icons';
import { STATUS_COLORS, themes } from '../constants/colors';
import Status from './Status/Status';
import { withTheme } from '../theme';
import { TUserStatus } from '../definitions';
import { OmnichannelRoomIcon } from './OmnichannelRoomIcon';
import { CustomIcon } from '../../lib/Icons';
import { STATUS_COLORS, themes } from '../../lib/constants';
import Status from '../Status/Status';
import { useTheme } from '../../theme';
import { TUserStatus, IOmnichannelSource } from '../../definitions';
const styles = StyleSheet.create({
icon: {
@ -14,21 +15,23 @@ const styles = StyleSheet.create({
});
interface IRoomTypeIcon {
theme?: string;
type: string;
isGroupChat?: boolean;
teamMain?: boolean;
status?: TUserStatus;
size?: number;
style?: ViewStyle;
sourceType?: IOmnichannelSource;
}
const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, teamMain, size = 16 }: IRoomTypeIcon) => {
const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, teamMain, size = 16, sourceType }: IRoomTypeIcon) => {
const { theme } = useTheme();
if (!type) {
return null;
}
const color = themes[theme!].titleText;
const color = themes[theme].titleText;
const iconStyle = [styles.icon, { color }, style];
if (type === 'd' && !isGroupChat) {
@ -38,6 +41,10 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, team
return <Status style={[iconStyle, { color: STATUS_COLORS[status] }]} size={size} status={status} />;
}
if (type === 'l') {
return <OmnichannelRoomIcon style={[styles.icon, style]} size={size} type={type} status={status} sourceType={sourceType} />;
}
// TODO: move this to a separate function
let icon = 'channel-private';
if (teamMain) {
@ -52,11 +59,9 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, team
} else {
icon = 'mention';
}
} else if (type === 'l') {
icon = 'omnichannel';
}
return <CustomIcon name={icon} size={size} style={iconStyle} />;
});
export default withTheme(RoomTypeIcon);
export default RoomTypeIcon;

View File

@ -1,9 +1,9 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { StyleSheet, ViewProps } from 'react-native';
import { SafeAreaView as SafeAreaContext } from 'react-native-safe-area-context';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import { themes } from '../lib/constants';
import { useTheme } from '../theme';
const styles = StyleSheet.create({
view: {
@ -11,22 +11,24 @@ const styles = StyleSheet.create({
}
});
interface ISafeAreaView {
testID?: string;
theme?: string;
type SupportedChildren = React.ReactElement | React.ReactElement[] | null;
type TSafeAreaViewChildren = SupportedChildren | SupportedChildren[];
interface ISafeAreaView extends ViewProps {
vertical?: boolean;
style?: object;
children: React.ReactNode;
children: TSafeAreaViewChildren;
}
const SafeAreaView = React.memo(({ style, children, testID, theme, vertical = true, ...props }: ISafeAreaView) => (
<SafeAreaContext
style={[styles.view, { backgroundColor: themes[theme!].auxiliaryBackground }, style]}
edges={vertical ? ['right', 'left'] : undefined}
testID={testID}
{...props}>
{children}
</SafeAreaContext>
));
const SafeAreaView = React.memo(({ style, children, vertical = true, ...props }: ISafeAreaView) => {
const { theme } = useTheme();
return (
<SafeAreaContext
style={[styles.view, { backgroundColor: themes[theme].auxiliaryBackground }, style]}
edges={vertical ? ['right', 'left'] : undefined}
{...props}>
{children}
</SafeAreaContext>
);
});
export default withTheme(SafeAreaView);
export default SafeAreaView;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { StyleSheet, Text, TextInput as RNTextInput, TextInputProps, View } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import { themes } from '../constants/colors';
import { themes } from '../lib/constants';
import I18n from '../i18n';
import { CustomIcon } from '../lib/Icons';
import TextInput from '../presentation/TextInput';

View File

@ -4,7 +4,7 @@ import { StyleSheet, View } from 'react-native';
import I18n from '../i18n';
import { useTheme } from '../theme';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import { themes } from '../lib/constants';
import TextInput from '../presentation/TextInput';
import { isIOS, isTablet } from '../utils/deviceInfo';
import { useOrientation } from '../dimensions';

View File

@ -0,0 +1,64 @@
import React from 'react';
// @ts-ignore // TODO: Remove on react-native update
import { Pressable, Text, View } from 'react-native';
import FastImage from '@rocket.chat/react-native-fast-image';
import { IServerInfo } from '../../definitions';
import Check from '../Check';
import styles, { ROW_HEIGHT } from './styles';
import { themes } from '../../lib/constants';
import { isIOS } from '../../utils/deviceInfo';
import { useTheme } from '../../theme';
export { ROW_HEIGHT };
interface IServerItem {
item: IServerInfo;
onPress(): void;
onLongPress?(): void;
hasCheck?: boolean;
}
const defaultLogo = require('../../static/images/logo.png');
const ServerItem = React.memo(({ item, onPress, onLongPress, hasCheck }: IServerItem) => {
const { theme } = useTheme();
return (
<Pressable
onPress={onPress}
onLongPress={() => onLongPress?.()}
testID={`rooms-list-header-server-${item.id}`}
android_ripple={{ color: themes[theme].bannerBackground }}
style={({ pressed }: { pressed: boolean }) => ({
backgroundColor: isIOS && pressed ? themes[theme].bannerBackground : themes[theme].backgroundColor
})}>
<View style={styles.serverItemContainer}>
{item.iconURL ? (
<FastImage
source={{
uri: item.iconURL,
priority: FastImage.priority.high
}}
// @ts-ignore TODO: Remove when updating FastImage
defaultSource={defaultLogo}
style={styles.serverIcon}
onError={() => console.log('err_loading_server_icon')}
/>
) : (
<FastImage source={defaultLogo} style={styles.serverIcon} />
)}
<View style={styles.serverTextContainer}>
<Text numberOfLines={1} style={[styles.serverName, { color: themes[theme].titleText }]}>
{item.name || item.id}
</Text>
<Text numberOfLines={1} style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}>
{item.id}
</Text>
</View>
{hasCheck ? <Check /> : null}
</View>
</Pressable>
);
});
export default ServerItem;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { StyleProp, TextStyle } from 'react-native';
import { CustomIcon } from '../../lib/Icons';
import { STATUS_COLORS } from '../../constants/colors';
import { STATUS_COLORS } from '../../lib/constants';
import { IStatus } from './definition';
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: Omit<IStatus, 'id'>) => {

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