Merge branch 'develop' into new.add-discusions-roomactionsview

This commit is contained in:
Gerzon Z 2021-10-04 13:14:07 -04:00
commit f9dc2a975f
74 changed files with 5653 additions and 499 deletions

View File

@ -31,7 +31,7 @@ Also check the [#react-native](https://open.rocket.chat/channel/react-native) co
Are you a dev and would like to help? Found a bug that you would like to report or a missing feature that you would like to work on? Great! We have written down a [Contribution guide](https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/CONTRIBUTING.md) so you can start easily. Are you a dev and would like to help? Found a bug that you would like to report or a missing feature that you would like to work on? Great! We have written down a [Contribution guide](https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/CONTRIBUTING.md) so you can start easily.
## Whitelabel ## Whitelabel
Do you want to make the app run on your own server only? [Follow our whitelabel documentation.](https://docs.rocket.chat/guides/developer/mobile-apps/whitelabeling-mobile-apps) Do you want to make the app run on your own server only? [Follow our whitelabel documentation.](https://developer.rocket.chat/mobile-app/mobile-app-white-labelling)
## Engage with us ## Engage with us
### Share your story ### Share your story

View File

@ -1322,6 +1322,595 @@ exports[`Storyshots BackgroundContainer text 1`] = `
</View> </View>
`; `;
exports[`Storyshots CannedResponseItem Itens 1`] = `
Array [
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"backgroundColor": "#ffffff",
"maxHeight": 141,
"minHeight": 117,
"opacity": 1,
"padding": 16,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"height": 36,
}
}
>
<View
style={
Object {
"flex": 1,
}
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"flex": 1,
"fontFamily": "System",
"fontSize": 14,
"fontWeight": "500",
"paddingBottom": 0,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#0d0e12",
},
]
}
>
!
!FAQ4
</Text>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"flex": 1,
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
Private
</Text>
</View>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"backgroundColor": "#f3f4f5",
"borderRadius": 2,
"height": 28,
"justifyContent": "center",
"marginBottom": 12,
"marginLeft": 8,
"opacity": 1,
"paddingHorizontal": 14,
"width": 56,
}
}
>
<Text
accessibilityLabel="Use"
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "500",
"textAlign": "center",
},
Object {
"color": "#0d0e12",
},
Object {
"fontSize": 12,
},
undefined,
]
}
>
Use
</Text>
</View>
</View>
<Text
ellipsizeMode="tail"
numberOfLines={2}
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 14,
"fontWeight": "400",
"marginTop": 8,
"paddingBottom": 0,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
ZCVXZVXCZVZXVZXCVZXCVXZCVZX
</Text>
<View
style={
Object {
"flexDirection": "row",
"overflow": "hidden",
}
}
/>
</View>,
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"backgroundColor": "#ffffff",
"maxHeight": 141,
"minHeight": 117,
"opacity": 1,
"padding": 16,
}
}
>
<View
style={
Object {
"flexDirection": "row",
"height": 36,
}
}
>
<View
style={
Object {
"flex": 1,
}
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"flex": 1,
"fontFamily": "System",
"fontSize": 14,
"fontWeight": "500",
"paddingBottom": 0,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#0d0e12",
},
]
}
>
!
test4mobilePrivate
</Text>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"flex": 1,
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
Private
</Text>
</View>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"backgroundColor": "#f3f4f5",
"borderRadius": 2,
"height": 28,
"justifyContent": "center",
"marginBottom": 12,
"marginLeft": 8,
"opacity": 1,
"paddingHorizontal": 14,
"width": 56,
}
}
>
<Text
accessibilityLabel="Use"
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "500",
"textAlign": "center",
},
Object {
"color": "#0d0e12",
},
Object {
"fontSize": 12,
},
undefined,
]
}
>
Use
</Text>
</View>
</View>
<Text
ellipsizeMode="tail"
numberOfLines={2}
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 14,
"fontWeight": "400",
"marginTop": 8,
"paddingBottom": 0,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
test for mobile private
</Text>
<View
style={
Object {
"flexDirection": "row",
"overflow": "hidden",
}
}
>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 16,
"marginRight": 4,
"marginTop": 8,
},
Object {
"backgroundColor": "#E6E6E7",
},
]
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingHorizontal": 4,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
HQ
</Text>
</View>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 16,
"marginRight": 4,
"marginTop": 8,
},
Object {
"backgroundColor": "#E6E6E7",
},
]
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingHorizontal": 4,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
Closed
</Text>
</View>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 16,
"marginRight": 4,
"marginTop": 8,
},
Object {
"backgroundColor": "#E6E6E7",
},
]
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingHorizontal": 4,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
HQ
</Text>
</View>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 16,
"marginRight": 4,
"marginTop": 8,
},
Object {
"backgroundColor": "#E6E6E7",
},
]
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingHorizontal": 4,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
Problem in Product Y
</Text>
</View>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 16,
"marginRight": 4,
"marginTop": 8,
},
Object {
"backgroundColor": "#E6E6E7",
},
]
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingHorizontal": 4,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
HQ
</Text>
</View>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 16,
"marginRight": 4,
"marginTop": 8,
},
Object {
"backgroundColor": "#E6E6E7",
},
]
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingHorizontal": 4,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
Closed
</Text>
</View>
<View
style={
Array [
Object {
"borderRadius": 4,
"height": 16,
"marginRight": 4,
"marginTop": 8,
},
Object {
"backgroundColor": "#E6E6E7",
},
]
}
>
<Text
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 12,
"fontWeight": "400",
"paddingBottom": 0,
"paddingHorizontal": 4,
"paddingTop": 0,
"textAlign": "left",
},
Object {
"color": "#6C727A",
},
]
}
>
Problem in Product Y
</Text>
</View>
</View>
</View>,
]
`;
exports[`Storyshots Header Buttons badge 1`] = ` exports[`Storyshots Header Buttons badge 1`] = `
<RNCSafeAreaView <RNCSafeAreaView
edges={ edges={
@ -40566,6 +41155,7 @@ exports[`Storyshots Message Show a button as attachment 1`] = `
"color": "#ffffff", "color": "#ffffff",
}, },
undefined, undefined,
undefined,
] ]
} }
> >

View File

@ -260,7 +260,6 @@ android {
dependencies { dependencies {
addUnimodulesDependencies() addUnimodulesDependencies()
implementation project(':watermelondb')
implementation project(':@react-native-community_viewpager') implementation project(':@react-native-community_viewpager')
playImplementation project(':reactnativenotifications') playImplementation project(':reactnativenotifications')
playImplementation project(':@react-native-firebase_app') playImplementation project(':@react-native-firebase_app')

View File

@ -9,8 +9,9 @@ import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader; import com.facebook.soloader.SoLoader;
import com.nozbe.watermelondb.WatermelonDBPackage;
import com.reactnativecommunity.viewpager.RNCViewPagerPackage; import com.reactnativecommunity.viewpager.RNCViewPagerPackage;
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
import org.unimodules.adapters.react.ModuleRegistryAdapter; import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider; import org.unimodules.adapters.react.ReactModuleRegistryProvider;
@ -35,7 +36,6 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() { protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable") @SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages(); List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new WatermelonDBPackage());
packages.add(new RNCViewPagerPackage()); packages.add(new RNCViewPagerPackage());
packages.add(new SSLPinningPackage()); packages.add(new SSLPinningPackage());
List<ReactPackage> unimodules = Arrays.<ReactPackage>asList( List<ReactPackage> unimodules = Arrays.<ReactPackage>asList(
@ -52,6 +52,11 @@ public class MainApplication extends Application implements ReactApplication {
return "index"; return "index";
} }
@Override
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage(); // <- add
}
@Override @Override
protected @Nullable String getBundleAssetName() { protected @Nullable String getBundleAssetName() {
return "app.bundle"; return "app.bundle";

View File

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

View File

@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import Navigation from './lib/Navigation'; import Navigation from './lib/Navigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
import { ROOT_INSIDE, ROOT_LOADING, ROOT_NEW_SERVER, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app'; import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app';
// Stacks // Stacks
import AuthLoadingView from './views/AuthLoadingView'; import AuthLoadingView from './views/AuthLoadingView';
// SetUsername Stack // SetUsername Stack
@ -56,9 +56,7 @@ const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}> <Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
<> <>
{root === ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null} {root === ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null}
{root === ROOT_OUTSIDE || root === ROOT_NEW_SERVER ? ( {root === ROOT_OUTSIDE ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null}
<Stack.Screen name='OutsideStack' component={OutsideStack} />
) : null}
{root === ROOT_INSIDE && isMasterDetail ? ( {root === ROOT_INSIDE && isMasterDetail ? (
<Stack.Screen name='MasterDetailStack' component={MasterDetailStack} /> <Stack.Screen name='MasterDetailStack' component={MasterDetailStack} />
) : null} ) : null}

View File

@ -3,7 +3,6 @@ import { APP } from './actionsTypes';
export const ROOT_OUTSIDE = 'outside'; export const ROOT_OUTSIDE = 'outside';
export const ROOT_INSIDE = 'inside'; export const ROOT_INSIDE = 'inside';
export const ROOT_LOADING = 'loading'; export const ROOT_LOADING = 'loading';
export const ROOT_NEW_SERVER = 'newServer';
export const ROOT_SET_USERNAME = 'setUsername'; export const ROOT_SET_USERNAME = 'setUsername';
export function appStart({ root, ...args }) { export function appStart({ root, ...args }) {

View File

@ -202,5 +202,8 @@ export default {
}, },
Jitsi_Enable_Channels: { Jitsi_Enable_Channels: {
type: 'valuesAsBoolean' type: 'valuesAsBoolean'
},
Canned_Responses_Enable: {
type: 'valueAsBoolean'
} }
}; };

View File

@ -3,7 +3,7 @@ import { Keyboard, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { State, TapGestureHandler } from 'react-native-gesture-handler'; import { State, TapGestureHandler } from 'react-native-gesture-handler';
import ScrollBottomSheet from 'react-native-scroll-bottom-sheet'; import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
import Animated, { Easing, Extrapolate, Value, interpolate } from 'react-native-reanimated'; import Animated, { Easing, Extrapolate, Value, interpolateNode } from 'react-native-reanimated';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
import { useBackHandler } from '@react-native-community/hooks'; import { useBackHandler } from '@react-native-community/hooks';
@ -132,11 +132,12 @@ const ActionSheet = React.memo(
const renderItem = ({ item }: any) => <Item item={item} hide={hide} theme={theme} />; const renderItem = ({ item }: any) => <Item item={item} hide={hide} theme={theme} />;
const animatedPosition = React.useRef(new Value(0)); const animatedPosition = React.useRef(new Value(0));
const opacity = interpolate(animatedPosition.current, { // TODO: Similar to https://github.com/wcandillon/react-native-redash/issues/307#issuecomment-827442320
const opacity = interpolateNode(animatedPosition.current, {
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity], outputRange: [0, themes[theme].backdropOpacity],
extrapolate: Extrapolate.CLAMP extrapolate: Extrapolate.CLAMP
}); }) as any;
return ( return (
<> <>

View File

@ -17,6 +17,7 @@ interface IButtonProps {
color: string; color: string;
fontSize: any; fontSize: any;
style: any; style: any;
styleText?: any;
testID: string; testID: string;
} }
@ -48,7 +49,8 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
}; };
render() { render() {
const { title, type, onPress, disabled, backgroundColor, color, loading, style, theme, fontSize, ...otherProps } = this.props; const { title, type, onPress, disabled, backgroundColor, color, loading, style, theme, fontSize, styleText, ...otherProps } =
this.props;
const isPrimary = type === 'primary'; const isPrimary = type === 'primary';
let textColor = isPrimary ? themes[theme!].buttonText : themes[theme!].bodyText; let textColor = isPrimary ? themes[theme!].buttonText : themes[theme!].bodyText;
@ -72,7 +74,7 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
{loading ? ( {loading ? (
<ActivityIndicator color={textColor} /> <ActivityIndicator color={textColor} />
) : ( ) : (
<Text style={[styles.text, { color: textColor }, fontSize && { fontSize }]} accessibilityLabel={title}> <Text style={[styles.text, { color: textColor }, fontSize && { fontSize }, styleText]} accessibilityLabel={title}>
{title} {title}
</Text> </Text>
)} )}

View File

@ -0,0 +1,49 @@
import React, { useContext } from 'react';
import { View, Text, ActivityIndicator, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
import { MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
import styles from '../styles';
import sharedStyles from '../../../views/Styles';
import I18n from '../../../i18n';
import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons';
import MessageboxContext from '../Context';
const MentionHeaderList = ({ trackingType, hasMentions, theme, loading }) => {
const context = useContext(MessageboxContext);
const { onPressNoMatchCanned } = context;
if (trackingType === MENTIONS_TRACKING_TYPE_CANNED) {
if (loading) {
return (
<View style={styles.wrapMentionHeaderListRow}>
<ActivityIndicator style={styles.loadingPaddingHeader} size='small' />
<Text style={[styles.mentionHeaderList, { color: themes[theme].auxiliaryText }]}>{I18n.t('Searching')}</Text>
</View>
);
}
if (!hasMentions) {
return (
<TouchableOpacity style={[styles.wrapMentionHeaderListRow, styles.mentionNoMatchHeader]} onPress={onPressNoMatchCanned}>
<Text style={[styles.mentionHeaderListNoMatchFound, { color: themes[theme].auxiliaryText }]}>
{I18n.t('No_match_found')} <Text style={sharedStyles.textSemibold}>{I18n.t('Check_canned_responses')}</Text>
</Text>
<CustomIcon name='chevron-right' size={24} color={themes[theme].auxiliaryText} />
</TouchableOpacity>
);
}
}
return null;
};
MentionHeaderList.propTypes = {
trackingType: PropTypes.string,
hasMentions: PropTypes.bool,
theme: PropTypes.string,
loading: PropTypes.bool
};
export default MentionHeaderList;

View File

@ -6,7 +6,7 @@ import Avatar from '../../Avatar';
import MessageboxContext from '../Context'; import MessageboxContext from '../Context';
import FixedMentionItem from './FixedMentionItem'; import FixedMentionItem from './FixedMentionItem';
import MentionEmoji from './MentionEmoji'; import MentionEmoji from './MentionEmoji';
import { MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_EMOJIS } from '../constants'; import { MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { IEmoji } from '../../EmojiPicker/interfaces'; import { IEmoji } from '../../EmojiPicker/interfaces';
@ -17,6 +17,8 @@ interface IMessageBoxMentionItem {
username: string; username: string;
t: string; t: string;
id: string; id: string;
shortcut: string;
text: string;
} & IEmoji; } & IEmoji;
trackingType: string; trackingType: string;
theme: string; theme: string;
@ -32,6 +34,8 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
return `mention-item-${item.name || item}`; return `mention-item-${item.name || item}`;
case MENTIONS_TRACKING_TYPE_COMMANDS: case MENTIONS_TRACKING_TYPE_COMMANDS:
return `mention-item-${item.command || item}`; return `mention-item-${item.command || item}`;
case MENTIONS_TRACKING_TYPE_CANNED:
return `mention-item-${item.shortcut || item}`;
default: default:
return `mention-item-${item.username || item.name || item}`; return `mention-item-${item.username || item.name || item}`;
} }
@ -68,6 +72,17 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
); );
} }
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 ( return (
<TouchableOpacity <TouchableOpacity
style={[ style={[

View File

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

View File

@ -17,11 +17,11 @@ interface IMessageBoxRecordAudioProps {
onFinish: Function; onFinish: Function;
} }
const RECORDING_EXTENSION = '.aac'; const RECORDING_EXTENSION = '.m4a';
const RECORDING_SETTINGS = { const RECORDING_SETTINGS = {
android: { android: {
extension: RECORDING_EXTENSION, extension: RECORDING_EXTENSION,
outputFormat: Audio.RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_AAC_ADTS, outputFormat: Audio.RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_MPEG_4,
audioEncoder: Audio.RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC, audioEncoder: Audio.RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC,
sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.sampleRate, sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.sampleRate,
numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.numberOfChannels, numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.numberOfChannels,
@ -39,7 +39,7 @@ const RECORDING_SETTINGS = {
const RECORDING_MODE = { const RECORDING_MODE = {
allowsRecordingIOS: true, allowsRecordingIOS: true,
playsInSilentModeIOS: true, playsInSilentModeIOS: true,
staysActiveInBackground: false, staysActiveInBackground: true,
shouldDuckAndroid: true, shouldDuckAndroid: true,
playThroughEarpieceAndroid: false, playThroughEarpieceAndroid: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
@ -63,20 +63,24 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
private recording: any; private recording: any;
private LastDuration: number;
constructor(props: IMessageBoxRecordAudioProps) { constructor(props: IMessageBoxRecordAudioProps) {
super(props); super(props);
this.isRecorderBusy = false; this.isRecorderBusy = false;
this.LastDuration = 0;
this.state = { this.state = {
isRecording: false, isRecording: false,
isRecorderActive: false,
recordingDurationMillis: 0 recordingDurationMillis: 0
}; };
} }
componentDidUpdate() { componentDidUpdate() {
const { recordingCallback } = this.props; const { recordingCallback } = this.props;
const { isRecording } = this.state; const { isRecorderActive } = this.state;
recordingCallback(isRecording); recordingCallback(isRecorderActive);
} }
componentWillUnmount() { componentWillUnmount() {
@ -90,6 +94,10 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
return formatTime(Math.floor(recordingDurationMillis / 1000)); return formatTime(Math.floor(recordingDurationMillis / 1000));
} }
get GetLastDuration() {
return formatTime(Math.floor(this.LastDuration / 1000));
}
isRecordingPermissionGranted = async () => { isRecordingPermissionGranted = async () => {
try { try {
const permission = await Audio.getPermissionsAsync(); const permission = await Audio.getPermissionsAsync();
@ -108,17 +116,20 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
isRecording: status.isRecording, isRecording: status.isRecording,
recordingDurationMillis: status.durationMillis recordingDurationMillis: status.durationMillis
}); });
this.LastDuration = status.durationMillis;
}; };
startRecordingAudio = async () => { startRecordingAudio = async () => {
logEvent(events.ROOM_AUDIO_RECORD); logEvent(events.ROOM_AUDIO_RECORD);
if (!this.isRecorderBusy) { if (!this.isRecorderBusy) {
this.isRecorderBusy = true; this.isRecorderBusy = true;
this.LastDuration = 0;
try { try {
const canRecord = await this.isRecordingPermissionGranted(); const canRecord = await this.isRecordingPermissionGranted();
if (canRecord) { if (canRecord) {
await Audio.setAudioModeAsync(RECORDING_MODE); await Audio.setAudioModeAsync(RECORDING_MODE);
this.setState({ isRecorderActive: true });
this.recording = new Audio.Recording(); this.recording = new Audio.Recording();
await this.recording.prepareToRecordAsync(RECORDING_SETTINGS); await this.recording.prepareToRecordAsync(RECORDING_SETTINGS);
this.recording.setOnRecordingStatusUpdate(this.onRecordingStatusUpdate); this.recording.setOnRecordingStatusUpdate(this.onRecordingStatusUpdate);
@ -147,7 +158,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
const fileURI = this.recording.getURI(); const fileURI = this.recording.getURI();
const fileData = await getInfoAsync(fileURI); const fileData = await getInfoAsync(fileURI);
const fileInfo = { const fileInfo = {
name: `${Date.now()}.aac`, name: `${Date.now()}.m4a`,
mime: 'audio/aac', mime: 'audio/aac',
type: 'audio/aac', type: 'audio/aac',
store: 'Uploads', store: 'Uploads',
@ -159,7 +170,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
} catch (error) { } catch (error) {
logEvent(events.ROOM_AUDIO_FINISH_F); logEvent(events.ROOM_AUDIO_FINISH_F);
} }
this.setState({ isRecording: false, recordingDurationMillis: 0 }); this.setState({ isRecording: false, isRecorderActive: false, recordingDurationMillis: 0 });
deactivateKeepAwake(); deactivateKeepAwake();
this.isRecorderBusy = false; this.isRecorderBusy = false;
} }
@ -174,7 +185,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
} catch (error) { } catch (error) {
logEvent(events.ROOM_AUDIO_CANCEL_F); logEvent(events.ROOM_AUDIO_CANCEL_F);
} }
this.setState({ isRecording: false, recordingDurationMillis: 0 }); this.setState({ isRecording: false, isRecorderActive: false, recordingDurationMillis: 0 });
deactivateKeepAwake(); deactivateKeepAwake();
this.isRecorderBusy = false; this.isRecorderBusy = false;
} }
@ -182,9 +193,9 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
render() { render() {
const { theme } = this.props; const { theme } = this.props;
const { isRecording } = this.state; const { isRecording, isRecorderActive } = this.state;
if (!isRecording) { if (!isRecording && !isRecorderActive) {
return ( return (
<BorderlessButton <BorderlessButton
onPress={this.startRecordingAudio} onPress={this.startRecordingAudio}
@ -198,6 +209,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
); );
} }
if (!isRecording && isRecorderActive) {
return ( return (
<View style={styles.recordingContent}> <View style={styles.recordingContent}>
<View style={styles.textArea}> <View style={styles.textArea}>
@ -207,9 +219,9 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
accessibilityLabel={I18n.t('Cancel_recording')} accessibilityLabel={I18n.t('Cancel_recording')}
accessibilityTraits='button' accessibilityTraits='button'
style={styles.actionButton}> style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].dangerColor} name='close' /> <CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
</BorderlessButton> </BorderlessButton>
<Text style={[styles.recordingCancelText, { color: themes[theme].titleText }]}>{this.duration}</Text> <Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.GetLastDuration}</Text>
</View> </View>
<BorderlessButton <BorderlessButton
onPress={this.finishRecordingAudio} onPress={this.finishRecordingAudio}
@ -217,7 +229,33 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
accessibilityLabel={I18n.t('Finish_recording')} accessibilityLabel={I18n.t('Finish_recording')}
accessibilityTraits='button' accessibilityTraits='button'
style={styles.actionButton}> style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].successColor} name='check' /> <CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
</BorderlessButton>
</View>
);
}
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>
<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> </BorderlessButton>
</View> </View>
); );

View File

@ -2,4 +2,5 @@ export const MENTIONS_TRACKING_TYPE_USERS = '@';
export const MENTIONS_TRACKING_TYPE_EMOJIS = ':'; export const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
export const MENTIONS_TRACKING_TYPE_COMMANDS = '/'; export const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
export const MENTIONS_TRACKING_TYPE_ROOMS = '#'; export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
export const MENTIONS_TRACKING_TYPE_CANNED = '!';
export const MENTIONS_COUNT_TO_DISPLAY = 4; export const MENTIONS_COUNT_TO_DISPLAY = 4;

View File

@ -35,6 +35,7 @@ import Mentions from './Mentions';
import MessageboxContext from './Context'; import MessageboxContext from './Context';
import { import {
MENTIONS_COUNT_TO_DISPLAY, MENTIONS_COUNT_TO_DISPLAY,
MENTIONS_TRACKING_TYPE_CANNED,
MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_COMMANDS,
MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_EMOJIS,
MENTIONS_TRACKING_TYPE_ROOMS, MENTIONS_TRACKING_TYPE_ROOMS,
@ -107,6 +108,7 @@ interface IMessageBoxProps {
iOSScrollBehavior: number; iOSScrollBehavior: number;
sharing: boolean; sharing: boolean;
isActionsEnabled: boolean; isActionsEnabled: boolean;
usedCannedResponse: string;
} }
interface IMessageBoxState { interface IMessageBoxState {
@ -121,6 +123,7 @@ interface IMessageBoxState {
appId?: any; appId?: any;
}; };
tshow: boolean; tshow: boolean;
mentionLoading: boolean;
} }
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> { class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
@ -175,7 +178,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
commandPreview: [], commandPreview: [],
showCommandPreview: false, showCommandPreview: false,
command: {}, command: {},
tshow: false tshow: false,
mentionLoading: false
}; };
this.text = ''; this.text = '';
this.selection = { start: 0, end: 0 }; this.selection = { start: 0, end: 0 };
@ -234,7 +238,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
async componentDidMount() { async componentDidMount() {
const db = database.active; const db = database.active;
const { rid, tmid, navigation, sharing } = this.props; const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props;
let msg; let msg;
try { try {
const threadsCollection = db.get('threads'); const threadsCollection = db.get('threads');
@ -269,6 +273,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands); EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
} }
if (isMasterDetail && usedCannedResponse) {
this.onChangeText(usedCannedResponse);
}
this.unsubscribeFocus = navigation.addListener('focus', () => { this.unsubscribeFocus = navigation.addListener('focus', () => {
// didFocus // didFocus
// We should wait pushed views be dismissed // We should wait pushed views be dismissed
@ -285,10 +293,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
} }
UNSAFE_componentWillReceiveProps(nextProps: any) { UNSAFE_componentWillReceiveProps(nextProps: any) {
const { isFocused, editing, replying, sharing } = this.props; const { isFocused, editing, replying, sharing, usedCannedResponse } = this.props;
if (!isFocused?.()) { if (!isFocused?.()) {
return; return;
} }
if (usedCannedResponse !== nextProps.usedCannedResponse) {
this.onChangeText(nextProps.usedCannedResponse ?? '');
}
if (sharing) { if (sharing) {
this.setInput(nextProps.message.msg ?? ''); this.setInput(nextProps.message.msg ?? '');
return; return;
@ -311,10 +322,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
} }
shouldComponentUpdate(nextProps: any, nextState: any) { shouldComponentUpdate(nextProps: any, nextState: any) {
const { showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow } = this.state; const { showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow, mentionLoading, trackingType } = this.state;
const { roomType, replying, editing, isFocused, message, theme } = this.props;
const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
return true; return true;
} }
@ -333,6 +343,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) { if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
return true; return true;
} }
if (nextState.trackingType !== trackingType) {
return true;
}
if (nextState.mentionLoading !== mentionLoading) {
return true;
}
if (nextState.showSend !== showSend) { if (nextState.showSend !== showSend) {
return true; return true;
} }
@ -351,6 +367,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (!dequal(nextProps.message?.id, message?.id)) { if (!dequal(nextProps.message?.id, message?.id)) {
return true; return true;
} }
if (nextProps.usedCannedResponse !== usedCannedResponse) {
return true;
}
return false; return false;
} }
@ -371,6 +390,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (this.getSlashCommands && this.getSlashCommands.stop) { if (this.getSlashCommands && this.getSlashCommands.stop) {
this.getSlashCommands.stop(); this.getSlashCommands.stop();
} }
if (this.getCannedResponses && this.getCannedResponses.stop) {
this.getCannedResponses.stop();
}
if (this.unsubscribeFocus) { if (this.unsubscribeFocus) {
this.unsubscribeFocus(); this.unsubscribeFocus();
} }
@ -395,7 +417,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
debouncedOnChangeText = debounce(async (text: any) => { debouncedOnChangeText = debounce(async (text: any) => {
const { sharing } = this.props; const { sharing, roomType } = this.props;
const isTextEmpty = text.length === 0; const isTextEmpty = text.length === 0;
if (isTextEmpty) { if (isTextEmpty) {
this.stopTrackingMention(); this.stopTrackingMention();
@ -412,6 +434,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const channelMention = lastWord.match(/^#/); const channelMention = lastWord.match(/^#/);
const userMention = lastWord.match(/^@/); const userMention = lastWord.match(/^@/);
const emojiMention = lastWord.match(/^:/); const emojiMention = lastWord.match(/^:/);
const cannedMention = lastWord.match(/^!/);
if (commandMention && !sharing) { if (commandMention && !sharing) {
const command = text.substr(1); const command = text.substr(1);
@ -440,6 +463,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (emojiMention) { if (emojiMention) {
return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_EMOJIS); return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_EMOJIS);
} }
if (cannedMention && roomType === 'l') {
return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_CANNED);
}
return this.stopTrackingMention(); return this.stopTrackingMention();
}, 100); }, 100);
@ -456,11 +482,17 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const { start, end } = this.selection; const { start, end } = this.selection;
const cursor = Math.max(start, end); const cursor = Math.max(start, end);
const regexp = /([a-z0-9._-]+)$/im; const regexp = /([a-z0-9._-]+)$/im;
const result = msg.substr(0, cursor).replace(regexp, ''); let result = msg.substr(0, cursor).replace(regexp, '');
// Remove the ! after select the canned response
if (trackingType === MENTIONS_TRACKING_TYPE_CANNED) {
const lastIndexOfExclamation = msg.lastIndexOf('!', cursor);
result = msg.substr(0, lastIndexOfExclamation).replace(regexp, '');
}
const mentionName = const mentionName =
trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ? `${item.name || item}:` : item.username || item.name || item.command; trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
? `${item.name || item}:`
: item.username || item.name || item.command || item.text;
const text = `${result}${mentionName} ${msg.slice(cursor)}`; const text = `${result}${mentionName} ${msg.slice(cursor)}`;
if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS && item.providesPreview) { if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS && item.providesPreview) {
this.setState({ showCommandPreview: true }); this.setState({ showCommandPreview: true });
} }
@ -532,12 +564,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
getUsers = debounce(async (keyword: any) => { getUsers = debounce(async (keyword: any) => {
let res = await RocketChat.search({ text: keyword, filterRooms: false, filterUsers: true }); let res = await RocketChat.search({ text: keyword, filterRooms: false, filterUsers: true });
res = [...this.getFixedMentions(keyword), ...res]; res = [...this.getFixedMentions(keyword), ...res];
this.setState({ mentions: res }); this.setState({ mentions: res, mentionLoading: false });
}, 300); }, 300);
getRooms = debounce(async (keyword = '') => { getRooms = debounce(async (keyword = '') => {
const res = await RocketChat.search({ text: keyword, filterRooms: true, filterUsers: false }); const res = await RocketChat.search({ text: keyword, filterRooms: true, filterUsers: false });
this.setState({ mentions: res }); this.setState({ mentions: res, mentionLoading: false });
}, 300); }, 300);
getEmojis = debounce(async (keyword: any) => { getEmojis = debounce(async (keyword: any) => {
@ -552,7 +584,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY); customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY);
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY); const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY); const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
this.setState({ mentions: mergedEmojis || [] }); this.setState({ mentions: mergedEmojis || [], mentionLoading: false });
}, 300); }, 300);
getSlashCommands = debounce(async (keyword: any) => { getSlashCommands = debounce(async (keyword: any) => {
@ -560,9 +592,14 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const commandsCollection = db.get('slash_commands'); const commandsCollection = db.get('slash_commands');
const likeString = sanitizeLikeString(keyword); const likeString = sanitizeLikeString(keyword);
const commands = await commandsCollection.query(Q.where('id', Q.like(`${likeString}%`))).fetch(); const commands = await commandsCollection.query(Q.where('id', Q.like(`${likeString}%`))).fetch();
this.setState({ mentions: commands || [] }); this.setState({ mentions: commands || [], mentionLoading: false });
}, 300); }, 300);
getCannedResponses = debounce(async (text?: string) => {
const res = await RocketChat.getListCannedResponse({ text });
this.setState({ mentions: res?.cannedResponses || [], mentionLoading: false });
}, 500);
focus = () => { focus = () => {
if (this.component && this.component.focus) { if (this.component && this.component.focus) {
this.component.focus(); this.component.focus();
@ -695,6 +732,16 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
} }
}; };
onPressNoMatchCanned = () => {
const { isMasterDetail, rid } = this.props;
const params = { rid };
if (isMasterDetail) {
Navigation.navigate('ModalStackNavigator', { screen: 'CannedResponsesListView', params });
} else {
Navigation.navigate('CannedResponsesListView', params);
}
};
openShareView = (attachments: any) => { openShareView = (attachments: any) => {
const { message, replyCancel, replyWithMention } = this.props; const { message, replyCancel, replyWithMention } = this.props;
// Start a thread with an attachment // Start a thread with an attachment
@ -854,6 +901,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
this.getEmojis(keyword); this.getEmojis(keyword);
} else if (type === MENTIONS_TRACKING_TYPE_COMMANDS) { } else if (type === MENTIONS_TRACKING_TYPE_COMMANDS) {
this.getSlashCommands(keyword); this.getSlashCommands(keyword);
} else if (type === MENTIONS_TRACKING_TYPE_CANNED) {
this.getCannedResponses(keyword);
} else { } else {
this.getRooms(keyword); this.getRooms(keyword);
} }
@ -862,7 +911,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
identifyMentionKeyword = (keyword: any, type: string) => { identifyMentionKeyword = (keyword: any, type: string) => {
this.setState({ this.setState({
showEmojiKeyboard: false, showEmojiKeyboard: false,
trackingType: type trackingType: type,
mentionLoading: true
}); });
this.updateMentions(keyword, type); this.updateMentions(keyword, type);
}; };
@ -918,7 +968,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}; };
renderContent = () => { renderContent = () => {
const { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview } = this.state; const { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview, mentionLoading } =
this.state;
const { const {
editing, editing,
message, message,
@ -950,8 +1001,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
const commandsPreviewAndMentions = !recording ? ( const commandsPreviewAndMentions = !recording ? (
<> <>
<CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} /> <CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} />
{/* @ts-ignore*/} <Mentions mentions={mentions} trackingType={trackingType} theme={theme} loading={mentionLoading} />
<Mentions mentions={mentions} trackingType={trackingType} theme={theme} />
</> </>
) : null; ) : null;
@ -1039,7 +1089,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
user, user,
baseUrl, baseUrl,
onPressMention: this.onPressMention, onPressMention: this.onPressMention,
onPressCommandPreview: this.onPressCommandPreview onPressCommandPreview: this.onPressCommandPreview,
onPressNoMatchCanned: this.onPressNoMatchCanned
}}> }}>
<KeyboardAccessoryView <KeyboardAccessoryView
ref={(ref: any) => (this.tracking = ref)} ref={(ref: any) => (this.tracking = ref)}

View File

@ -36,6 +36,30 @@ export default StyleSheet.create({
width: 60, width: 60,
height: 48 height: 48
}, },
wrapMentionHeaderList: {
height: MENTION_HEIGHT,
justifyContent: 'center'
},
wrapMentionHeaderListRow: {
height: MENTION_HEIGHT,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12
},
loadingPaddingHeader: {
paddingRight: 12
},
mentionHeaderList: {
fontSize: 14,
...sharedStyles.textMedium
},
mentionHeaderListNoMatchFound: {
fontSize: 14,
...sharedStyles.textRegular
},
mentionNoMatchHeader: {
justifyContent: 'space-between'
},
mentionList: { mentionList: {
maxHeight: MENTION_HEIGHT * 4 maxHeight: MENTION_HEIGHT * 4
}, },
@ -67,6 +91,18 @@ export default StyleSheet.create({
fontSize: 14, fontSize: 14,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
cannedMentionText: {
flex: 1,
fontSize: 14,
paddingRight: 12,
...sharedStyles.textRegular
},
cannedItem: {
fontSize: 14,
...sharedStyles.textBold,
paddingLeft: 12,
paddingRight: 8
},
emojiKeyboardContainer: { emojiKeyboardContainer: {
flex: 1, flex: 1,
borderTopWidth: StyleSheet.hairlineWidth borderTopWidth: StyleSheet.hairlineWidth
@ -103,7 +139,8 @@ export default StyleSheet.create({
flex: 1, flex: 1,
justifyContent: 'space-between' justifyContent: 'space-between'
}, },
recordingCancelText: { recordingDurationText: {
width: 60,
fontSize: 16, fontSize: 16,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },

View File

@ -0,0 +1,49 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import { withTheme } from '../theme';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import TextInput from '../presentation/TextInput';
import { isIOS, isTablet } from '../utils/deviceInfo';
import { useOrientation } from '../dimensions';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
marginLeft: 0
},
title: {
...sharedStyles.textSemibold
}
});
// TODO: it might be useful to refactor this component for reusage
const SearchHeader = ({ theme, onSearchChangeText }) => {
const titleColorStyle = { color: themes[theme].headerTitleColor };
const isLight = theme === 'light';
const { isLandscape } = useOrientation();
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
const titleFontSize = 16 * scale;
return (
<View style={styles.container}>
<TextInput
autoFocus
style={[styles.title, isLight && titleColorStyle, { fontSize: titleFontSize }]}
placeholder='Search'
onChangeText={onSearchChangeText}
theme={theme}
testID='thread-messages-view-search-header'
/>
</View>
);
};
SearchHeader.propTypes = {
theme: PropTypes.string,
onSearchChangeText: PropTypes.func
};
export default withTheme(SearchHeader);

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import { BorderlessButton } from 'react-native-gesture-handler'; import Touchable from 'react-native-platform-touchable';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import TextInput from '../presentation/TextInput'; import TextInput from '../presentation/TextInput';
@ -95,9 +95,9 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
get iconRight() { get iconRight() {
const { iconRight, onIconRightPress, theme } = this.props; const { iconRight, onIconRightPress, theme } = this.props;
return ( return (
<BorderlessButton onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}> <Touchable onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}>
<CustomIcon name={iconRight} style={{ color: themes[theme].bodyText }} size={20} /> <CustomIcon name={iconRight} style={{ color: themes[theme].bodyText }} size={20} />
</BorderlessButton> </Touchable>
); );
} }
@ -105,14 +105,14 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
const { showPassword } = this.state; const { showPassword } = this.state;
const { testID, theme } = this.props; const { testID, theme } = this.props;
return ( return (
<BorderlessButton onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}> <Touchable onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}>
<CustomIcon <CustomIcon
name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'} name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'}
testID={testID ? `${testID}-icon-right` : null} testID={testID ? `${testID}-icon-right` : null}
style={{ color: themes[theme].auxiliaryText }} style={{ color: themes[theme].auxiliaryText }}
size={20} size={20}
/> />
</BorderlessButton> </Touchable>
); );
} }

View File

@ -12,18 +12,19 @@ interface IInput {
onPress: Function; onPress: Function;
theme: string; theme: string;
inputStyle: object; inputStyle: object;
disabled: boolean; disabled?: boolean | object;
placeholder: string; placeholder?: string;
loading: boolean; loading?: boolean;
innerInputStyle?: object;
} }
const Input = ({ children, onPress, theme, loading, inputStyle, placeholder, disabled }: IInput) => ( const Input = ({ children, onPress, theme, loading, inputStyle, placeholder, disabled, innerInputStyle }: IInput) => (
<Touchable <Touchable
onPress={onPress} onPress={onPress}
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]} style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
background={Touchable.Ripple(themes[theme].bannerBackground)} background={Touchable.Ripple(themes[theme].bannerBackground)}
disabled={disabled}> disabled={disabled}>
<View style={[styles.input, { borderColor: themes[theme].separatorColor }]}> <View style={[styles.input, { borderColor: themes[theme].separatorColor }, innerInputStyle]}>
{placeholder ? <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder}</Text> : children} {placeholder ? <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder}</Text> : children}
{loading ? ( {loading ? (
<ActivityIndicator style={[styles.loading, styles.icon]} /> <ActivityIndicator style={[styles.loading, styles.icon]} />

View File

@ -28,6 +28,7 @@ interface IMultiSelect {
value?: any[]; value?: any[];
disabled?: boolean | object; disabled?: boolean | object;
theme: string; theme: string;
innerInputStyle?: object;
} }
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
@ -53,7 +54,8 @@ export const MultiSelect = React.memo(
onClose = () => {}, onClose = () => {},
disabled, disabled,
inputStyle, inputStyle,
theme theme,
innerInputStyle
}: IMultiSelect) => { }: IMultiSelect) => {
const [selected, select] = useState<any>(Array.isArray(values) ? values : []); const [selected, select] = useState<any>(Array.isArray(values) ? values : []);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -143,8 +145,13 @@ export const MultiSelect = React.memo(
let button = multiselect ? ( let button = multiselect ? (
<Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} theme={theme} /> <Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} theme={theme} />
) : ( ) : (
// @ts-ignore <Input
<Input onPress={onShow} theme={theme} loading={loading} disabled={disabled} inputStyle={inputStyle}> onPress={onShow}
theme={theme}
loading={loading}
disabled={disabled}
inputStyle={inputStyle}
innerInputStyle={innerInputStyle}>
<Text style={[styles.pickerText, { color: currentValue ? themes[theme].titleText : themes[theme].auxiliaryText }]}> <Text style={[styles.pickerText, { color: currentValue ? themes[theme].titleText : themes[theme].auxiliaryText }]}>
{currentValue || placeholder.text} {currentValue || placeholder.text}
</Text> </Text>
@ -154,8 +161,13 @@ export const MultiSelect = React.memo(
if (context === BLOCK_CONTEXT.FORM) { if (context === BLOCK_CONTEXT.FORM) {
const items: any = options.filter((option: any) => selected.includes(option.value)); const items: any = options.filter((option: any) => selected.includes(option.value));
button = ( button = (
// @ts-ignore <Input
<Input onPress={onShow} theme={theme} loading={loading} disabled={disabled} inputStyle={inputStyle}> onPress={onShow}
theme={theme}
loading={loading}
disabled={disabled}
inputStyle={inputStyle}
innerInputStyle={innerInputStyle}>
{items.length ? ( {items.length ? (
<Chips items={items} onSelect={(item: any) => (disabled ? {} : onSelect(item))} theme={theme} /> <Chips items={items} onSelect={(item: any) => (disabled ? {} : onSelect(item))} theme={theme} />
) : ( ) : (

View File

@ -43,7 +43,7 @@ interface IMessageAudioState {
const mode = { const mode = {
allowsRecordingIOS: false, allowsRecordingIOS: false,
playsInSilentModeIOS: true, playsInSilentModeIOS: true,
staysActiveInBackground: false, staysActiveInBackground: true,
shouldDuckAndroid: true, shouldDuckAndroid: true,
playThroughEarpieceAndroid: false, playThroughEarpieceAndroid: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,

View File

@ -773,5 +773,14 @@
"Converting_Team_To_Channel": "Converting Team to Channel", "Converting_Team_To_Channel": "Converting Team to Channel",
"Select_Team_Channels_To_Delete": "Select the Teams Channels you would like to delete, the ones you do not select will be moved to the Workspace. \n\nNotice that public Channels will be public and visible to everyone.", "Select_Team_Channels_To_Delete": "Select the Teams Channels you would like to delete, the ones you do not select will be moved to the Workspace. \n\nNotice that public Channels will be public and visible to everyone.",
"You_are_converting_the_team": "You are converting this Team to a Channel", "You_are_converting_the_team": "You are converting this Team to a Channel",
"creating_discussion": "creating discussion" "creating_discussion": "creating discussion",
"Canned_Responses": "Canned Responses",
"No_match_found": "No match found.",
"Check_canned_responses": "Check on canned responses.",
"Searching": "Searching",
"Use": "Use",
"Shortcut": "Shortcut",
"Content": "Content",
"Sharing": "Sharing",
"No_canned_responses": "No canned responses"
} }

View File

@ -672,5 +672,13 @@
"Left_The_Room_Successfully": "Saiu da sala com sucesso", "Left_The_Room_Successfully": "Saiu da sala com sucesso",
"Deleted_The_Team_Successfully": "Time deletado com sucesso", "Deleted_The_Team_Successfully": "Time deletado com sucesso",
"Deleted_The_Room_Successfully": "Sala deletada com sucesso", "Deleted_The_Room_Successfully": "Sala deletada com sucesso",
"Convert_to_Channel": "Converter para um Canal" "Convert_to_Channel": "Converter para um Canal",
"Canned_Responses": "Respostas Predefinidas",
"No_match_found": "Nenhum resultado encontrado",
"Check_canned_responses": "Verifique nas respostas predefinidas",
"Searching": "Buscando",
"Use": "Use",
"Shortcut": "Atalho",
"Content": "Conteúdo",
"No_canned_responses": "Não há respostas predefinidas"
} }

View File

@ -60,8 +60,7 @@ export const getDatabase = (database = '') => {
Permission, Permission,
SlashCommand, SlashCommand,
User User
], ]
actionsEnabled: true
}); });
}; };
@ -73,8 +72,7 @@ class DB {
schema: serversSchema, schema: serversSchema,
migrations: serversMigrations migrations: serversMigrations
}), }),
modelClasses: [Server, LoggedUser, ServersHistory], modelClasses: [Server, LoggedUser, ServersHistory]
actionsEnabled: true
}) })
}; };
@ -117,8 +115,7 @@ class DB {
FrequentlyUsedEmoji, FrequentlyUsedEmoji,
Setting, Setting,
User User
], ]
actionsEnabled: true
}); });
} }

View File

@ -50,7 +50,8 @@ const PERMISSIONS = [
'view-all-team-channels', 'view-all-team-channels',
'convert-team', 'convert-team',
'edit-omnichannel-contact', 'edit-omnichannel-contact',
'edit-livechat-room-customfields' 'edit-livechat-room-customfields',
'view-canned-responses'
]; ];
export async function setPermissions() { export async function setPermissions() {

View File

@ -1049,7 +1049,7 @@ const RocketChat = {
} }
return this.post('subscriptions.read', { rid: roomId }); return this.post('subscriptions.read', { rid: roomId });
}, },
getRoomMembers({ rid, allUsers, roomType, type, filter, skip = 0, limit = 10 }) { async getRoomMembers({ rid, allUsers, roomType, type, filter, skip = 0, limit = 10 }) {
const serverVersion = reduxStore.getState().server.version; const serverVersion = reduxStore.getState().server.version;
if (compareServerVersion(serverVersion, '3.16.0', methods.greaterThanOrEqualTo)) { if (compareServerVersion(serverVersion, '3.16.0', methods.greaterThanOrEqualTo)) {
const params = { const params = {
@ -1060,10 +1060,12 @@ const RocketChat = {
...(filter && { filter }) ...(filter && { filter })
}; };
// RC 3.16.0 // RC 3.16.0
return this.sdk.get(`${this.roomTypeToApiType(roomType)}.members`, params); const result = await this.sdk.get(`${this.roomTypeToApiType(roomType)}.members`, params);
return result?.members;
} }
// RC 0.42.0 // RC 0.42.0
return this.methodCallWrapper('getUsersOfRoom', rid, allUsers, { skip, limit }); const result = await this.methodCallWrapper('getUsersOfRoom', rid, allUsers, { skip, limit });
return result?.records;
}, },
methodCallWrapper(method, ...params) { methodCallWrapper(method, ...params) {
const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings; const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
@ -1171,6 +1173,19 @@ const RocketChat = {
return this.sdk.get('livechat/custom-fields'); return this.sdk.get('livechat/custom-fields');
}, },
getListCannedResponse({ scope = '', departmentId = '', offset = 0, count = 25, text = '' }) {
const params = {
offset,
count,
...(departmentId && { departmentId }),
...(text && { text }),
...(scope && { scope })
};
// RC 3.17.0
return this.sdk.get('canned-responses', params);
},
getUidDirectMessage(room) { getUidDirectMessage(room) {
const { id: userId } = reduxStore.getState().login.user; const { id: userId } = reduxStore.getState().login.user;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { PanGestureHandler, PinchGestureHandler, State } from 'react-native-gesture-handler'; import { PanGestureHandler, PinchGestureHandler, State } from 'react-native-gesture-handler';
import Animated, { Easing } from 'react-native-reanimated'; import Animated, { EasingNode } from 'react-native-reanimated';
import { ImageComponent } from './ImageComponent'; import { ImageComponent } from './ImageComponent';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -94,7 +94,7 @@ function runTiming(clock: any, value: any, dest: any, startStopClock: any = true
const config = { const config = {
toValue: new Value(0), toValue: new Value(0),
duration: 300, duration: 300,
easing: Easing.inOut(Easing.cubic) easing: EasingNode.inOut(EasingNode.cubic)
}; };
return [ return [
@ -215,7 +215,9 @@ class Image extends React.PureComponent<IImageProps, any> {
return <Component {...this.props} />; return <Component {...this.props} />;
} }
} }
const AnimatedImage = Animated.createAnimatedComponent(Image);
// https://github.com/software-mansion/react-native-reanimated/issues/1717
const AnimatedImage: any = Animated.createAnimatedComponent(Image);
// it was picked from https://github.com/software-mansion/react-native-reanimated/tree/master/Example/imageViewer // it was picked from https://github.com/software-mansion/react-native-reanimated/tree/master/Example/imageViewer
// and changed to use FastImage animated component // and changed to use FastImage animated component

View File

@ -7,7 +7,6 @@ const initialState = {
server: '', server: '',
version: null, version: null,
loading: true, loading: true,
adding: false,
previousServer: null, previousServer: null,
changingServer: false changingServer: false
}; };
@ -58,13 +57,11 @@ export default function server(state = initialState, action) {
case SERVER.INIT_ADD: case SERVER.INIT_ADD:
return { return {
...state, ...state,
adding: true,
previousServer: action.previousServer previousServer: action.previousServer
}; };
case SERVER.FINISH_ADD: case SERVER.FINISH_ADD:
return { return {
...state, ...state,
adding: false,
previousServer: null previousServer: null
}; };
default: default:

View File

@ -8,7 +8,7 @@ import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'
import database from '../lib/database'; import database from '../lib/database';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import { ROOT_INSIDE, ROOT_NEW_SERVER, appInit, appStart } from '../actions/app'; import { ROOT_INSIDE, ROOT_OUTSIDE, appInit, appStart } from '../actions/app';
import { localAuthenticate } from '../utils/localAuthentication'; import { localAuthenticate } from '../utils/localAuthentication';
import { goRoom } from '../utils/goRoom'; import { goRoom } from '../utils/goRoom';
import { loginRequest } from '../actions/login'; import { loginRequest } from '../actions/login';
@ -180,7 +180,7 @@ const handleOpen = function* handleOpen({ params }) {
yield fallbackNavigation(); yield fallbackNavigation();
return; return;
} }
yield put(appStart({ root: ROOT_NEW_SERVER })); yield put(appStart({ root: ROOT_OUTSIDE }));
yield put(serverInitAdd(server)); yield put(serverInitAdd(server));
yield delay(1000); yield delay(1000);
EventEmitter.emit('NewServer', { server: host }); EventEmitter.emit('NewServer', { server: host });

View File

@ -118,8 +118,6 @@ const fetchRooms = function* fetchRooms() {
const handleLoginSuccess = function* handleLoginSuccess({ user }) { const handleLoginSuccess = function* handleLoginSuccess({ user }) {
try { try {
const adding = yield select(state => state.server.adding);
RocketChat.getUserPresence(user.id); RocketChat.getUserPresence(user.id);
const server = yield select(getServer); const server = yield select(getServer);
@ -170,25 +168,11 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield put(setUser(user)); yield put(setUser(user));
EventEmitter.emit('connected'); EventEmitter.emit('connected');
let currentRoot;
if (adding) {
yield put(serverFinishAdd());
yield put(appStart({ root: ROOT_INSIDE })); yield put(appStart({ root: ROOT_INSIDE }));
} else {
currentRoot = yield select(state => state.app.root);
if (currentRoot !== ROOT_INSIDE) {
yield put(appStart({ root: ROOT_INSIDE }));
}
}
// after a successful login, check if it's been invited via invite link
currentRoot = yield select(state => state.app.root);
if (currentRoot === ROOT_INSIDE) {
const inviteLinkToken = yield select(state => state.inviteLinks.token); const inviteLinkToken = yield select(state => state.inviteLinks.token);
if (inviteLinkToken) { if (inviteLinkToken) {
yield put(inviteLinksRequest(inviteLinkToken)); yield put(inviteLinksRequest(inviteLinkToken));
} }
}
} catch (e) { } catch (e) {
log(e); log(e);
} }

View File

@ -30,6 +30,8 @@ import ThreadMessagesView from '../views/ThreadMessagesView';
import TeamChannelsView from '../views/TeamChannelsView'; import TeamChannelsView from '../views/TeamChannelsView';
import MarkdownTableView from '../views/MarkdownTableView'; import MarkdownTableView from '../views/MarkdownTableView';
import ReadReceiptsView from '../views/ReadReceiptView'; import ReadReceiptsView from '../views/ReadReceiptView';
import CannedResponsesListView from '../views/CannedResponsesListView';
import CannedResponseDetail from '../views/CannedResponseDetail';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
// Profile Stack // Profile Stack
import ProfileView from '../views/ProfileView'; import ProfileView from '../views/ProfileView';
@ -138,6 +140,16 @@ const ChatsStackNavigator = () => {
<ChatsStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} /> <ChatsStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} />
<ChatsStack.Screen name='ReadReceiptsView' component={ReadReceiptsView} options={ReadReceiptsView.navigationOptions} /> <ChatsStack.Screen name='ReadReceiptsView' component={ReadReceiptsView} options={ReadReceiptsView.navigationOptions} />
<ChatsStack.Screen name='QueueListView' component={QueueListView} options={QueueListView.navigationOptions} /> <ChatsStack.Screen name='QueueListView' component={QueueListView} options={QueueListView.navigationOptions} />
<ChatsStack.Screen
name='CannedResponsesListView'
component={CannedResponsesListView}
options={CannedResponsesListView.navigationOptions}
/>
<ChatsStack.Screen
name='CannedResponseDetail'
component={CannedResponseDetail}
options={CannedResponseDetail.navigationOptions}
/>
</ChatsStack.Navigator> </ChatsStack.Navigator>
); );
}; };

View File

@ -24,6 +24,8 @@ import DirectoryView from '../../views/DirectoryView';
import NotificationPrefView from '../../views/NotificationPreferencesView'; import NotificationPrefView from '../../views/NotificationPreferencesView';
import VisitorNavigationView from '../../views/VisitorNavigationView'; import VisitorNavigationView from '../../views/VisitorNavigationView';
import ForwardLivechatView from '../../views/ForwardLivechatView'; import ForwardLivechatView from '../../views/ForwardLivechatView';
import CannedResponsesListView from '../../views/CannedResponsesListView';
import CannedResponseDetail from '../../views/CannedResponseDetail';
import LivechatEditView from '../../views/LivechatEditView'; import LivechatEditView from '../../views/LivechatEditView';
import PickerView from '../../views/PickerView'; import PickerView from '../../views/PickerView';
import ThreadMessagesView from '../../views/ThreadMessagesView'; import ThreadMessagesView from '../../views/ThreadMessagesView';
@ -160,6 +162,16 @@ const ModalStackNavigator = React.memo(({ navigation }) => {
component={ForwardLivechatView} component={ForwardLivechatView}
options={ForwardLivechatView.navigationOptions} options={ForwardLivechatView.navigationOptions}
/> />
<ModalStack.Screen
name='CannedResponsesListView'
component={CannedResponsesListView}
options={CannedResponsesListView.navigationOptions}
/>
<ModalStack.Screen
name='CannedResponseDetail'
component={CannedResponseDetail}
options={CannedResponseDetail.navigationOptions}
/>
<ModalStack.Screen name='LivechatEditView' component={LivechatEditView} options={LivechatEditView.navigationOptions} /> <ModalStack.Screen name='LivechatEditView' component={LivechatEditView} options={LivechatEditView.navigationOptions} />
<ModalStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} /> <ModalStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} />
<ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} /> <ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} />

View File

@ -1,13 +1,11 @@
import React from 'react'; import React from 'react';
import { createStackNavigator } from '@react-navigation/stack'; import { createStackNavigator } from '@react-navigation/stack';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { ThemeContext } from '../theme'; import { ThemeContext } from '../theme';
import { ModalAnimation, StackAnimation, defaultHeader, themedHeader } from '../utils/navigation'; import { ModalAnimation, StackAnimation, defaultHeader, themedHeader } from '../utils/navigation';
// Outside Stack // Outside Stack
import OnboardingView from '../views/OnboardingView';
import NewServerView from '../views/NewServerView'; import NewServerView from '../views/NewServerView';
import WorkspaceView from '../views/WorkspaceView'; import WorkspaceView from '../views/WorkspaceView';
import LoginView from '../views/LoginView'; import LoginView from '../views/LoginView';
@ -15,18 +13,14 @@ import ForgotPasswordView from '../views/ForgotPasswordView';
import RegisterView from '../views/RegisterView'; import RegisterView from '../views/RegisterView';
import LegalView from '../views/LegalView'; import LegalView from '../views/LegalView';
import AuthenticationWebView from '../views/AuthenticationWebView'; import AuthenticationWebView from '../views/AuthenticationWebView';
import { ROOT_OUTSIDE } from '../actions/app';
// Outside // Outside
const Outside = createStackNavigator(); const Outside = createStackNavigator();
const _OutsideStack = ({ root }) => { const _OutsideStack = () => {
const { theme } = React.useContext(ThemeContext); const { theme } = React.useContext(ThemeContext);
return ( return (
<Outside.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> <Outside.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}>
{root === ROOT_OUTSIDE ? (
<Outside.Screen name='OnboardingView' component={OnboardingView} options={OnboardingView.navigationOptions} />
) : null}
<Outside.Screen name='NewServerView' component={NewServerView} options={NewServerView.navigationOptions} /> <Outside.Screen name='NewServerView' component={NewServerView} options={NewServerView.navigationOptions} />
<Outside.Screen name='WorkspaceView' component={WorkspaceView} options={WorkspaceView.navigationOptions} /> <Outside.Screen name='WorkspaceView' component={WorkspaceView} options={WorkspaceView.navigationOptions} />
<Outside.Screen name='LoginView' component={LoginView} options={LoginView.navigationOptions} /> <Outside.Screen name='LoginView' component={LoginView} options={LoginView.navigationOptions} />
@ -41,10 +35,6 @@ const mapStateToProps = state => ({
root: state.app.root root: state.app.root
}); });
_OutsideStack.propTypes = {
root: PropTypes.string
};
const OutsideStack = connect(mapStateToProps)(_OutsideStack); const OutsideStack = connect(mapStateToProps)(_OutsideStack);
// OutsideStackModal // OutsideStackModal

View File

@ -1,9 +1,4 @@
export default { export default {
// ONBOARDING VIEW
ONBOARD_JOIN_A_WORKSPACE: 'onboard_join_a_workspace',
ONBOARD_CREATE_NEW_WORKSPACE: 'onboard_create_new_workspace',
ONBOARD_CREATE_NEW_WORKSPACE_F: 'onboard_create_new_workspace_f',
// NEW SERVER VIEW // NEW SERVER VIEW
NS_CONNECT_TO_WORKSPACE: 'ns_connect_to_workspace', NS_CONNECT_TO_WORKSPACE: 'ns_connect_to_workspace',
NS_JOIN_OPEN_WORKSPACE: 'ns_join_open_workspace', NS_JOIN_OPEN_WORKSPACE: 'ns_join_open_workspace',
@ -78,6 +73,7 @@ export default {
RL_GROUP_CHANNELS_BY_TYPE: 'rl_group_channels_by_type', RL_GROUP_CHANNELS_BY_TYPE: 'rl_group_channels_by_type',
RL_GROUP_CHANNELS_BY_FAVORITE: 'rl_group_channels_by_favorite', RL_GROUP_CHANNELS_BY_FAVORITE: 'rl_group_channels_by_favorite',
RL_GROUP_CHANNELS_BY_UNREAD: 'rl_group_channels_by_unread', RL_GROUP_CHANNELS_BY_UNREAD: 'rl_group_channels_by_unread',
RL_CREATE_NEW_WORKSPACE: 'rl_create_new_workspace',
// QUEUE LIST VIEW // QUEUE LIST VIEW
QL_GO_ROOM: 'ql_go_room', QL_GO_ROOM: 'ql_go_room',

View File

@ -1,14 +1,11 @@
import { Dimensions } from 'react-native';
import { isTablet } from './deviceInfo'; import { isTablet } from './deviceInfo';
const { width, height } = Dimensions.get('window');
const guidelineBaseWidth = isTablet ? 600 : 375; const guidelineBaseWidth = isTablet ? 600 : 375;
const guidelineBaseHeight = isTablet ? 800 : 667; const guidelineBaseHeight = isTablet ? 800 : 667;
const scale = size => (width / guidelineBaseWidth) * size; // TODO: we need to refactor this
const verticalScale = size => (height / guidelineBaseHeight) * size; const scale = (size, width) => (width / guidelineBaseWidth) * size;
const moderateScale = (size, factor = 0.5) => size + (scale(size) - size) * factor; const verticalScale = (size, height) => (height / guidelineBaseHeight) * size;
const moderateScale = (size, factor = 0.5, width) => size + (scale(size, width) - size) * factor;
export { scale, verticalScale, moderateScale }; export { scale, verticalScale, moderateScale };

View File

@ -0,0 +1,171 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View, ScrollView } from 'react-native';
import { useSelector } from 'react-redux';
import I18n from '../i18n';
import SafeAreaView from '../containers/SafeAreaView';
import StatusBar from '../containers/StatusBar';
import Button from '../containers/Button';
import { useTheme } from '../theme';
import RocketChat from '../lib/rocketchat';
import Navigation from '../lib/Navigation';
import { goRoom } from '../utils/goRoom';
import { themes } from '../constants/colors';
import Markdown from '../containers/markdown';
import sharedStyles from './Styles';
const styles = StyleSheet.create({
scroll: {
flex: 1
},
container: {
flex: 1,
marginTop: 12,
marginHorizontal: 15
},
cannedText: {
marginTop: 8,
marginBottom: 16,
fontSize: 14,
paddingTop: 0,
paddingBottom: 0,
...sharedStyles.textRegular
},
cannedTagWrap: {
borderRadius: 4,
marginRight: 4,
marginTop: 8,
height: 16
},
cannedTagContainer: {
flexDirection: 'row',
flexWrap: 'wrap'
},
cannedTag: {
fontSize: 12,
paddingTop: 0,
paddingBottom: 0,
paddingHorizontal: 4,
...sharedStyles.textRegular
},
button: {
margin: 24,
marginBottom: 24
},
item: {
paddingVertical: 10,
justifyContent: 'center'
},
itemLabel: {
marginBottom: 10,
fontSize: 14,
...sharedStyles.textMedium
},
itemContent: {
fontSize: 14,
...sharedStyles.textRegular
}
});
const Item = ({ label, content, theme, testID }) =>
content ? (
<View style={styles.item} testID={testID}>
<Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>
{label}
</Text>
<Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} theme={theme} />
</View>
) : null;
Item.propTypes = {
label: PropTypes.string,
content: PropTypes.string,
theme: PropTypes.string,
testID: PropTypes.string
};
const CannedResponseDetail = ({ navigation, route }) => {
const { cannedResponse } = route?.params;
const { theme } = useTheme();
const { isMasterDetail } = useSelector(state => state.app);
const { rooms } = useSelector(state => state.room);
useEffect(() => {
navigation.setOptions({
title: `!${cannedResponse?.shortcut}`
});
}, []);
const navigateToRoom = item => {
const { room } = route.params;
const { name, username } = room;
const params = {
rid: room.rid,
name: RocketChat.getRoomTitle({
t: room.t,
fname: name,
name: username
}),
t: room.t,
roomUserId: RocketChat.getUidDirectMessage(room),
usedCannedResponse: item.text
};
if (room.rid) {
// if it's on master detail layout, we close the modal and replace RoomView
if (isMasterDetail) {
Navigation.navigate('DrawerNavigator');
goRoom({ item: params, isMasterDetail, usedCannedResponse: item.text });
} else {
let navigate = navigation.push;
// if this is a room focused
if (rooms.includes(room.rid)) {
({ navigate } = navigation);
}
navigate('RoomView', params);
}
}
};
return (
<SafeAreaView>
<ScrollView contentContainerStyle={[styles.scroll, { backgroundColor: themes[theme].messageboxBackground }]}>
<StatusBar />
<View style={styles.container}>
<Item label={I18n.t('Shortcut')} content={`!${cannedResponse?.shortcut}`} theme={theme} />
<Item label={I18n.t('Content')} content={cannedResponse?.text} theme={theme} />
<Item label={I18n.t('Sharing')} content={cannedResponse?.scopeName} theme={theme} />
<View style={styles.item}>
<Text style={[styles.itemLabel, { color: themes[theme].titleText }]}>{I18n.t('Tags')}</Text>
<View style={styles.cannedTagContainer}>
{cannedResponse?.tags?.length > 0 ? (
cannedResponse.tags.map(t => (
<View style={[styles.cannedTagWrap, { backgroundColor: themes[theme].searchboxBackground }]}>
<Text style={[styles.cannedTag, { color: themes[theme].auxiliaryTintColor }]}>{t}</Text>
</View>
))
) : (
<Text style={[styles.cannedText, { color: themes[theme].auxiliaryTintColor }]}>-</Text>
)}
</View>
</View>
</View>
<Button
title={I18n.t('Use')}
theme={theme}
style={styles.button}
type='primary'
onPress={() => navigateToRoom(cannedResponse)}
/>
</ScrollView>
</SafeAreaView>
);
};
CannedResponseDetail.propTypes = {
navigation: PropTypes.object,
route: PropTypes.object
};
export default CannedResponseDetail;

View File

@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, Text } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import { themes } from '../../constants/colors';
import Button from '../../containers/Button';
import I18n from '../../i18n';
import styles from './styles';
const CannedResponseItem = ({ theme, onPressDetail, shortcut, scope, onPressUse, text, tags }) => (
<Touchable onPress={onPressDetail} style={[styles.wrapCannedItem, { backgroundColor: themes[theme].messageboxBackground }]}>
<>
<View style={styles.cannedRow}>
<View style={styles.cannedWrapShortcutScope}>
<Text style={[styles.cannedShortcut, { color: themes[theme].titleText }]}>!{shortcut}</Text>
<Text style={[styles.cannedScope, { color: themes[theme].auxiliaryTintColor }]}>{scope}</Text>
</View>
<Button
title={I18n.t('Use')}
fontSize={12}
color={themes[theme].titleText}
style={[styles.cannedUseButton, { backgroundColor: themes[theme].chatComponentBackground }]}
theme={theme}
onPress={onPressUse}
/>
</View>
<Text ellipsizeMode='tail' numberOfLines={2} style={[styles.cannedText, { color: themes[theme].auxiliaryTintColor }]}>
{text}
</Text>
<View style={styles.cannedTagContainer}>
{tags?.length > 0
? tags.map(t => (
<View style={[styles.cannedTagWrap, { backgroundColor: themes[theme].searchboxBackground }]}>
<Text style={[styles.cannedTag, { color: themes[theme].auxiliaryTintColor }]}>{t}</Text>
</View>
))
: null}
</View>
</>
</Touchable>
);
CannedResponseItem.propTypes = {
theme: PropTypes.string,
onPressDetail: PropTypes.func,
shortcut: PropTypes.string,
scope: PropTypes.string,
onPressUse: PropTypes.func,
text: PropTypes.string,
tags: PropTypes.array
};
CannedResponseItem.defaultProps = {
onPressDetail: () => {},
onPressUse: () => {}
};
export default CannedResponseItem;

View File

@ -0,0 +1,64 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react-native';
import CannedResponseItem from './CannedResponseItem';
const stories = storiesOf('CannedResponseItem', module);
const item = [
{
_id: 'x1-x1-x1',
shortcut: '!FAQ4',
text: 'ZCVXZVXCZVZXVZXCVZXCVXZCVZX',
scope: 'user',
userId: 'xxx-x-xx-x-x-',
createdBy: {
_id: 'xxx-x-xx-x-x-',
username: 'rocket.cat'
},
_createdAt: '2021-08-11T01:23:17.379Z',
_updatedAt: '2021-08-11T01:23:17.379Z',
scopeName: 'Private'
},
{
_id: 'x1-1x-1x',
shortcut: 'test4mobilePrivate',
text: 'test for mobile private',
scope: 'user',
tags: ['HQ', 'Closed', 'HQ', 'Problem in Product Y', 'HQ', 'Closed', 'Problem in Product Y'],
userId: 'laslsaklasal',
createdBy: {
_id: 'laslsaklasal',
username: 'reinaldo.neto'
},
_createdAt: '2021-09-02T17:44:52.095Z',
_updatedAt: '2021-09-02T18:24:40.436Z',
scopeName: 'Private'
}
];
const theme = 'light';
stories.add('Itens', () => (
<>
<CannedResponseItem
theme={theme}
scope={item[0].scopeName}
shortcut={item[0].shortcut}
tags={item[0]?.tags}
text={item[0].text}
onPressDetail={() => alert('navigation to CannedResponseDetail')}
onPressUse={() => alert('Back to RoomView and wrote in MessageBox')}
/>
<CannedResponseItem
theme={theme}
scope={item[1].scopeName}
shortcut={item[1].shortcut}
tags={item[1]?.tags}
text={item[1].text}
onPressDetail={() => alert('navigation to CannedResponseDetail')}
onPressUse={() => alert('Back to RoomView and wrote in MessageBox')}
/>
</>
));

View File

@ -0,0 +1,44 @@
import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View } from 'react-native';
import { themes } from '../../../constants/colors';
import { withTheme } from '../../../theme';
import Touch from '../../../utils/touch';
import { CustomIcon } from '../../../lib/Icons';
import sharedStyles from '../../Styles';
export const ROW_HEIGHT = 44;
const styles = StyleSheet.create({
container: {
paddingVertical: 11,
height: ROW_HEIGHT,
paddingHorizontal: 16,
flexDirection: 'row',
alignItems: 'center'
},
text: {
flex: 1,
fontSize: 16,
...sharedStyles.textRegular
}
});
const DropdownItem = React.memo(({ theme, onPress, iconName, text }) => (
<Touch theme={theme} onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }}>
<View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{text}</Text>
{iconName ? <CustomIcon name={iconName} size={22} color={themes[theme].auxiliaryText} /> : null}
</View>
</Touch>
));
DropdownItem.propTypes = {
text: PropTypes.string,
iconName: PropTypes.string,
theme: PropTypes.string,
onPress: PropTypes.func
};
export default withTheme(DropdownItem);

View File

@ -0,0 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import DropdownItem from './DropdownItem';
const DropdownItemFilter = ({ currentDepartment, value, onPress }) => (
<DropdownItem
text={value?.name}
iconName={currentDepartment?._id === value?._id ? 'check' : null}
onPress={() => onPress(value)}
/>
);
DropdownItemFilter.propTypes = {
currentDepartment: PropTypes.object,
value: PropTypes.string,
onPress: PropTypes.func
};
export default DropdownItemFilter;

View File

@ -0,0 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import DropdownItem from './DropdownItem';
const DropdownItemHeader = ({ department, onPress }) => (
<DropdownItem text={department?.name} iconName='filter' onPress={onPress} />
);
DropdownItemHeader.propTypes = {
department: PropTypes.object,
onPress: PropTypes.func
};
export default DropdownItemHeader;

View File

@ -0,0 +1,106 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import styles from '../styles';
import { themes } from '../../../constants/colors';
import { withTheme } from '../../../theme';
import { headerHeight } from '../../../containers/Header';
import * as List from '../../../containers/List';
import DropdownItemFilter from './DropdownItemFilter';
import DropdownItemHeader from './DropdownItemHeader';
import { ROW_HEIGHT } from './DropdownItem';
const ANIMATION_DURATION = 200;
class Dropdown extends React.Component {
static propTypes = {
isMasterDetail: PropTypes.bool,
theme: PropTypes.string,
insets: PropTypes.object,
currentDepartment: PropTypes.object,
onClose: PropTypes.func,
onDepartmentSelected: PropTypes.func,
departments: PropTypes.array
};
constructor(props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(this.animatedValue, {
toValue: 1,
duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true
}).start();
}
close = () => {
const { onClose } = this.props;
Animated.timing(this.animatedValue, {
toValue: 0,
duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true
}).start(() => onClose());
};
render() {
const { isMasterDetail, insets, theme, currentDepartment, onDepartmentSelected, departments } = this.props;
const statusBarHeight = insets?.top ?? 0;
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
const translateY = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-300, heightDestination] // approximated height of the component when closed/open
});
const backdropOpacity = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity]
});
const maxRows = 5;
return (
<>
<TouchableWithoutFeedback onPress={this.close}>
<Animated.View
style={[
styles.backdrop,
{
backgroundColor: themes[theme].backdropColor,
opacity: backdropOpacity,
top: heightDestination
}
]}
/>
</TouchableWithoutFeedback>
<Animated.View
style={[
styles.dropdownContainer,
{
transform: [{ translateY }],
backgroundColor: themes[theme].backgroundColor,
borderColor: themes[theme].separatorColor
}
]}>
<DropdownItemHeader department={currentDepartment} onPress={this.close} />
<List.Separator />
<FlatList
style={{ maxHeight: maxRows * ROW_HEIGHT }}
data={departments}
keyExtractor={item => item._id}
renderItem={({ item }) => (
<DropdownItemFilter onPress={onDepartmentSelected} currentDepartment={currentDepartment} value={item} />
)}
keyboardShouldPersistTaps='always'
/>
</Animated.View>
</>
);
}
}
export default withTheme(withSafeAreaInsets(Dropdown));

View File

@ -0,0 +1,363 @@
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { FlatList } from 'react-native';
import { useSelector } from 'react-redux';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { HeaderBackButton } from '@react-navigation/stack';
import database from '../../lib/database';
import I18n from '../../i18n';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import ActivityIndicator from '../../containers/ActivityIndicator';
import SearchHeader from '../../containers/SearchHeader';
import BackgroundContainer from '../../containers/BackgroundContainer';
import { getHeaderTitlePosition } from '../../containers/Header';
import { useTheme } from '../../theme';
import RocketChat from '../../lib/rocketchat';
import debounce from '../../utils/debounce';
import Navigation from '../../lib/Navigation';
import { goRoom } from '../../utils/goRoom';
import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List';
import { themes } from '../../constants/colors';
import log from '../../utils/log';
import CannedResponseItem from './CannedResponseItem';
import Dropdown from './Dropdown';
import DropdownItemHeader from './Dropdown/DropdownItemHeader';
import styles from './styles';
const COUNT = 25;
const fixedScopes = [
{
_id: 'all',
name: I18n.t('All')
},
{
_id: 'global',
name: I18n.t('Public')
},
{
_id: 'user',
name: I18n.t('Private')
}
];
const CannedResponsesListView = ({ navigation, route }) => {
const [room, setRoom] = useState(null);
const [cannedResponses, setCannedResponses] = useState([]);
const [cannedResponsesScopeName, setCannedResponsesScopeName] = useState([]);
const [departments, setDepartments] = useState([]);
// states used by the filter in Header and Dropdown
const [isSearching, setIsSearching] = useState(false);
const [currentDepartment, setCurrentDepartment] = useState(fixedScopes[0]);
const [showFilterDropdown, setShowFilterDropDown] = useState(false);
// states used to do a fetch by onChangeText, onDepartmentSelect and onEndReached
const [searchText, setSearchText] = useState('');
const [scope, setScope] = useState('');
const [departmentId, setDepartmentId] = useState('');
const [loading, setLoading] = useState(true);
const [offset, setOffset] = useState(0);
const insets = useSafeAreaInsets();
const { theme } = useTheme();
const { isMasterDetail } = useSelector(state => state.app);
const { rooms } = useSelector(state => state.room);
const getRoomFromDb = async () => {
const { rid } = route.params;
const db = database.active;
const subsCollection = db.get('subscriptions');
try {
const r = await subsCollection.find(rid);
setRoom(r);
} catch (error) {
console.log('CannedResponsesListView: Room not found');
log(error);
}
};
const getDepartments = debounce(async () => {
try {
const res = await RocketChat.getDepartments();
if (res.success) {
setDepartments([...fixedScopes, ...res.departments]);
}
} catch (e) {
setDepartments(fixedScopes);
log(e);
}
}, 300);
const goToDetail = item => {
navigation.navigate('CannedResponseDetail', { cannedResponse: item, room });
};
const navigateToRoom = item => {
if (!room) {
return;
}
const { name, username } = room;
const params = {
rid: room.rid,
name: RocketChat.getRoomTitle({
t: room.t,
fname: name,
name: username
}),
t: room.t,
roomUserId: RocketChat.getUidDirectMessage(room),
usedCannedResponse: item.text
};
if (room.rid) {
// if it's on master detail layout, we close the modal and replace RoomView
if (isMasterDetail) {
Navigation.navigate('DrawerNavigator');
goRoom({ item: params, isMasterDetail, usedCannedResponse: item.text });
} else {
let navigate = navigation.push;
// if this is a room focused
if (rooms.includes(room.rid)) {
({ navigate } = navigation);
}
navigate('RoomView', params);
}
}
};
const getListCannedResponse = async ({ text, department, depId, debounced }) => {
try {
const res = await RocketChat.getListCannedResponse({
text,
offset,
count: COUNT,
departmentId: depId,
scope: department
});
if (res.success) {
// search with changes on text or scope are debounced
// the begin result and pagination aren't debounced
setCannedResponses(prevCanned => (debounced ? res.cannedResponses : [...prevCanned, ...res.cannedResponses]));
setLoading(false);
setOffset(prevOffset => prevOffset + COUNT);
}
} catch (e) {
log(e);
}
};
useEffect(() => {
if (departments.length > 0) {
const newCannedResponses = cannedResponses.map(cr => {
let scopeName = '';
if (cr?.departmentId) {
scopeName = departments.filter(dep => dep._id === cr.departmentId)[0]?.name || 'Department';
} else {
scopeName = departments.filter(dep => dep._id === cr.scope)[0]?.name;
}
cr.scopeName = scopeName;
return cr;
});
setCannedResponsesScopeName(newCannedResponses);
}
}, [departments, cannedResponses]);
const searchCallback = useCallback(
debounce(async (text = '', department = '', depId = '') => {
await getListCannedResponse({ text, department, depId, debounced: true });
}, 1000),
[]
); // use debounce with useCallback https://stackoverflow.com/a/58594890
useEffect(() => {
getRoomFromDb();
getDepartments();
getListCannedResponse({ text: '', department: '', depId: '', debounced: false });
}, []);
const newSearch = () => {
setCannedResponses([]);
setLoading(true);
setOffset(0);
};
const onChangeText = text => {
newSearch();
setSearchText(text);
searchCallback(text, scope, departmentId);
};
const onDepartmentSelect = value => {
let department = '';
let depId = '';
if (value._id === fixedScopes[0]._id) {
department = '';
} else if (value._id === fixedScopes[1]._id) {
department = 'global';
} else if (value._id === fixedScopes[2]._id) {
department = 'user';
} else {
department = 'department';
depId = value._id;
}
newSearch();
setCurrentDepartment(value);
setScope(department);
setDepartmentId(depId);
setShowFilterDropDown(false);
searchCallback(searchText, department, depId);
};
const onEndReached = async () => {
if (cannedResponses.length < offset || loading) {
return;
}
setLoading(true);
await getListCannedResponse({ text: searchText, department: scope, depId: departmentId, debounced: false });
};
const getHeader = () => {
if (isSearching) {
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 });
return {
headerTitleAlign: 'left',
headerLeft: () => (
<HeaderButton.Container left>
<HeaderButton.Item
iconName='close'
onPress={() => {
onChangeText();
setIsSearching(false);
}}
/>
</HeaderButton.Container>
),
headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} />,
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerRight: () => null
};
}
const options = {
headerLeft: () => (
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
),
headerTitleAlign: 'center',
headerTitle: I18n.t('Canned_Responses'),
headerTitleContainerStyle: {
left: null,
right: null
}
};
if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
}
options.headerRight = () => (
<HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={() => setIsSearching(true)} />
</HeaderButton.Container>
);
return options;
};
const setHeader = () => {
const options = getHeader();
navigation.setOptions(options);
};
useEffect(() => {
setHeader();
}, [isSearching]);
const showDropdown = () => {
if (isSearching) {
setSearchText('');
setIsSearching(false);
}
setShowFilterDropDown(true);
};
const renderFlatListHeader = () => {
if (!departments.length) {
return null;
}
return (
<>
<DropdownItemHeader department={currentDepartment} onPress={showDropdown} />
<List.Separator />
</>
);
};
const renderContent = () => {
if (!cannedResponsesScopeName.length && !loading) {
return (
<>
{renderFlatListHeader()}
<BackgroundContainer text={I18n.t('No_canned_responses')} />
</>
);
}
return (
<FlatList
data={cannedResponsesScopeName}
extraData={cannedResponsesScopeName}
style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]}
renderItem={({ item }) => (
<CannedResponseItem
theme={theme}
scope={item.scopeName}
shortcut={item.shortcut}
tags={item?.tags}
text={item.text}
onPressDetail={() => goToDetail(item)}
onPressUse={() => navigateToRoom(item)}
/>
)}
keyExtractor={item => item._id || item.shortcut}
ListHeaderComponent={renderFlatListHeader}
stickyHeaderIndices={[0]}
onEndReached={onEndReached}
onEndReachedThreshold={0.5}
ItemSeparatorComponent={List.Separator}
ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null}
/>
);
};
return (
<SafeAreaView>
<StatusBar />
{renderContent()}
{showFilterDropdown ? (
<Dropdown
departments={departments}
currentDepartment={currentDepartment}
onDepartmentSelected={onDepartmentSelect}
onClose={() => setShowFilterDropDown(false)}
/>
) : null}
</SafeAreaView>
);
};
CannedResponsesListView.propTypes = {
navigation: PropTypes.object,
route: PropTypes.object
};
export default CannedResponsesListView;

View File

@ -0,0 +1,73 @@
import { StyleSheet } from 'react-native';
import sharedStyles from '../Styles';
export default StyleSheet.create({
list: {
flex: 1
},
dropdownContainer: {
width: '100%',
position: 'absolute',
top: 0,
borderBottomWidth: StyleSheet.hairlineWidth
},
backdrop: {
...StyleSheet.absoluteFill
},
wrapCannedItem: {
minHeight: 117,
maxHeight: 141,
padding: 16
},
cannedRow: {
flexDirection: 'row',
height: 36
},
cannedWrapShortcutScope: {
flex: 1
},
cannedShortcut: {
flex: 1,
fontSize: 14,
paddingTop: 0,
paddingBottom: 0,
...sharedStyles.textMedium
},
cannedScope: {
flex: 1,
fontSize: 12,
paddingTop: 0,
paddingBottom: 0,
...sharedStyles.textRegular
},
cannedText: {
marginTop: 8,
fontSize: 14,
paddingTop: 0,
paddingBottom: 0,
...sharedStyles.textRegular
},
cannedTagContainer: {
flexDirection: 'row',
overflow: 'hidden'
},
cannedTagWrap: {
borderRadius: 4,
marginRight: 4,
marginTop: 8,
height: 16
},
cannedTag: {
fontSize: 12,
paddingTop: 0,
paddingBottom: 0,
paddingHorizontal: 4,
...sharedStyles.textRegular
},
cannedUseButton: {
height: 28,
width: 56,
marginLeft: 8
}
});

View File

@ -10,9 +10,7 @@ import Item from './Item';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
zIndex: 1, zIndex: 1
marginTop: 24,
marginBottom: 32
}, },
inputContainer: { inputContainer: {
marginTop: 0, marginTop: 0,

View File

@ -1,15 +1,16 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { BackHandler, Keyboard, StyleSheet, Text, View } from 'react-native'; import { Text, Keyboard, StyleSheet, View, BackHandler, Image } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import parse from 'url-parse'; import parse from 'url-parse';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { TouchableOpacity } from 'react-native-gesture-handler'; import { TouchableOpacity } from 'react-native-gesture-handler';
import Orientation from 'react-native-orientation-locker';
import UserPreferences from '../../lib/userPreferences'; import UserPreferences from '../../lib/userPreferences';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import { selectServerRequest, serverRequest } from '../../actions/server'; import { selectServerRequest, serverRequest, serverFinishAdd as serverFinishAddAction } from '../../actions/server';
import { inviteLinksClear as inviteLinksClearAction } from '../../actions/inviteLinks'; import { inviteLinksClear as inviteLinksClearAction } from '../../actions/inviteLinks';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
import Button from '../../containers/Button'; import Button from '../../containers/Button';
@ -27,31 +28,38 @@ import database from '../../lib/database';
import { sanitizeLikeString } from '../../lib/database/utils'; import { sanitizeLikeString } from '../../lib/database/utils';
import SSLPinning from '../../utils/sslPinning'; import SSLPinning from '../../utils/sslPinning';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import { isTablet } from '../../utils/deviceInfo';
import { verticalScale, moderateScale } from '../../utils/scaling';
import { withDimensions } from '../../dimensions';
import ServerInput from './ServerInput'; import ServerInput from './ServerInput';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
onboardingImage: {
alignSelf: 'center',
resizeMode: 'contain'
},
title: { title: {
...sharedStyles.textBold, ...sharedStyles.textBold,
fontSize: 22 letterSpacing: 0,
alignSelf: 'center'
},
subtitle: {
...sharedStyles.textRegular,
alignSelf: 'center'
}, },
certificatePicker: { certificatePicker: {
marginBottom: 32,
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-end' justifyContent: 'flex-end'
}, },
chooseCertificateTitle: { chooseCertificateTitle: {
fontSize: 13,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
chooseCertificate: { chooseCertificate: {
fontSize: 13,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
description: { description: {
...sharedStyles.textRegular, ...sharedStyles.textRegular,
fontSize: 14, textAlign: 'center'
textAlign: 'left',
marginBottom: 24
}, },
connectButton: { connectButton: {
marginBottom: 0 marginBottom: 0
@ -59,23 +67,22 @@ const styles = StyleSheet.create({
}); });
class NewServerView extends React.Component { class NewServerView extends React.Component {
static navigationOptions = () => ({
title: I18n.t('Workspaces')
});
static propTypes = { static propTypes = {
navigation: PropTypes.object, navigation: PropTypes.object,
theme: PropTypes.string, theme: PropTypes.string,
connecting: PropTypes.bool.isRequired, connecting: PropTypes.bool.isRequired,
connectServer: PropTypes.func.isRequired, connectServer: PropTypes.func.isRequired,
selectServer: PropTypes.func.isRequired, selectServer: PropTypes.func.isRequired,
adding: PropTypes.bool,
previousServer: PropTypes.string, previousServer: PropTypes.string,
inviteLinksClear: PropTypes.func inviteLinksClear: PropTypes.func,
serverFinishAdd: PropTypes.func
}; };
constructor(props) { constructor(props) {
super(props); super(props);
if (!isTablet) {
Orientation.lockToPortrait();
}
this.setHeader(); this.setHeader();
this.state = { this.state = {
@ -92,25 +99,27 @@ class NewServerView extends React.Component {
this.queryServerHistory(); this.queryServerHistory();
} }
componentDidUpdate(prevProps) {
const { adding } = this.props;
if (prevProps.adding !== adding) {
this.setHeader();
}
}
componentWillUnmount() { componentWillUnmount() {
EventEmitter.removeListener('NewServer', this.handleNewServerEvent); EventEmitter.removeListener('NewServer', this.handleNewServerEvent);
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress); BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
const { previousServer, serverFinishAdd } = this.props;
if (previousServer) {
serverFinishAdd();
}
} }
setHeader = () => { setHeader = () => {
const { adding, navigation } = this.props; const { previousServer, navigation } = this.props;
if (adding) { if (previousServer) {
navigation.setOptions({ return navigation.setOptions({
headerTitle: I18n.t('Workspaces'),
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} onPress={this.close} testID='new-server-view-close' /> headerLeft: () => <HeaderButton.CloseModal navigation={navigation} onPress={this.close} testID='new-server-view-close' />
}); });
} }
return navigation.setOptions({
headerShown: false
});
}; };
handleBackPress = () => { handleBackPress = () => {
@ -273,16 +282,26 @@ class NewServerView extends React.Component {
renderCertificatePicker = () => { renderCertificatePicker = () => {
const { certificate } = this.state; const { certificate } = this.state;
const { theme } = this.props; const { theme, width, height, previousServer } = this.props;
return ( return (
<View style={styles.certificatePicker}> <View
<Text style={[styles.chooseCertificateTitle, { color: themes[theme].auxiliaryText }]}> style={[
styles.certificatePicker,
{
marginBottom: verticalScale(previousServer && !isTablet ? 10 : 30, height)
}
]}>
<Text
style={[
styles.chooseCertificateTitle,
{ color: themes[theme].auxiliaryText, fontSize: moderateScale(13, null, width) }
]}>
{certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')} {certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')}
</Text> </Text>
<TouchableOpacity <TouchableOpacity
onPress={certificate ? this.handleRemove : this.chooseCertificate} onPress={certificate ? this.handleRemove : this.chooseCertificate}
testID='new-server-choose-certificate'> testID='new-server-choose-certificate'>
<Text style={[styles.chooseCertificate, { color: themes[theme].tintColor }]}> <Text style={[styles.chooseCertificate, { color: themes[theme].tintColor, fontSize: moderateScale(13, null, width) }]}>
{certificate ?? I18n.t('Apply_Your_Certificate')} {certificate ?? I18n.t('Apply_Your_Certificate')}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
@ -291,12 +310,48 @@ class NewServerView extends React.Component {
}; };
render() { render() {
const { connecting, theme } = this.props; const { connecting, theme, previousServer, width, height } = this.props;
const { text, connectingOpen, serversHistory } = this.state; const { text, connectingOpen, serversHistory } = this.state;
const marginTop = previousServer ? 0 : 35;
return ( return (
<FormContainer theme={theme} testID='new-server-view' keyboardShouldPersistTaps='never'> <FormContainer theme={theme} testID='new-server-view' keyboardShouldPersistTaps='never'>
<FormContainerInner> <FormContainerInner>
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Join_your_workspace')}</Text> <Image
style={[
styles.onboardingImage,
{
marginBottom: verticalScale(10, height),
marginTop: isTablet ? 0 : verticalScale(marginTop, height),
width: verticalScale(100, height),
height: verticalScale(100, height)
}
]}
source={require('../../static/images/logo.png')}
fadeDuration={0}
/>
<Text
style={[
styles.title,
{
color: themes[theme].titleText,
fontSize: moderateScale(22, null, width),
marginBottom: verticalScale(8, height)
}
]}>
Rocket.Chat
</Text>
<Text
style={[
styles.subtitle,
{
color: themes[theme].controlText,
fontSize: moderateScale(16, null, width),
marginBottom: verticalScale(30, height)
}
]}>
{I18n.t('Onboarding_subtitle')}
</Text>
<ServerInput <ServerInput
text={text} text={text}
theme={theme} theme={theme}
@ -312,12 +367,20 @@ class NewServerView extends React.Component {
onPress={this.submit} onPress={this.submit}
disabled={!text || connecting} disabled={!text || connecting}
loading={!connectingOpen && connecting} loading={!connectingOpen && connecting}
style={styles.connectButton} style={[styles.connectButton, { marginTop: verticalScale(16, height) }]}
theme={theme} theme={theme}
testID='new-server-view-button' testID='new-server-view-button'
/> />
<OrSeparator theme={theme} /> <OrSeparator theme={theme} />
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}> <Text
style={[
styles.description,
{
color: themes[theme].auxiliaryText,
fontSize: moderateScale(14, null, width),
marginBottom: verticalScale(16, height)
}
]}>
{I18n.t('Onboarding_join_open_description')} {I18n.t('Onboarding_join_open_description')}
</Text> </Text>
<Button <Button
@ -339,14 +402,14 @@ class NewServerView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
connecting: state.server.connecting, connecting: state.server.connecting,
adding: state.server.adding,
previousServer: state.server.previousServer previousServer: state.server.previousServer
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
connectServer: (...params) => dispatch(serverRequest(...params)), connectServer: (...params) => dispatch(serverRequest(...params)),
selectServer: server => dispatch(selectServerRequest(server)), selectServer: server => dispatch(selectServerRequest(server)),
inviteLinksClear: () => dispatch(inviteLinksClearAction()) inviteLinksClear: () => dispatch(inviteLinksClearAction()),
serverFinishAdd: () => dispatch(serverFinishAddAction())
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewServerView)); export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(NewServerView)));

View File

@ -1,87 +0,0 @@
import React from 'react';
import { Image, Linking, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import Orientation from 'react-native-orientation-locker';
import I18n from '../../i18n';
import Button from '../../containers/Button';
import { isTablet } from '../../utils/deviceInfo';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
import { events, logEvent } from '../../utils/log';
import styles from './styles';
class OnboardingView extends React.Component {
static navigationOptions = {
headerShown: false
};
static propTypes = {
navigation: PropTypes.object,
theme: PropTypes.string
};
constructor(props) {
super(props);
if (!isTablet) {
Orientation.lockToPortrait();
}
}
shouldComponentUpdate(nextProps) {
const { theme } = this.props;
if (theme !== nextProps.theme) {
return true;
}
return false;
}
connectServer = () => {
logEvent(events.ONBOARD_JOIN_A_WORKSPACE);
const { navigation } = this.props;
navigation.navigate('NewServerView');
};
createWorkspace = async () => {
logEvent(events.ONBOARD_CREATE_NEW_WORKSPACE);
try {
await Linking.openURL('https://cloud.rocket.chat/trial');
} catch {
logEvent(events.ONBOARD_CREATE_NEW_WORKSPACE_F);
}
};
render() {
const { theme } = this.props;
return (
<FormContainer theme={theme} testID='onboarding-view'>
<FormContainerInner>
<Image style={styles.onboarding} source={require('../../static/images/logo.png')} fadeDuration={0} />
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Onboarding_title')}</Text>
<Text style={[styles.subtitle, { color: themes[theme].controlText }]}>{I18n.t('Onboarding_subtitle')}</Text>
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_description')}</Text>
<View style={styles.buttonsContainer}>
<Button
title={I18n.t('Onboarding_join_workspace')}
type='primary'
onPress={this.connectServer}
theme={theme}
testID='join-workspace'
/>
<Button
title={I18n.t('Create_a_new_workspace')}
type='secondary'
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.createWorkspace}
theme={theme}
testID='create-workspace-button'
/>
</View>
</FormContainerInner>
</FormContainer>
);
}
}
export default withTheme(OnboardingView);

View File

@ -1,41 +0,0 @@
import { StyleSheet } from 'react-native';
import { moderateScale, verticalScale } from '../../utils/scaling';
import { isTablet } from '../../utils/deviceInfo';
import sharedStyles from '../Styles';
export default StyleSheet.create({
onboarding: {
alignSelf: 'center',
marginTop: isTablet ? 0 : verticalScale(116),
marginBottom: verticalScale(50),
maxHeight: verticalScale(150),
resizeMode: 'contain',
width: 100,
height: 100
},
title: {
...sharedStyles.textBold,
letterSpacing: 0,
fontSize: moderateScale(24),
alignSelf: 'center',
marginBottom: verticalScale(8)
},
subtitle: {
...sharedStyles.textRegular,
fontSize: moderateScale(16),
alignSelf: 'center',
marginBottom: verticalScale(24)
},
description: {
...sharedStyles.textRegular,
...sharedStyles.textAlignCenter,
fontSize: moderateScale(14),
alignSelf: 'center',
marginHorizontal: 20
},
buttonsContainer: {
marginBottom: verticalScale(10),
marginTop: verticalScale(30)
}
});

View File

@ -64,7 +64,8 @@ class RoomActionsView extends React.Component {
transferLivechatGuestPermission: PropTypes.array, transferLivechatGuestPermission: PropTypes.array,
createTeamPermission: PropTypes.array, createTeamPermission: PropTypes.array,
addTeamChannelPermission: PropTypes.array, addTeamChannelPermission: PropTypes.array,
convertTeamPermission: PropTypes.array convertTeamPermission: PropTypes.array,
viewCannedResponsesPermission: PropTypes.array
}; };
constructor(props) { constructor(props) {
@ -74,6 +75,7 @@ class RoomActionsView extends React.Component {
const member = props.route.params?.member; const member = props.route.params?.member;
this.rid = props.route.params?.rid; this.rid = props.route.params?.rid;
this.t = props.route.params?.t; this.t = props.route.params?.t;
this.joined = props.route.params?.joined;
this.state = { this.state = {
room: room || { rid: this.rid, t: this.t }, room: room || { rid: this.rid, t: this.t },
membersCount: 0, membersCount: 0,
@ -89,7 +91,8 @@ class RoomActionsView extends React.Component {
canToggleEncryption: false, canToggleEncryption: false,
canCreateTeam: false, canCreateTeam: false,
canAddChannelToTeam: false, canAddChannelToTeam: false,
canConvertTeam: false canConvertTeam: false,
canViewCannedResponse: false
}; };
if (room && room.observe && room.rid) { if (room && room.observe && room.rid) {
this.roomObservable = room.observe(); this.roomObservable = room.observe();
@ -157,7 +160,8 @@ class RoomActionsView extends React.Component {
if (room.t === 'l') { if (room.t === 'l') {
const canForwardGuest = await this.canForwardGuest(); const canForwardGuest = await this.canForwardGuest();
const canReturnQueue = await this.canReturnQueue(); const canReturnQueue = await this.canReturnQueue();
this.setState({ canForwardGuest, canReturnQueue }); const canViewCannedResponse = await this.canViewCannedResponse();
this.setState({ canForwardGuest, canReturnQueue, canViewCannedResponse });
} }
} }
} }
@ -170,7 +174,7 @@ class RoomActionsView extends React.Component {
get isOmnichannelPreview() { get isOmnichannelPreview() {
const { room } = this.state; const { room } = this.state;
return room.t === 'l' && room.status === 'queued'; return room.t === 'l' && room.status === 'queued' && !this.joined;
} }
onPressTouchable = item => { onPressTouchable = item => {
@ -294,6 +298,14 @@ class RoomActionsView extends React.Component {
return permissions[0]; return permissions[0];
}; };
canViewCannedResponse = async () => {
const { room } = this.state;
const { viewCannedResponsesPermission } = this.props;
const { rid } = room;
const permissions = await RocketChat.hasPermission([viewCannedResponsesPermission], rid);
return permissions[0];
};
canReturnQueue = async () => { canReturnQueue = async () => {
try { try {
const { returnQueue } = await RocketChat.getRoutingConfig(); const { returnQueue } = await RocketChat.getRoutingConfig();
@ -927,7 +939,8 @@ class RoomActionsView extends React.Component {
joined, joined,
canAutoTranslate, canAutoTranslate,
canForwardGuest, canForwardGuest,
canReturnQueue canReturnQueue,
canViewCannedResponse
} = this.state; } = this.state;
const { rid, t, prid } = room; const { rid, t, prid } = room;
const isGroupChat = RocketChat.isGroupChat(room); const isGroupChat = RocketChat.isGroupChat(room);
@ -1145,6 +1158,18 @@ class RoomActionsView extends React.Component {
{this.teamChannelActions(t, room)} {this.teamChannelActions(t, room)}
{this.teamToChannelActions(t, room)} {this.teamToChannelActions(t, room)}
{['l'].includes(t) && !this.isOmnichannelPreview && canViewCannedResponse ? (
<>
<List.Item
title='Canned_Responses'
onPress={() => this.onPressTouchable({ route: 'CannedResponsesListView', params: { rid, room } })}
left={() => <List.Icon name='canned-response' />}
showActionIndicator
/>
<List.Separator />
</>
) : null}
{['l'].includes(t) && !this.isOmnichannelPreview ? ( {['l'].includes(t) && !this.isOmnichannelPreview ? (
<> <>
<List.Item <List.Item
@ -1236,7 +1261,8 @@ const mapStateToProps = state => ({
transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'], transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'],
createTeamPermission: state.permissions['create-team'], createTeamPermission: state.permissions['create-team'],
addTeamChannelPermission: state.permissions['add-team-channel'], addTeamChannelPermission: state.permissions['add-team-channel'],
convertTeamPermission: state.permissions['convert-team'] convertTeamPermission: state.permissions['convert-team'],
viewCannedResponsesPermission: state.permissions['view-canned-responses']
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -434,7 +434,6 @@ class RoomMembersView extends React.Component {
fetchMembers = async () => { fetchMembers = async () => {
const { rid, members, isLoading, allUsers, end, room, filtering } = this.state; const { rid, members, isLoading, allUsers, end, room, filtering } = this.state;
const { t } = room; const { t } = room;
let newMembers;
if (isLoading || end) { if (isLoading || end) {
return; return;
} }
@ -447,13 +446,13 @@ class RoomMembersView extends React.Component {
type: allUsers ? 'all' : 'online', type: allUsers ? 'all' : 'online',
filter: filtering, filter: filtering,
skip: members.length, skip: members.length,
limit: PAGE_SIZE limit: PAGE_SIZE,
allUsers
}); });
newMembers = membersResult.members;
this.setState({ this.setState({
members: members.concat(newMembers || []), members: members.concat(membersResult || []),
isLoading: false, isLoading: false,
end: newMembers.length < PAGE_SIZE end: membersResult?.length < PAGE_SIZE
}); });
this.setHeader(); this.setHeader();
} catch (e) { } catch (e) {

View File

@ -433,7 +433,7 @@ class RoomView extends React.Component {
goRoomActionsView = screen => { goRoomActionsView = screen => {
logEvent(events.ROOM_GO_RA); logEvent(events.ROOM_GO_RA);
const { room, member } = this.state; const { room, member, joined } = this.state;
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
if (isMasterDetail) { if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', { navigation.navigate('ModalStackNavigator', {
@ -443,7 +443,8 @@ class RoomView extends React.Component {
t: this.t, t: this.t,
room, room,
member, member,
showCloseModal: !!screen showCloseModal: !!screen,
joined
} }
}); });
} else { } else {
@ -451,7 +452,8 @@ class RoomView extends React.Component {
rid: this.rid, rid: this.rid,
t: this.t, t: this.t,
room, room,
member member,
joined
}); });
} }
}; };
@ -1057,8 +1059,10 @@ class RoomView extends React.Component {
}; };
renderFooter = () => { renderFooter = () => {
const { joined, room, selectedMessage, editing, replying, replyWithMention, readOnly } = this.state; const { joined, room, selectedMessage, editing, replying, replyWithMention, readOnly, loading } = this.state;
const { navigation, theme } = this.props; const { navigation, theme, route } = this.props;
const usedCannedResponse = route?.params?.usedCannedResponse;
if (!this.rid) { if (!this.rid) {
return null; return null;
@ -1074,6 +1078,7 @@ class RoomView extends React.Component {
<Touch <Touch
onPress={this.joinRoom} onPress={this.joinRoom}
style={[styles.joinRoomButton, { backgroundColor: themes[theme].actionTintColor }]} style={[styles.joinRoomButton, { backgroundColor: themes[theme].actionTintColor }]}
enabled={!loading}
theme={theme}> theme={theme}>
<Text style={[styles.joinRoomText, { color: themes[theme].buttonText }]} testID='room-view-join-button'> <Text style={[styles.joinRoomText, { color: themes[theme].buttonText }]} testID='room-view-join-button'>
{I18n.t(this.isOmnichannel ? 'Take_it' : 'Join')} {I18n.t(this.isOmnichannel ? 'Take_it' : 'Join')}
@ -1118,6 +1123,7 @@ class RoomView extends React.Component {
replyCancel={this.onReplyCancel} replyCancel={this.onReplyCancel}
getCustomEmoji={this.getCustomEmoji} getCustomEmoji={this.getCustomEmoji}
navigation={navigation} navigation={navigation}
usedCannedResponse={usedCannedResponse}
/> />
); );
}; };

View File

@ -1,13 +1,14 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Animated, Easing, FlatList, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native'; import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Linking } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { batch, connect } from 'react-redux'; import { batch, connect } from 'react-redux';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { withSafeAreaInsets } from 'react-native-safe-area-context';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
import Button from '../../containers/Button';
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms'; import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
import { selectServerRequest as selectServerRequestAction, serverInitAdd as serverInitAddAction } from '../../actions/server'; import { selectServerRequest as selectServerRequestAction, serverInitAdd as serverInitAddAction } from '../../actions/server';
import { ROOT_NEW_SERVER, appStart as appStartAction } from '../../actions/app'; import { appStart as appStartAction, ROOT_OUTSIDE } from '../../actions/app';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import I18n from '../../i18n'; import I18n from '../../i18n';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
@ -19,7 +20,7 @@ import { KEY_COMMAND, handleCommandSelectServer } from '../../commands';
import { isTablet } from '../../utils/deviceInfo'; import { isTablet } from '../../utils/deviceInfo';
import { localAuthenticate } from '../../utils/localAuthentication'; import { localAuthenticate } from '../../utils/localAuthentication';
import { showConfirmationAlert } from '../../utils/info'; import { showConfirmationAlert } from '../../utils/info';
import { events, logEvent } from '../../utils/log'; import log, { events, logEvent } from '../../utils/log';
import { headerHeight } from '../../containers/Header'; import { headerHeight } from '../../containers/Header';
import { goRoom } from '../../utils/goRoom'; import { goRoom } from '../../utils/goRoom';
import UserPreferences from '../../lib/userPreferences'; import UserPreferences from '../../lib/userPreferences';
@ -97,10 +98,19 @@ class ServerDropdown extends Component {
}).start(() => toggleServerDropdown()); }).start(() => toggleServerDropdown());
}; };
createWorkspace = async () => {
logEvent(events.RL_CREATE_NEW_WORKSPACE);
try {
await Linking.openURL('https://cloud.rocket.chat/trial');
} catch (e) {
log(e);
}
};
navToNewServer = previousServer => { navToNewServer = previousServer => {
const { appStart, initAdd } = this.props; const { appStart, initAdd } = this.props;
batch(() => { batch(() => {
appStart({ root: ROOT_NEW_SERVER }); appStart({ root: ROOT_OUTSIDE });
initAdd(previousServer); initAdd(previousServer);
}); });
}; };
@ -181,7 +191,7 @@ class ServerDropdown extends Component {
const { servers } = this.state; const { servers } = this.state;
const { theme, isMasterDetail, insets } = this.props; const { theme, isMasterDetail, insets } = this.props;
const maxRows = 4; const maxRows = 4;
const initialTop = 41 + Math.min(servers.length, maxRows) * ROW_HEIGHT; const initialTop = 87 + Math.min(servers.length, maxRows) * ROW_HEIGHT;
const statusBarHeight = insets?.top ?? 0; const statusBarHeight = insets?.top ?? 0;
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
const translateY = this.animatedValue.interpolate({ const translateY = this.animatedValue.interpolate({
@ -230,6 +240,17 @@ class ServerDropdown extends Component {
ItemSeparatorComponent={List.Separator} ItemSeparatorComponent={List.Separator}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />
<List.Separator />
<Button
title={I18n.t('Create_a_new_workspace')}
type='secondary'
onPress={this.createWorkspace}
theme={theme}
testID='rooms-list-header-create-workspace-button'
style={styles.buttonCreateWorkspace}
color={themes[theme].tintColor}
styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]}
/>
</Animated.View> </Animated.View>
</> </>
); );

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { BackHandler, FlatList, Keyboard, RefreshControl, Text, View } from 'react-native'; import { BackHandler, FlatList, Keyboard, RefreshControl, Text, View } from 'react-native';
import { connect } from 'react-redux'; import { batch, connect } from 'react-redux';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import Orientation from 'react-native-orientation-locker'; import Orientation from 'react-native-orientation-locker';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
@ -19,12 +19,13 @@ import {
roomsRequest as roomsRequestAction, roomsRequest as roomsRequestAction,
toggleSortDropdown as toggleSortDropdownAction toggleSortDropdown as toggleSortDropdownAction
} from '../../actions/rooms'; } from '../../actions/rooms';
import { appStart as appStartAction, ROOT_OUTSIDE } from '../../actions/app';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ActivityIndicator from '../../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import { selectServerRequest as selectServerRequestAction } from '../../actions/server'; import { selectServerRequest as selectServerRequestAction, serverInitAdd as serverInitAddAction } from '../../actions/server';
import { animateNextTransition } from '../../utils/layoutAnimation'; import { animateNextTransition } from '../../utils/layoutAnimation';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -133,7 +134,8 @@ class RoomsListView extends React.Component {
insets: PropTypes.object, insets: PropTypes.object,
queueSize: PropTypes.number, queueSize: PropTypes.number,
inquiryEnabled: PropTypes.bool, inquiryEnabled: PropTypes.bool,
encryptionBanner: PropTypes.string encryptionBanner: PropTypes.string,
initAdd: PropTypes.func
}; };
constructor(props) { constructor(props) {
@ -763,7 +765,7 @@ class RoomsListView extends React.Component {
}; };
handleCommands = ({ event }) => { handleCommands = ({ event }) => {
const { navigation, server, isMasterDetail } = this.props; const { navigation, server, isMasterDetail, appStart, initAdd } = this.props;
const { input } = event; const { input } = event;
if (handleCommandShowPreferences(event)) { if (handleCommandShowPreferences(event)) {
navigation.navigate('SettingsView'); navigation.navigate('SettingsView');
@ -782,7 +784,10 @@ class RoomsListView extends React.Component {
navigation.navigate('NewMessageStack'); navigation.navigate('NewMessageStack');
} }
} else if (handleCommandAddNewServer(event)) { } else if (handleCommandAddNewServer(event)) {
navigation.navigate('NewServerView', { previousServer: server }); batch(() => {
appStart({ root: ROOT_OUTSIDE });
initAdd(server);
});
} }
}; };
@ -967,7 +972,9 @@ const mapDispatchToProps = dispatch => ({
closeSearchHeader: () => dispatch(closeSearchHeaderAction()), closeSearchHeader: () => dispatch(closeSearchHeaderAction()),
roomsRequest: params => dispatch(roomsRequestAction(params)), roomsRequest: params => dispatch(roomsRequestAction(params)),
selectServerRequest: server => dispatch(selectServerRequestAction(server)), selectServerRequest: server => dispatch(selectServerRequestAction(server)),
closeServerDropdown: () => dispatch(closeServerDropdownAction()) closeServerDropdown: () => dispatch(closeServerDropdownAction()),
appStart: params => dispatch(appStartAction(params)),
initAdd: previousServer => dispatch(serverInitAddAction(previousServer))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomsListView)))); export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomsListView))));

View File

@ -56,5 +56,10 @@ export default StyleSheet.create({
marginRight: 12, marginRight: 12,
paddingVertical: 10, paddingVertical: 10,
...sharedStyles.textRegular ...sharedStyles.textRegular
},
buttonCreateWorkspace: {
height: 46,
justifyContent: 'center',
marginBottom: 0
} }
}); });

View File

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { HeaderBackButton } from '@react-navigation/stack';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import RoomHeader from '../containers/RoomHeader'; import RoomHeader from '../containers/RoomHeader';
@ -16,6 +17,7 @@ import * as HeaderButton from '../containers/HeaderButton';
import BackgroundContainer from '../containers/BackgroundContainer'; import BackgroundContainer from '../containers/BackgroundContainer';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import ActivityIndicator from '../containers/ActivityIndicator'; import ActivityIndicator from '../containers/ActivityIndicator';
import SearchHeader from '../containers/SearchHeader';
import RoomItem, { ROW_HEIGHT } from '../presentation/RoomItem'; import RoomItem, { ROW_HEIGHT } from '../presentation/RoomItem';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import { withDimensions } from '../dimensions'; import { withDimensions } from '../dimensions';
@ -28,7 +30,6 @@ import { withActionSheet } from '../containers/ActionSheet';
import { deleteRoom as deleteRoomAction } from '../actions/room'; import { deleteRoom as deleteRoomAction } from '../actions/room';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import SearchHeader from '../containers/SearchHeader';
const API_FETCH_COUNT = 25; const API_FETCH_COUNT = 25;
const PERMISSION_DELETE_C = 'delete-c'; const PERMISSION_DELETE_C = 'delete-c';
@ -157,9 +158,7 @@ class TeamChannelsView extends React.Component {
setHeader = () => { setHeader = () => {
const { isSearching, showCreate, data } = this.state; const { isSearching, showCreate, data } = this.state;
const { const { navigation, isMasterDetail, insets, theme } = this.props;
navigation, isMasterDetail, insets, theme
} = this.props;
const { team } = this; const { team } = this;
if (!team) { if (!team) {
@ -169,7 +168,7 @@ class TeamChannelsView extends React.Component {
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 }); const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 });
if (isSearching) { if (isSearching) {
return { const options = {
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerLeft: () => ( headerLeft: () => (
<HeaderButton.Container left> <HeaderButton.Container left>
@ -190,6 +189,7 @@ class TeamChannelsView extends React.Component {
}, },
headerRight: () => null headerRight: () => null
}; };
return navigation.setOptions(options);
} }
const options = { const options = {
@ -199,6 +199,9 @@ class TeamChannelsView extends React.Component {
left: headerTitlePosition.left, left: headerTitlePosition.left,
right: headerTitlePosition.right right: headerTitlePosition.right
}, },
headerLeft: () => (
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
),
headerTitle: () => ( headerTitle: () => (
<RoomHeader <RoomHeader
title={RocketChat.getRoomTitle(team)} title={RocketChat.getRoomTitle(team)}

View File

@ -96,7 +96,6 @@ class WorkspaceView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
server: state.server.server, server: state.server.server,
adding: state.server.adding,
Site_Name: state.settings.Site_Name, Site_Name: state.settings.Site_Name,
Site_Url: state.settings.Site_Url, Site_Url: state.settings.Site_Url,
Assets_favicon_512: state.settings.Assets_favicon_512, Assets_favicon_512: state.settings.Assets_favicon_512,

View File

@ -1,6 +1,6 @@
module.exports = { module.exports = {
presets: ['module:metro-react-native-babel-preset'], presets: ['module:metro-react-native-babel-preset'],
plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], plugins: [['@babel/plugin-proposal-decorators', { legacy: true }], 'react-native-reanimated/plugin'],
env: { env: {
production: { production: {
plugins: ['transform-remove-console'] plugins: ['transform-remove-console']

View File

@ -1,10 +1,6 @@
const data = require('../data'); const data = require('../data');
async function navigateToWorkspace(server = data.server) { async function navigateToWorkspace(server = data.server) {
await waitFor(element(by.id('onboarding-view')))
.toBeVisible()
.withTimeout(10000);
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);
@ -16,9 +12,6 @@ async function navigateToWorkspace(server = data.server) {
} }
async function navigateToLogin(server) { async function navigateToLogin(server) {
await waitFor(element(by.id('onboarding-view')))
.toBeVisible()
.withTimeout(20000);
await navigateToWorkspace(server); await navigateToWorkspace(server);
await element(by.id('workspace-view-login')).tap(); await element(by.id('workspace-view-login')).tap();
await waitFor(element(by.id('login-view'))) await waitFor(element(by.id('login-view')))
@ -28,9 +21,6 @@ async function navigateToLogin(server) {
} }
async function navigateToRegister(server) { async function navigateToRegister(server) {
await waitFor(element(by.id('onboarding-view')))
.toBeVisible()
.withTimeout(20000);
await navigateToWorkspace(server); await navigateToWorkspace(server);
await element(by.id('workspace-view-register')).tap(); await element(by.id('workspace-view-register')).tap();
await waitFor(element(by.id('register-view'))) await waitFor(element(by.id('register-view')))
@ -70,10 +60,10 @@ async function logout() {
.withTimeout(10000); .withTimeout(10000);
await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist(); await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist();
await element(by.text('Logout')).tap(); await element(by.text('Logout')).tap();
await waitFor(element(by.id('onboarding-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(10000); .withTimeout(10000);
await expect(element(by.id('onboarding-view'))).toBeVisible(); await expect(element(by.id('new-server-view'))).toBeVisible();
} }
async function mockMessage(message, isThread = false) { async function mockMessage(message, isThread = false) {

View File

@ -5,7 +5,7 @@ const reopenAndCheckServer = async server => {
await device.launchApp({ permissions: { notifications: 'YES' } }); await device.launchApp({ permissions: { notifications: 'YES' } });
await waitFor(element(by.id('rooms-list-view'))) await waitFor(element(by.id('rooms-list-view')))
.toBeVisible() .toBeVisible()
.withTimeout(6000); .withTimeout(10000);
await checkServer(server); await checkServer(server);
}; };
@ -19,13 +19,21 @@ describe('Change server', () => {
.withTimeout(10000); .withTimeout(10000);
}); });
it('should login to server, add new server, close the app, open the app and show previous logged server', async () => { it('should open the dropdown button, have the server add button and create workspace button', async () => {
await element(by.id('rooms-list-header-server-dropdown-button')).tap(); await element(by.id('rooms-list-header-server-dropdown-button')).tap();
await waitFor(element(by.id('rooms-list-header-server-dropdown'))) await waitFor(element(by.id('rooms-list-header-server-dropdown')))
.toBeVisible() .toBeVisible()
.withTimeout(5000); .withTimeout(5000);
await element(by.id('rooms-list-header-server-add')).tap(); await waitFor(element(by.id('rooms-list-header-server-add')))
.toBeVisible()
.withTimeout(5000);
await waitFor(element(by.id('rooms-list-header-create-workspace-button')))
.toBeVisible()
.withTimeout(5000);
});
it('should login to server, add new server, close the app, open the app and show previous logged server', async () => {
await element(by.id('rooms-list-header-server-add')).tap();
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(6000); .withTimeout(6000);

View File

@ -37,11 +37,10 @@ describe('i18n', () => {
}, },
delete: true delete: true
}); });
await waitFor(element(by.id('onboarding-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(20000); .withTimeout(20000);
await expect(element(by.id('join-workspace').and(by.label('Join a workspace')))).toBeVisible(); await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible();
await expect(element(by.id('create-workspace-button').and(by.label('Create a new workspace')))).toBeVisible();
}); });
it("OS set to unavailable language and fallback to 'en'", async () => { it("OS set to unavailable language and fallback to 'en'", async () => {
@ -52,11 +51,10 @@ describe('i18n', () => {
locale: 'es-MX' locale: 'es-MX'
} }
}); });
await waitFor(element(by.id('onboarding-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(20000); .withTimeout(20000);
await expect(element(by.id('join-workspace').and(by.label('Join a workspace')))).toBeVisible(); await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible();
await expect(element(by.id('create-workspace-button').and(by.label('Create a new workspace')))).toBeVisible();
}); });
/** /**
@ -71,9 +69,6 @@ describe('i18n', () => {
// locale: "nl" // locale: "nl"
// } // }
// }); // });
// await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000);
// await expect(element(by.id('join-workspace').and(by.label('Word lid van een werkruimte')))).toBeVisible();
// await expect(element(by.id('create-workspace-button').and(by.label('Een nieuwe werkruimte aanmaken')))).toBeVisible();
// }); // });
}); });

View File

@ -3,22 +3,18 @@ const data = require('../../data');
describe('Onboarding', () => { describe('Onboarding', () => {
before(async () => { before(async () => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await waitFor(element(by.id('onboarding-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(20000); .withTimeout(20000);
}); });
describe('Render', () => { describe('Render', () => {
it('should have onboarding screen', async () => { it('should have onboarding screen', async () => {
await expect(element(by.id('onboarding-view'))).toBeVisible(); await expect(element(by.id('new-server-view'))).toBeVisible();
}); });
it('should have "Join a workspace"', async () => { it('should have "Join our open workspace"', async () => {
await expect(element(by.id('join-workspace'))).toBeVisible(); await expect(element(by.id('new-server-view-open'))).toBeVisible();
});
it('should have "Create a new workspace"', async () => {
await expect(element(by.id('create-workspace-button'))).toBeVisible();
}); });
}); });
@ -27,13 +23,6 @@ describe('Onboarding', () => {
// // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554 // // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554
// }); // });
it('should navigate to join a workspace', async () => {
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view')))
.toBeVisible()
.withTimeout(60000);
});
it('should enter an invalid server and get error', async () => { it('should enter an invalid server and get error', async () => {
await element(by.id('new-server-view-input')).typeText('invalidtest\n'); await element(by.id('new-server-view-input')).typeText('invalidtest\n');
const errorText = 'Oops!'; const errorText = 'Oops!';
@ -52,13 +41,9 @@ describe('Onboarding', () => {
it('should enter a valid server without login services and navigate to login', async () => { it('should enter a valid server without login services and navigate to login', async () => {
await device.launchApp({ newInstance: true }); await device.launchApp({ newInstance: true });
await waitFor(element(by.id('onboarding-view')))
.toBeVisible()
.withTimeout(2000);
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(5000);
await element(by.id('new-server-view-input')).typeText(`${data.server}\n`); await element(by.id('new-server-view-input')).typeText(`${data.server}\n`);
await waitFor(element(by.id('workspace-view'))) await waitFor(element(by.id('workspace-view')))
.toBeVisible() .toBeVisible()

View File

@ -11,7 +11,6 @@ describe('Server history', () => {
await navigateToLogin(); await navigateToLogin();
await login(data.users.regular.username, data.users.regular.password); await login(data.users.regular.username, data.users.regular.password);
await logout(); await logout();
await element(by.id('join-workspace')).tap();
await waitFor(element(by.id('new-server-view'))) await waitFor(element(by.id('new-server-view')))
.toBeVisible() .toBeVisible()
.withTimeout(60000); .withTimeout(60000);

View File

@ -7,3 +7,10 @@
#import <react-native-simple-crypto/Aes.h> #import <react-native-simple-crypto/Aes.h>
#import <react-native-simple-crypto/Rsa.h> #import <react-native-simple-crypto/Rsa.h>
#import <react-native-simple-crypto/Shared.h> #import <react-native-simple-crypto/Shared.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTViewManager.h>
#import <React/RCTBridgeModule.h>
// Silence warning
#import "../../node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/SupportingFiles/Bridging.h"

View File

@ -4,6 +4,9 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ
require_relative '../node_modules/react-native-unimodules/cocoapods.rb' require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
def all_pods def all_pods
pod 'WatermelonDB', :path => '../node_modules/@nozbe/watermelondb'
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi', :modular_headers => true
pod 'simdjson', path: '../node_modules/@nozbe/simdjson'
config = use_native_modules! config = use_native_modules!
use_unimodules! use_unimodules!
use_react_native!( use_react_native!(

View File

@ -541,7 +541,7 @@ PODS:
- TOCropViewController - TOCropViewController
- RNLocalize (2.1.1): - RNLocalize (2.1.1):
- React-Core - React-Core
- RNReanimated (2.2.0): - RNReanimated (2.2.2):
- DoubleConversion - DoubleConversion
- FBLazyVector - FBLazyVector
- FBReactNativeSpec - FBReactNativeSpec
@ -582,6 +582,7 @@ PODS:
- SDWebImageWebPCoder (0.4.1): - SDWebImageWebPCoder (0.4.1):
- libwebp (~> 1.0) - libwebp (~> 1.0)
- SDWebImage/Core (~> 5.5) - SDWebImage/Core (~> 5.5)
- simdjson (0.9.6-fix2)
- TOCropViewController (2.5.3) - TOCropViewController (2.5.3)
- UMAppLoader (1.2.0) - UMAppLoader (1.2.0)
- UMBarCodeScannerInterface (5.2.1) - UMBarCodeScannerInterface (5.2.1)
@ -600,6 +601,9 @@ PODS:
- UMFontInterface - UMFontInterface
- UMSensorsInterface (5.2.1) - UMSensorsInterface (5.2.1)
- UMTaskManagerInterface (5.2.1) - UMTaskManagerInterface (5.2.1)
- WatermelonDB (0.23.0):
- React
- React-jsi
- Yoga (1.14.0) - Yoga (1.14.0)
- YogaKit (1.18.1): - YogaKit (1.18.1):
- Yoga (~> 1.14) - Yoga (~> 1.14)
@ -708,6 +712,7 @@ DEPENDENCIES:
- RNRootView (from `../node_modules/rn-root-view`) - RNRootView (from `../node_modules/rn-root-view`)
- RNScreens (from `../node_modules/react-native-screens`) - RNScreens (from `../node_modules/react-native-screens`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`) - RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- "simdjson (from `../node_modules/@nozbe/simdjson`)"
- UMAppLoader (from `../node_modules/unimodules-app-loader/ios`) - UMAppLoader (from `../node_modules/unimodules-app-loader/ios`)
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`) - UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
- UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`) - UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`)
@ -721,6 +726,7 @@ DEPENDENCIES:
- "UMReactNativeAdapter (from `../node_modules/@unimodules/react-native-adapter/ios`)" - "UMReactNativeAdapter (from `../node_modules/@unimodules/react-native-adapter/ios`)"
- UMSensorsInterface (from `../node_modules/unimodules-sensors-interface/ios`) - UMSensorsInterface (from `../node_modules/unimodules-sensors-interface/ios`)
- UMTaskManagerInterface (from `../node_modules/unimodules-task-manager-interface/ios`) - UMTaskManagerInterface (from `../node_modules/unimodules-task-manager-interface/ios`)
- "WatermelonDB (from `../node_modules/@nozbe/watermelondb`)"
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS: SPEC REPOS:
@ -916,6 +922,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-screens" :path: "../node_modules/react-native-screens"
RNVectorIcons: RNVectorIcons:
:path: "../node_modules/react-native-vector-icons" :path: "../node_modules/react-native-vector-icons"
simdjson:
:path: "../node_modules/@nozbe/simdjson"
UMAppLoader: UMAppLoader:
:path: "../node_modules/unimodules-app-loader/ios" :path: "../node_modules/unimodules-app-loader/ios"
UMBarCodeScannerInterface: UMBarCodeScannerInterface:
@ -942,6 +950,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/unimodules-sensors-interface/ios" :path: "../node_modules/unimodules-sensors-interface/ios"
UMTaskManagerInterface: UMTaskManagerInterface:
:path: "../node_modules/unimodules-task-manager-interface/ios" :path: "../node_modules/unimodules-task-manager-interface/ios"
WatermelonDB:
:path: "../node_modules/@nozbe/watermelondb"
Yoga: Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga" :path: "../node_modules/react-native/ReactCommon/yoga"
@ -1053,12 +1063,13 @@ SPEC CHECKSUMS:
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211 RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNImageCropPicker: 38865ab4af1b0b2146ad66061196bc0184946855 RNImageCropPicker: 38865ab4af1b0b2146ad66061196bc0184946855
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
RNReanimated: 9c13c86454bfd54dab7505c1a054470bfecd2563 RNReanimated: 241c586663f44f19a53883c63375fdd041253960
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494 RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
RNScreens: c526239bbe0e957b988dacc8d75ac94ec9cb19da RNScreens: c526239bbe0e957b988dacc8d75ac94ec9cb19da
RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4 RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4
SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9 SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9
SDWebImageWebPCoder: 36f8f47bd9879a8aea6044765c1351120fd8e3a8 SDWebImageWebPCoder: 36f8f47bd9879a8aea6044765c1351120fd8e3a8
simdjson: 85016870cd17207312b718ef6652eb6a1cd6a2b0
TOCropViewController: 20a14b6a7a098308bf369e7c8d700dc983a974e6 TOCropViewController: 20a14b6a7a098308bf369e7c8d700dc983a974e6
UMAppLoader: 61049c8d55590b74e9ae1d5429bf68d96b4a2528 UMAppLoader: 61049c8d55590b74e9ae1d5429bf68d96b4a2528
UMBarCodeScannerInterface: e5e4c87797d3d01214e25cd1618866caf5d4f17f UMBarCodeScannerInterface: e5e4c87797d3d01214e25cd1618866caf5d4f17f
@ -1073,9 +1084,10 @@ SPEC CHECKSUMS:
UMReactNativeAdapter: 538efe92e781b5d7678cf95b34c46f2d0989a557 UMReactNativeAdapter: 538efe92e781b5d7678cf95b34c46f2d0989a557
UMSensorsInterface: cb5bf31d52c4349f0ff9e3c049bbe4df0d80d383 UMSensorsInterface: cb5bf31d52c4349f0ff9e3c049bbe4df0d80d383
UMTaskManagerInterface: 80653f25c55d9e6d79d6a0a65589fa213feaee11 UMTaskManagerInterface: 80653f25c55d9e6d79d6a0a65589fa213feaee11
WatermelonDB: 577c61fceff16e9f9103b59d14aee4850c0307b6
Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 2cb6f38193d69310b4dfc466be3cf4ff7a5e98d3 PODFILE CHECKSUM: 46fb1ed324f44252f8900c63e7c93e4391658bad
COCOAPODS: 1.10.1 COCOAPODS: 1.10.1

View File

@ -24,7 +24,6 @@
1E068CFF24FD2DC700A0FFC1 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E068CFD24FD2DC700A0FFC1 /* AppGroup.swift */; }; 1E068CFF24FD2DC700A0FFC1 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E068CFD24FD2DC700A0FFC1 /* AppGroup.swift */; };
1E068D0124FD2E0500A0FFC1 /* AppGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E068D0024FD2E0500A0FFC1 /* AppGroup.m */; }; 1E068D0124FD2E0500A0FFC1 /* AppGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E068D0024FD2E0500A0FFC1 /* AppGroup.m */; };
1E068D0224FD2E0500A0FFC1 /* AppGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E068D0024FD2E0500A0FFC1 /* AppGroup.m */; }; 1E068D0224FD2E0500A0FFC1 /* AppGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E068D0024FD2E0500A0FFC1 /* AppGroup.m */; };
1E1C2F7E250FACB5005DCE7D /* libWatermelonDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AAA749B23043AD300F1ADE9 /* libWatermelonDB.a */; };
1E1C2F80250FCB69005DCE7D /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1C2F7F250FCB69005DCE7D /* Database.swift */; }; 1E1C2F80250FCB69005DCE7D /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1C2F7F250FCB69005DCE7D /* Database.swift */; };
1E1EA80A2326CD2200E22452 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8092326CD2200E22452 /* AVFoundation.framework */; }; 1E1EA80A2326CD2200E22452 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8092326CD2200E22452 /* AVFoundation.framework */; };
1E1EA80C2326CD2800E22452 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA80B2326CD2800E22452 /* AudioToolbox.framework */; }; 1E1EA80C2326CD2800E22452 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA80B2326CD2800E22452 /* AudioToolbox.framework */; };
@ -43,7 +42,6 @@
1E470E832513A71E00E3DD1D /* RocketChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E470E822513A71E00E3DD1D /* RocketChat.swift */; }; 1E470E832513A71E00E3DD1D /* RocketChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E470E822513A71E00E3DD1D /* RocketChat.swift */; };
1E51D962251263CD00DC95DE /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E51D961251263CD00DC95DE /* MessageType.swift */; }; 1E51D962251263CD00DC95DE /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E51D961251263CD00DC95DE /* MessageType.swift */; };
1E51D965251263D600DC95DE /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E51D964251263D600DC95DE /* NotificationType.swift */; }; 1E51D965251263D600DC95DE /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E51D964251263D600DC95DE /* NotificationType.swift */; };
1E55FDB32320675C0048D2F9 /* libWatermelonDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AAA749B23043AD300F1ADE9 /* libWatermelonDB.a */; };
1E598AE42515057D002BDFBD /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE32515057D002BDFBD /* Date+Extensions.swift */; }; 1E598AE42515057D002BDFBD /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE32515057D002BDFBD /* Date+Extensions.swift */; };
1E598AE725150660002BDFBD /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE625150660002BDFBD /* Data+Extensions.swift */; }; 1E598AE725150660002BDFBD /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE625150660002BDFBD /* Data+Extensions.swift */; };
1E598AE925151A63002BDFBD /* SendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE825151A63002BDFBD /* SendMessage.swift */; }; 1E598AE925151A63002BDFBD /* SendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE825151A63002BDFBD /* SendMessage.swift */; };
@ -158,13 +156,6 @@
remoteGlobalIDString = 1EFEB5942493B6640072EDC0; remoteGlobalIDString = 1EFEB5942493B6640072EDC0;
remoteInfo = NotificationService; remoteInfo = NotificationService;
}; };
7AAA749A23043AD300F1ADE9 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 290E43E48AD8418287FA99D6 /* WatermelonDB.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 6E660D5E213BCCD300189354;
remoteInfo = WatermelonDB;
};
7AAB3E0F257E6A6E00707CF6 /* PBXContainerItemProxy */ = { 7AAB3E0F257E6A6E00707CF6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
@ -265,7 +256,6 @@
1EFEB5972493B6640072EDC0 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; }; 1EFEB5972493B6640072EDC0 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
1EFEB5992493B6640072EDC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 1EFEB5992493B6640072EDC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1EFEB5A12493B67D0072EDC0 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; }; 1EFEB5A12493B67D0072EDC0 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = "<group>"; };
290E43E48AD8418287FA99D6 /* WatermelonDB.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = WatermelonDB.xcodeproj; path = "../node_modules/@nozbe/watermelondb/native/ios/WatermelonDB.xcodeproj"; sourceTree = "<group>"; };
2977818A2C0F18284F485E2B /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Pods/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = "<group>"; }; 2977818A2C0F18284F485E2B /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Pods/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = "<group>"; };
298C921C910A1A2FD60068AE /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Pods/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = "<group>"; }; 298C921C910A1A2FD60068AE /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Pods/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = "<group>"; };
2B2C0164CCC5BED03F5A257E /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2B2C0164CCC5BED03F5A257E /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -321,7 +311,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1E55FDB32320675C0048D2F9 /* libWatermelonDB.a in Frameworks */,
1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */, 1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */,
DC5529EC3B4695AC7319279F /* libPods-defaults-ShareRocketChatRN.a in Frameworks */, DC5529EC3B4695AC7319279F /* libPods-defaults-ShareRocketChatRN.a in Frameworks */,
); );
@ -331,7 +320,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1E1C2F7E250FACB5005DCE7D /* libWatermelonDB.a in Frameworks */,
EDE8A8F63A3ADB93A6640F8E /* libPods-defaults-NotificationService.a in Frameworks */, EDE8A8F63A3ADB93A6640F8E /* libPods-defaults-NotificationService.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -503,18 +491,9 @@
name = Pods; name = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7AAA749723043AD300F1ADE9 /* Products */ = {
isa = PBXGroup;
children = (
7AAA749B23043AD300F1ADE9 /* libWatermelonDB.a */,
);
name = Products;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = { 832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
290E43E48AD8418287FA99D6 /* WatermelonDB.xcodeproj */,
); );
name = Libraries; name = Libraries;
sourceTree = "<group>"; sourceTree = "<group>";
@ -747,12 +726,6 @@
mainGroup = 83CBB9F61A601CBA00E9B192; mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = (
{
ProductGroup = 7AAA749723043AD300F1ADE9 /* Products */;
ProjectRef = 290E43E48AD8418287FA99D6 /* WatermelonDB.xcodeproj */;
},
);
projectRoot = ""; projectRoot = "";
targets = ( targets = (
13B07F861A680F5B00A75B9A /* RocketChatRN */, 13B07F861A680F5B00A75B9A /* RocketChatRN */,
@ -763,16 +736,6 @@
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXReferenceProxy section */
7AAA749B23043AD300F1ADE9 /* libWatermelonDB.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libWatermelonDB.a;
remoteRef = 7AAA749A23043AD300F1ADE9 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
13B07F8E1A680F5B00A75B9A /* Resources */ = { 13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;

View File

@ -103,6 +103,7 @@
</dict> </dict>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>audio</string>
<string>fetch</string> <string>fetch</string>
<string>voip</string> <string>voip</string>
</array> </array>

View File

@ -29,7 +29,7 @@
"dependencies": { "dependencies": {
"@bugsnag/react-native": "^7.10.5", "@bugsnag/react-native": "^7.10.5",
"@codler/react-native-keyboard-aware-scroll-view": "^1.0.1", "@codler/react-native-keyboard-aware-scroll-view": "^1.0.1",
"@nozbe/watermelondb": "0.19.0", "@nozbe/watermelondb": "0.23.0",
"@react-native-community/art": "^1.2.0", "@react-native-community/art": "^1.2.0",
"@react-native-community/async-storage": "1.12.1", "@react-native-community/async-storage": "1.12.1",
"@react-native-community/blur": "^3.6.0", "@react-native-community/blur": "^3.6.0",
@ -104,7 +104,7 @@
"react-native-popover-view": "4.0.1", "react-native-popover-view": "4.0.1",
"react-native-progress": "4.1.2", "react-native-progress": "4.1.2",
"react-native-prompt-android": "^1.1.0", "react-native-prompt-android": "^1.1.0",
"react-native-reanimated": "1.9.0", "react-native-reanimated": "2.2.2",
"react-native-restart": "0.0.22", "react-native-restart": "0.0.22",
"react-native-safe-area-context": "3.2.0", "react-native-safe-area-context": "3.2.0",
"react-native-screens": "2.9.0", "react-native-screens": "2.9.0",

View File

@ -1,9 +1,32 @@
diff --git a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt b/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
index 802f137..cfcac91 100644
--- a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
+++ b/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
@@ -8,7 +8,7 @@ import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteQuery
import java.io.File
-class Database(private val name: String, private val context: Context) {
+public class Database(private val name: String, private val context: Context) {
private val db: SQLiteDatabase by lazy {
SQLiteDatabase.openOrCreateDatabase(
@@ -44,7 +44,7 @@ class Database(private val name: String, private val context: Context) {
fun delete(query: SQL, args: QueryArgs) = db.execSQL(query, args)
- fun rawQuery(sql: SQL, args: QueryArgs = emptyArray()): Cursor {
+ public fun rawQuery(sql: SQL, args: QueryArgs = emptyArray()): Cursor {
// HACK: db.rawQuery only supports String args, and there's no clean way AFAIK to construct
// a query with arbitrary args (like with execSQL). However, we can misuse cursor factory
// to get the reference of a SQLiteQuery before it's executed
diff --git a/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift b/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift diff --git a/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift b/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift
index 43f2c9c..e24a24f 100644 index b4d7151..429e318 100644
--- a/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift --- a/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift
+++ b/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift +++ b/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift
@@ -1,14 +1,14 @@ @@ -1,15 +1,15 @@
import Foundation import Foundation
import SQLite3
-class Database { -class Database {
- typealias SQL = String - typealias SQL = String
@ -22,7 +45,7 @@ index 43f2c9c..e24a24f 100644
self.path = path self.path = path
fmdb = FMDatabase(path: path) fmdb = FMDatabase(path: path)
open() open()
@@ -53,7 +53,7 @@ class Database { @@ -54,7 +54,7 @@ class Database {
} }
} }
@ -31,38 +54,3 @@ index 43f2c9c..e24a24f 100644
let resultSet = try fmdb.executeQuery(query, values: args) let resultSet = try fmdb.executeQuery(query, values: args)
return AnyIterator { return AnyIterator {
diff --git a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt b/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
index 2217222..5b2eb73 100644
--- a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
+++ b/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
@@ -5,7 +5,7 @@ import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import java.io.File
-class Database(private val name: String, private val context: Context) {
+public class Database(private val name: String, private val context: Context) {
private val db: SQLiteDatabase by lazy {
SQLiteDatabase.openOrCreateDatabase(
@@ -41,7 +41,7 @@ class Database(private val name: String, private val context: Context) {
fun delete(query: SQL, args: QueryArgs) = db.execSQL(query, args)
- fun rawQuery(query: SQL, args: RawQueryArgs = emptyArray()): Cursor = db.rawQuery(query, args)
+ public fun rawQuery(query: SQL, args: RawQueryArgs = emptyArray()): Cursor = db.rawQuery(query, args)
fun count(query: SQL, args: RawQueryArgs = emptyArray()): Int =
rawQuery(query, args).use {
diff --git a/node_modules/@nozbe/watermelondb/decorators/date/index.js b/node_modules/@nozbe/watermelondb/decorators/date/index.js
index 65690af..ce71aa0 100644
--- a/node_modules/@nozbe/watermelondb/decorators/date/index.js
+++ b/node_modules/@nozbe/watermelondb/decorators/date/index.js
@@ -44,7 +44,7 @@ var dateDecorator = (0, _makeDecorator.default)(function (columnName) {
var rawValue = date ? +new Date(date) : null;
if (rawValue && date) {
- cache.set(rawValue, date);
+ cache.set(rawValue, new Date(date));
}
this.asModel._setRaw(columnName, rawValue);

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,6 @@ module.exports = {
android: null android: null
} }
}, },
'@nozbe/watermelondb': {
platforms: {
android: null,
ios: null
}
},
'@react-native-firebase/app': { '@react-native-firebase/app': {
platforms: { platforms: {
android: null android: null

View File

@ -15,6 +15,7 @@ import './Avatar';
import '../../app/containers/BackgroundContainer/index.stories.js'; import '../../app/containers/BackgroundContainer/index.stories.js';
import '../../app/containers/RoomHeader/RoomHeader.stories.js'; import '../../app/containers/RoomHeader/RoomHeader.stories.js';
import '../../app/views/RoomView/LoadMore/LoadMore.stories'; import '../../app/views/RoomView/LoadMore/LoadMore.stories';
import '../../app/views/CannedResponsesListView/CannedResponseItem.stories';
import '../../app/containers/TextInput.stories'; import '../../app/containers/TextInput.stories';
// Change here to see themed storybook // Change here to see themed storybook

101
yarn.lock
View File

@ -1428,6 +1428,13 @@
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-transform-object-assign@^7.10.4":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.14.5.tgz#62537d54b6d85de04f4df48bfdba2eebff17b760"
integrity sha512-lvhjk4UN9xJJYB1mI5KC0/o1D5EcJXdbhVe+4fSk08D6ZN+iuAIs7LJC+71h8av9Ew4+uRq9452v9R93SFmQlQ==
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
"@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.14.5": "@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.14.5":
version "7.14.5" version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz#d0b5faeac9e98597a161a9cf78c527ed934cdc45" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz#d0b5faeac9e98597a161a9cf78c527ed934cdc45"
@ -1792,6 +1799,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.11.2":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.1": "@babel/runtime@^7.12.1":
version "7.14.0" version "7.14.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
@ -2840,23 +2854,37 @@
"@nodelib/fs.scandir" "2.1.5" "@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0" fastq "^1.6.0"
"@nozbe/simdjson@0.9.6-fix2":
version "0.9.6-fix2"
resolved "https://registry.yarnpkg.com/@nozbe/simdjson/-/simdjson-0.9.6-fix2.tgz#00d1c8ec76bfac25c022b07511c8fff4568b2973"
integrity sha512-xKzrhtH7elBUOOihtNwN4Jr0iVcI7+95NCzC2gLvBYkITiCYqqOUm+2badFMkWFEE9gKQLUJJaux4qtgPPItaQ==
"@nozbe/sqlite@3.31.1": "@nozbe/sqlite@3.31.1":
version "3.31.1" version "3.31.1"
resolved "https://registry.yarnpkg.com/@nozbe/sqlite/-/sqlite-3.31.1.tgz#ffd394ad7c188c6b73f89fd6e1ccb849a1b96dba" resolved "https://registry.yarnpkg.com/@nozbe/sqlite/-/sqlite-3.31.1.tgz#ffd394ad7c188c6b73f89fd6e1ccb849a1b96dba"
integrity sha512-z5+GdcHZl9OQ1g0pnygORAnwCYUlYw/gQxdW/8rS0HxD2Gnn/k3DBQOvqQIH4Z3Z3KWVMbGUYhcH1v4SqTAdwg== integrity sha512-z5+GdcHZl9OQ1g0pnygORAnwCYUlYw/gQxdW/8rS0HxD2Gnn/k3DBQOvqQIH4Z3Z3KWVMbGUYhcH1v4SqTAdwg==
"@nozbe/watermelondb@0.19.0": "@nozbe/watermelondb@0.23.0":
version "0.19.0" version "0.23.0"
resolved "https://registry.yarnpkg.com/@nozbe/watermelondb/-/watermelondb-0.19.0.tgz#70dae4bfca9dde8d61819a6092846e178a50e2f7" resolved "https://registry.yarnpkg.com/@nozbe/watermelondb/-/watermelondb-0.23.0.tgz#505b1dda6445c734708d1af0d82b10b7fcc58c07"
integrity sha512-x7sclLu/4RDmLzANIYQioKjWdmoIxgqYw7OJnS7UtWEtJGn28l5Z69SWojll/RM0X2KOEem0BIcjqM46CA53GA== integrity sha512-nQCQCZe9jthWlVPDI5WfOPc9lx3hI+sxG74vlyDH3NXgzjJiLEkVmpV+zF71U6VQLHsFRE7pGntys4SdsuEmzg==
dependencies: dependencies:
"@babel/runtime" "^7.11.2"
"@nozbe/simdjson" "0.9.6-fix2"
"@nozbe/sqlite" "3.31.1" "@nozbe/sqlite" "3.31.1"
lokijs "npm:@nozbe/lokijs@1.5.10-wmelon0" "@nozbe/with-observables" "1.4.0"
rambdax "2.15.0" hoist-non-react-statics "^3.3.2"
rxjs "^6.2.2" lokijs "npm:@nozbe/lokijs@1.5.12-wmelon2"
rxjs-compat "^6.3.2" rxjs "^6.5.3"
sql-escape-string "^1.1.0" sql-escape-string "^1.1.0"
"@nozbe/with-observables@1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@nozbe/with-observables/-/with-observables-1.4.0.tgz#38d91186c932d431b767302f9f31214335912c6a"
integrity sha512-vzc0QiYcXK/GmflBGBXTs02ayL25e1l4Cr9aYgSuYCZVqpyMfep9xp89RygNbiibAfVLbgEUa7thxlm3jw8hFw==
dependencies:
hoist-non-react-statics "^3.3.2"
"@reach/router@^1.2.1": "@reach/router@^1.2.1":
version "1.3.3" version "1.3.3"
resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.3.tgz#58162860dce6c9449d49be86b0561b5ef46d80db" resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.3.tgz#58162860dce6c9449d49be86b0561b5ef46d80db"
@ -8147,7 +8175,7 @@ fbjs-css-vars@^1.0.0:
resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8"
integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==
fbjs@1.0.0, fbjs@^1.0.0: fbjs@1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a"
integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==
@ -11212,10 +11240,10 @@ logkitty@^0.7.1:
dayjs "^1.8.15" dayjs "^1.8.15"
yargs "^15.1.0" yargs "^15.1.0"
"lokijs@npm:@nozbe/lokijs@1.5.10-wmelon0": "lokijs@npm:@nozbe/lokijs@1.5.12-wmelon2":
version "1.5.10-wmelon0" version "1.5.12-wmelon2"
resolved "https://registry.yarnpkg.com/@nozbe/lokijs/-/lokijs-1.5.10-wmelon0.tgz#dcad682083b85c238d14cdcd1f016125fff54336" resolved "https://registry.yarnpkg.com/@nozbe/lokijs/-/lokijs-1.5.12-wmelon2.tgz#31bc7b2d0c62edc1ccabd50ba795635d40e3e8c9"
integrity sha512-GSLyRUTxCPwWLLKxMxA8/Pujm2xAgPGSehze9ICHZDG9yyMMfuWYae+qY+fQctgA1HuKiwoRUEvrrKBCQj9jzA== integrity sha512-/YkZghPWKmyXgVpZ2MaIe3y/t/IYm/wQxXyEoi0G8JrjTrO9tlrAs2uiHUhbdY/2ZSPMLKbw1q9EqXZxA8ZqnQ==
long@~3: long@~3:
version "3.2.0" version "3.2.0"
@ -11904,6 +11932,11 @@ mocha@9.0.1:
yargs-parser "20.2.4" yargs-parser "20.2.4"
yargs-unparser "2.0.0" yargs-unparser "2.0.0"
mockdate@^3.0.2:
version "3.0.5"
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb"
integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==
moment@2.29.1, moment@^2.19.3, moment@^2.24.0: moment@2.29.1, moment@^2.19.3, moment@^2.24.0:
version "2.29.1" version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
@ -13394,11 +13427,6 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
rambdax@2.15.0:
version "2.15.0"
resolved "https://registry.yarnpkg.com/rambdax/-/rambdax-2.15.0.tgz#34fb481cea1a88e64a25e3a25e34a258fa18ca12"
integrity sha512-9ScXRMAcLaiist63yYDeDTRSIbL9DFY5yaIkoWyodaHS94PUL1ECQxQkxPP+h76oXG6bLo9b/ML+yvIRd78qeg==
ramda@^0.21.0: ramda@^0.21.0:
version "0.21.0" version "0.21.0"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35"
@ -13782,12 +13810,15 @@ react-native-prompt-android@^1.1.0:
resolved "https://registry.yarnpkg.com/react-native-prompt-android/-/react-native-prompt-android-1.1.0.tgz#3c5168029075cb9f72549fd5f92403372fb234e9" resolved "https://registry.yarnpkg.com/react-native-prompt-android/-/react-native-prompt-android-1.1.0.tgz#3c5168029075cb9f72549fd5f92403372fb234e9"
integrity sha512-4JoyEaT2ZnK9IH+tDFpbTiQBgva8UIFGQf4/Uw/tnEVWBERlVlzcs5B82T9BkeEhEqXhp89JaiSBnLWj30lciw== integrity sha512-4JoyEaT2ZnK9IH+tDFpbTiQBgva8UIFGQf4/Uw/tnEVWBERlVlzcs5B82T9BkeEhEqXhp89JaiSBnLWj30lciw==
react-native-reanimated@1.9.0: react-native-reanimated@2.2.2:
version "1.9.0" version "2.2.2"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-1.9.0.tgz#38676c99dd585504fdc7331efb45e5f48ec7339a" resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.2.2.tgz#8bc81c7ee93d599991507bb826050a5eeee1e7f2"
integrity sha512-Aj+spgIHRiVv7ezGADxnSH1EoKrQRD2+XaSiGY0MiB/pvRNNrZPSJ+3NVpvLwWf9lZMOP7dwqqyJIzoZgBDt8w== integrity sha512-Lfv4ogbNLU9x3DqhXUFza9pnzyTvPrw5xGC1wWA6aGXqZgyaikNLgC5dNWzxVbfEwRdOuLPFsai3LAcIM92TCg==
dependencies: dependencies:
fbjs "^1.0.0" "@babel/plugin-transform-object-assign" "^7.10.4"
fbjs "^3.0.0"
mockdate "^3.0.2"
string-hash-64 "^1.0.3"
react-native-redash@^12.0.3: react-native-redash@^12.0.3:
version "12.6.1" version "12.6.1"
@ -14599,18 +14630,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies: dependencies:
aproba "^1.1.1" aproba "^1.1.1"
rxjs-compat@^6.3.2:
version "6.5.5"
resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.5.5.tgz#073c40510f29c45a2a5fc02dde87f8c3ad75f2c2"
integrity sha512-F42sssVbUyWH4vJswEo6m+Eh02xHv3q93n8S7nUJO58R7sbc3CvJIOts605zdaBhWa1xMB9aVSyqPqhQ5q3eXg==
rxjs@^6.2.2, rxjs@^6.5.3:
version "6.5.5"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
dependencies:
tslib "^1.9.0"
rxjs@^6.4.0, rxjs@^6.6.7: rxjs@^6.4.0, rxjs@^6.6.7:
version "6.6.7" version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
@ -14618,6 +14637,13 @@ rxjs@^6.4.0, rxjs@^6.6.7:
dependencies: dependencies:
tslib "^1.9.0" tslib "^1.9.0"
rxjs@^6.5.3:
version "6.5.5"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.1: safe-buffer@5.1.1:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@ -15337,6 +15363,11 @@ string-argv@0.3.1:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-hash-64@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322"
integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==
string-length@^4.0.1: string-length@^4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"