Merge 4.27.0 into master (#4135)
This commit is contained in:
parent
3b20ea6596
commit
601d94444b
|
@ -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:
|
||||
|
|
10
.eslintrc.js
10
.eslintrc.js
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
Binary file not shown.
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: '' })}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -23,4 +23,5 @@ export interface IAvatar {
|
|||
rid?: string;
|
||||
blockUnauthenticatedAccess?: boolean;
|
||||
serverVersion: string | null;
|
||||
externalProviderUrl?: string;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
}}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
const MessageboxContext = React.createContext<any>();
|
||||
const MessageboxContext = React.createContext<any>(null);
|
||||
export default MessageboxContext;
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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('');
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 }) => (
|
||||
|
|
|
@ -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
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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],
|
|
@ -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>
|
||||
);
|
|
@ -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,
|
|
@ -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}
|
|
@ -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 }]}>
|
|
@ -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'
|
|
@ -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);
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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}>
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
},
|
|
@ -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']}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue