Merge branch 'develop' into new.add-discusions-roomactionsview
This commit is contained in:
commit
f9dc2a975f
|
@ -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.
|
||||
|
||||
## 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
|
||||
### Share your story
|
||||
|
|
|
@ -1322,6 +1322,595 @@ exports[`Storyshots BackgroundContainer text 1`] = `
|
|||
</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`] = `
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
|
@ -40566,6 +41155,7 @@ exports[`Storyshots Message Show a button as attachment 1`] = `
|
|||
"color": "#ffffff",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
|
@ -260,7 +260,6 @@ android {
|
|||
|
||||
dependencies {
|
||||
addUnimodulesDependencies()
|
||||
implementation project(':watermelondb')
|
||||
implementation project(':@react-native-community_viewpager')
|
||||
playImplementation project(':reactnativenotifications')
|
||||
playImplementation project(':@react-native-firebase_app')
|
||||
|
|
|
@ -9,8 +9,9 @@ import com.facebook.react.ReactApplication;
|
|||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.nozbe.watermelondb.WatermelonDBPackage;
|
||||
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.ReactModuleRegistryProvider;
|
||||
|
@ -35,7 +36,6 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
protected List<ReactPackage> getPackages() {
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
List<ReactPackage> packages = new PackageList(this).getPackages();
|
||||
packages.add(new WatermelonDBPackage());
|
||||
packages.add(new RNCViewPagerPackage());
|
||||
packages.add(new SSLPinningPackage());
|
||||
List<ReactPackage> unimodules = Arrays.<ReactPackage>asList(
|
||||
|
@ -52,6 +52,11 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
return "index";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JSIModulePackage getJSIModulePackage() {
|
||||
return new ReanimatedJSIModulePackage(); // <- add
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable String getBundleAssetName() {
|
||||
return "app.bundle";
|
||||
|
|
|
@ -2,8 +2,6 @@ apply from: '../node_modules/react-native-unimodules/gradle.groovy'
|
|||
includeUnimodulesProjects()
|
||||
|
||||
rootProject.name = 'RocketChatRN'
|
||||
include ':watermelondb'
|
||||
project(':watermelondb').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android')
|
||||
include ':reactnativenotifications'
|
||||
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android/app')
|
||||
include ':@react-native-community_viewpager'
|
||||
|
|
|
@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
|||
|
||||
import Navigation from './lib/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
|
||||
import AuthLoadingView from './views/AuthLoadingView';
|
||||
// SetUsername Stack
|
||||
|
@ -56,9 +56,7 @@ const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail
|
|||
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
|
||||
<>
|
||||
{root === ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null}
|
||||
{root === ROOT_OUTSIDE || root === ROOT_NEW_SERVER ? (
|
||||
<Stack.Screen name='OutsideStack' component={OutsideStack} />
|
||||
) : null}
|
||||
{root === ROOT_OUTSIDE ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null}
|
||||
{root === ROOT_INSIDE && isMasterDetail ? (
|
||||
<Stack.Screen name='MasterDetailStack' component={MasterDetailStack} />
|
||||
) : null}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { APP } from './actionsTypes';
|
|||
export const ROOT_OUTSIDE = 'outside';
|
||||
export const ROOT_INSIDE = 'inside';
|
||||
export const ROOT_LOADING = 'loading';
|
||||
export const ROOT_NEW_SERVER = 'newServer';
|
||||
export const ROOT_SET_USERNAME = 'setUsername';
|
||||
|
||||
export function appStart({ root, ...args }) {
|
||||
|
|
|
@ -202,5 +202,8 @@ export default {
|
|||
},
|
||||
Jitsi_Enable_Channels: {
|
||||
type: 'valuesAsBoolean'
|
||||
},
|
||||
Canned_Responses_Enable: {
|
||||
type: 'valueAsBoolean'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Keyboard, Text } from 'react-native';
|
|||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { State, TapGestureHandler } from 'react-native-gesture-handler';
|
||||
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 { 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 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],
|
||||
outputRange: [0, themes[theme].backdropOpacity],
|
||||
extrapolate: Extrapolate.CLAMP
|
||||
});
|
||||
}) as any;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -17,6 +17,7 @@ interface IButtonProps {
|
|||
color: string;
|
||||
fontSize: any;
|
||||
style: any;
|
||||
styleText?: any;
|
||||
testID: string;
|
||||
}
|
||||
|
||||
|
@ -48,7 +49,8 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
|
|||
};
|
||||
|
||||
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';
|
||||
|
||||
let textColor = isPrimary ? themes[theme!].buttonText : themes[theme!].bodyText;
|
||||
|
@ -72,7 +74,7 @@ export default class Button extends React.PureComponent<Partial<IButtonProps>, a
|
|||
{loading ? (
|
||||
<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}
|
||||
</Text>
|
||||
)}
|
||||
|
|
|
@ -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;
|
|
@ -6,7 +6,7 @@ import Avatar from '../../Avatar';
|
|||
import MessageboxContext from '../Context';
|
||||
import FixedMentionItem from './FixedMentionItem';
|
||||
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 { IEmoji } from '../../EmojiPicker/interfaces';
|
||||
|
||||
|
@ -17,6 +17,8 @@ interface IMessageBoxMentionItem {
|
|||
username: string;
|
||||
t: string;
|
||||
id: string;
|
||||
shortcut: string;
|
||||
text: string;
|
||||
} & IEmoji;
|
||||
trackingType: string;
|
||||
theme: string;
|
||||
|
@ -32,6 +34,8 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
|
|||
return `mention-item-${item.name || item}`;
|
||||
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
||||
return `mention-item-${item.command || item}`;
|
||||
case MENTIONS_TRACKING_TYPE_CANNED:
|
||||
return `mention-item-${item.shortcut || item}`;
|
||||
default:
|
||||
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 (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
|
|
|
@ -2,18 +2,20 @@ import React from 'react';
|
|||
import { FlatList, View } from 'react-native';
|
||||
import { dequal } from 'dequal';
|
||||
|
||||
import MentionHeaderList from './MentionHeaderList';
|
||||
import styles from '../styles';
|
||||
import MentionItem from './MentionItem';
|
||||
import { themes } from '../../../constants/colors';
|
||||
|
||||
interface IMessageBoxMentions {
|
||||
mentions: [];
|
||||
mentions: any[];
|
||||
trackingType: string;
|
||||
theme: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const Mentions = React.memo(
|
||||
({ mentions, trackingType, theme }: IMessageBoxMentions) => {
|
||||
({ mentions, trackingType, theme, loading }: IMessageBoxMentions) => {
|
||||
if (!trackingType) {
|
||||
return null;
|
||||
}
|
||||
|
@ -21,16 +23,22 @@ const Mentions = React.memo(
|
|||
<View testID='messagebox-container'>
|
||||
<FlatList
|
||||
style={[styles.mentionList, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
ListHeaderComponent={() => (
|
||||
<MentionHeaderList trackingType={trackingType} hasMentions={mentions.length > 0} theme={theme} loading={loading} />
|
||||
)}
|
||||
data={mentions}
|
||||
extraData={mentions}
|
||||
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'
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
if (prevProps.loading !== nextProps.loading) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.theme !== nextProps.theme) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ interface IMessageBoxRecordAudioProps {
|
|||
onFinish: Function;
|
||||
}
|
||||
|
||||
const RECORDING_EXTENSION = '.aac';
|
||||
const RECORDING_EXTENSION = '.m4a';
|
||||
const RECORDING_SETTINGS = {
|
||||
android: {
|
||||
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,
|
||||
sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.sampleRate,
|
||||
numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.numberOfChannels,
|
||||
|
@ -39,7 +39,7 @@ const RECORDING_SETTINGS = {
|
|||
const RECORDING_MODE = {
|
||||
allowsRecordingIOS: true,
|
||||
playsInSilentModeIOS: true,
|
||||
staysActiveInBackground: false,
|
||||
staysActiveInBackground: true,
|
||||
shouldDuckAndroid: true,
|
||||
playThroughEarpieceAndroid: false,
|
||||
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
|
||||
|
@ -63,20 +63,24 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
|
||||
private recording: any;
|
||||
|
||||
private LastDuration: number;
|
||||
|
||||
constructor(props: IMessageBoxRecordAudioProps) {
|
||||
super(props);
|
||||
this.isRecorderBusy = false;
|
||||
this.LastDuration = 0;
|
||||
this.state = {
|
||||
isRecording: false,
|
||||
isRecorderActive: false,
|
||||
recordingDurationMillis: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { recordingCallback } = this.props;
|
||||
const { isRecording } = this.state;
|
||||
const { isRecorderActive } = this.state;
|
||||
|
||||
recordingCallback(isRecording);
|
||||
recordingCallback(isRecorderActive);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -90,6 +94,10 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
return formatTime(Math.floor(recordingDurationMillis / 1000));
|
||||
}
|
||||
|
||||
get GetLastDuration() {
|
||||
return formatTime(Math.floor(this.LastDuration / 1000));
|
||||
}
|
||||
|
||||
isRecordingPermissionGranted = async () => {
|
||||
try {
|
||||
const permission = await Audio.getPermissionsAsync();
|
||||
|
@ -108,17 +116,20 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
isRecording: status.isRecording,
|
||||
recordingDurationMillis: status.durationMillis
|
||||
});
|
||||
this.LastDuration = status.durationMillis;
|
||||
};
|
||||
|
||||
startRecordingAudio = async () => {
|
||||
logEvent(events.ROOM_AUDIO_RECORD);
|
||||
if (!this.isRecorderBusy) {
|
||||
this.isRecorderBusy = true;
|
||||
this.LastDuration = 0;
|
||||
try {
|
||||
const canRecord = await this.isRecordingPermissionGranted();
|
||||
if (canRecord) {
|
||||
await Audio.setAudioModeAsync(RECORDING_MODE);
|
||||
|
||||
this.setState({ isRecorderActive: true });
|
||||
this.recording = new Audio.Recording();
|
||||
await this.recording.prepareToRecordAsync(RECORDING_SETTINGS);
|
||||
this.recording.setOnRecordingStatusUpdate(this.onRecordingStatusUpdate);
|
||||
|
@ -147,7 +158,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
const fileURI = this.recording.getURI();
|
||||
const fileData = await getInfoAsync(fileURI);
|
||||
const fileInfo = {
|
||||
name: `${Date.now()}.aac`,
|
||||
name: `${Date.now()}.m4a`,
|
||||
mime: 'audio/aac',
|
||||
type: 'audio/aac',
|
||||
store: 'Uploads',
|
||||
|
@ -159,7 +170,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
} catch (error) {
|
||||
logEvent(events.ROOM_AUDIO_FINISH_F);
|
||||
}
|
||||
this.setState({ isRecording: false, recordingDurationMillis: 0 });
|
||||
this.setState({ isRecording: false, isRecorderActive: false, recordingDurationMillis: 0 });
|
||||
deactivateKeepAwake();
|
||||
this.isRecorderBusy = false;
|
||||
}
|
||||
|
@ -174,7 +185,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
} catch (error) {
|
||||
logEvent(events.ROOM_AUDIO_CANCEL_F);
|
||||
}
|
||||
this.setState({ isRecording: false, recordingDurationMillis: 0 });
|
||||
this.setState({ isRecording: false, isRecorderActive: false, recordingDurationMillis: 0 });
|
||||
deactivateKeepAwake();
|
||||
this.isRecorderBusy = false;
|
||||
}
|
||||
|
@ -182,9 +193,9 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
const { isRecording } = this.state;
|
||||
const { isRecording, isRecorderActive } = this.state;
|
||||
|
||||
if (!isRecording) {
|
||||
if (!isRecording && !isRecorderActive) {
|
||||
return (
|
||||
<BorderlessButton
|
||||
onPress={this.startRecordingAudio}
|
||||
|
@ -198,6 +209,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
);
|
||||
}
|
||||
|
||||
if (!isRecording && isRecorderActive) {
|
||||
return (
|
||||
<View style={styles.recordingContent}>
|
||||
<View style={styles.textArea}>
|
||||
|
@ -207,9 +219,9 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
accessibilityLabel={I18n.t('Cancel_recording')}
|
||||
accessibilityTraits='button'
|
||||
style={styles.actionButton}>
|
||||
<CustomIcon size={24} color={themes[theme].dangerColor} name='close' />
|
||||
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
|
||||
</BorderlessButton>
|
||||
<Text style={[styles.recordingCancelText, { color: themes[theme].titleText }]}>{this.duration}</Text>
|
||||
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.GetLastDuration}</Text>
|
||||
</View>
|
||||
<BorderlessButton
|
||||
onPress={this.finishRecordingAudio}
|
||||
|
@ -217,7 +229,33 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
|||
accessibilityLabel={I18n.t('Finish_recording')}
|
||||
accessibilityTraits='button'
|
||||
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>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -2,4 +2,5 @@ export const MENTIONS_TRACKING_TYPE_USERS = '@';
|
|||
export const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
|
||||
export const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
|
||||
export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
|
||||
export const MENTIONS_TRACKING_TYPE_CANNED = '!';
|
||||
export const MENTIONS_COUNT_TO_DISPLAY = 4;
|
||||
|
|
|
@ -35,6 +35,7 @@ import Mentions from './Mentions';
|
|||
import MessageboxContext from './Context';
|
||||
import {
|
||||
MENTIONS_COUNT_TO_DISPLAY,
|
||||
MENTIONS_TRACKING_TYPE_CANNED,
|
||||
MENTIONS_TRACKING_TYPE_COMMANDS,
|
||||
MENTIONS_TRACKING_TYPE_EMOJIS,
|
||||
MENTIONS_TRACKING_TYPE_ROOMS,
|
||||
|
@ -107,6 +108,7 @@ interface IMessageBoxProps {
|
|||
iOSScrollBehavior: number;
|
||||
sharing: boolean;
|
||||
isActionsEnabled: boolean;
|
||||
usedCannedResponse: string;
|
||||
}
|
||||
|
||||
interface IMessageBoxState {
|
||||
|
@ -121,6 +123,7 @@ interface IMessageBoxState {
|
|||
appId?: any;
|
||||
};
|
||||
tshow: boolean;
|
||||
mentionLoading: boolean;
|
||||
}
|
||||
|
||||
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||
|
@ -175,7 +178,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
commandPreview: [],
|
||||
showCommandPreview: false,
|
||||
command: {},
|
||||
tshow: false
|
||||
tshow: false,
|
||||
mentionLoading: false
|
||||
};
|
||||
this.text = '';
|
||||
this.selection = { start: 0, end: 0 };
|
||||
|
@ -234,7 +238,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
|
||||
async componentDidMount() {
|
||||
const db = database.active;
|
||||
const { rid, tmid, navigation, sharing } = this.props;
|
||||
const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props;
|
||||
let msg;
|
||||
try {
|
||||
const threadsCollection = db.get('threads');
|
||||
|
@ -269,6 +273,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
|
||||
}
|
||||
|
||||
if (isMasterDetail && usedCannedResponse) {
|
||||
this.onChangeText(usedCannedResponse);
|
||||
}
|
||||
|
||||
this.unsubscribeFocus = navigation.addListener('focus', () => {
|
||||
// didFocus
|
||||
// We should wait pushed views be dismissed
|
||||
|
@ -285,10 +293,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: any) {
|
||||
const { isFocused, editing, replying, sharing } = this.props;
|
||||
const { isFocused, editing, replying, sharing, usedCannedResponse } = this.props;
|
||||
if (!isFocused?.()) {
|
||||
return;
|
||||
}
|
||||
if (usedCannedResponse !== nextProps.usedCannedResponse) {
|
||||
this.onChangeText(nextProps.usedCannedResponse ?? '');
|
||||
}
|
||||
if (sharing) {
|
||||
this.setInput(nextProps.message.msg ?? '');
|
||||
return;
|
||||
|
@ -311,10 +322,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: any, nextState: any) {
|
||||
const { showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow } = this.state;
|
||||
|
||||
const { roomType, replying, editing, isFocused, message, theme } = this.props;
|
||||
const { showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow, mentionLoading, trackingType } = this.state;
|
||||
|
||||
const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
|
@ -333,6 +343,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.trackingType !== trackingType) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.mentionLoading !== mentionLoading) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.showSend !== showSend) {
|
||||
return true;
|
||||
}
|
||||
|
@ -351,6 +367,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (!dequal(nextProps.message?.id, message?.id)) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.usedCannedResponse !== usedCannedResponse) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -371,6 +390,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (this.getSlashCommands && this.getSlashCommands.stop) {
|
||||
this.getSlashCommands.stop();
|
||||
}
|
||||
if (this.getCannedResponses && this.getCannedResponses.stop) {
|
||||
this.getCannedResponses.stop();
|
||||
}
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
|
@ -395,7 +417,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
debouncedOnChangeText = debounce(async (text: any) => {
|
||||
const { sharing } = this.props;
|
||||
const { sharing, roomType } = this.props;
|
||||
const isTextEmpty = text.length === 0;
|
||||
if (isTextEmpty) {
|
||||
this.stopTrackingMention();
|
||||
|
@ -412,6 +434,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
const channelMention = lastWord.match(/^#/);
|
||||
const userMention = lastWord.match(/^@/);
|
||||
const emojiMention = lastWord.match(/^:/);
|
||||
const cannedMention = lastWord.match(/^!/);
|
||||
|
||||
if (commandMention && !sharing) {
|
||||
const command = text.substr(1);
|
||||
|
@ -440,6 +463,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (emojiMention) {
|
||||
return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_EMOJIS);
|
||||
}
|
||||
if (cannedMention && roomType === 'l') {
|
||||
return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_CANNED);
|
||||
}
|
||||
return this.stopTrackingMention();
|
||||
}, 100);
|
||||
|
||||
|
@ -456,11 +482,17 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
const { start, end } = this.selection;
|
||||
const cursor = Math.max(start, end);
|
||||
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 =
|
||||
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)}`;
|
||||
|
||||
if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS && item.providesPreview) {
|
||||
this.setState({ showCommandPreview: true });
|
||||
}
|
||||
|
@ -532,12 +564,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
getUsers = debounce(async (keyword: any) => {
|
||||
let res = await RocketChat.search({ text: keyword, filterRooms: false, filterUsers: true });
|
||||
res = [...this.getFixedMentions(keyword), ...res];
|
||||
this.setState({ mentions: res });
|
||||
this.setState({ mentions: res, mentionLoading: false });
|
||||
}, 300);
|
||||
|
||||
getRooms = debounce(async (keyword = '') => {
|
||||
const res = await RocketChat.search({ text: keyword, filterRooms: true, filterUsers: false });
|
||||
this.setState({ mentions: res });
|
||||
this.setState({ mentions: res, mentionLoading: false });
|
||||
}, 300);
|
||||
|
||||
getEmojis = debounce(async (keyword: any) => {
|
||||
|
@ -552,7 +584,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
customEmojis = customEmojis.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);
|
||||
this.setState({ mentions: mergedEmojis || [] });
|
||||
this.setState({ mentions: mergedEmojis || [], mentionLoading: false });
|
||||
}, 300);
|
||||
|
||||
getSlashCommands = debounce(async (keyword: any) => {
|
||||
|
@ -560,9 +592,14 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
const commandsCollection = db.get('slash_commands');
|
||||
const likeString = sanitizeLikeString(keyword);
|
||||
const commands = await commandsCollection.query(Q.where('id', Q.like(`${likeString}%`))).fetch();
|
||||
this.setState({ mentions: commands || [] });
|
||||
this.setState({ mentions: commands || [], mentionLoading: false });
|
||||
}, 300);
|
||||
|
||||
getCannedResponses = debounce(async (text?: string) => {
|
||||
const res = await RocketChat.getListCannedResponse({ text });
|
||||
this.setState({ mentions: res?.cannedResponses || [], mentionLoading: false });
|
||||
}, 500);
|
||||
|
||||
focus = () => {
|
||||
if (this.component && 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) => {
|
||||
const { message, replyCancel, replyWithMention } = this.props;
|
||||
// Start a thread with an attachment
|
||||
|
@ -854,6 +901,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
this.getEmojis(keyword);
|
||||
} else if (type === MENTIONS_TRACKING_TYPE_COMMANDS) {
|
||||
this.getSlashCommands(keyword);
|
||||
} else if (type === MENTIONS_TRACKING_TYPE_CANNED) {
|
||||
this.getCannedResponses(keyword);
|
||||
} else {
|
||||
this.getRooms(keyword);
|
||||
}
|
||||
|
@ -862,7 +911,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
identifyMentionKeyword = (keyword: any, type: string) => {
|
||||
this.setState({
|
||||
showEmojiKeyboard: false,
|
||||
trackingType: type
|
||||
trackingType: type,
|
||||
mentionLoading: true
|
||||
});
|
||||
this.updateMentions(keyword, type);
|
||||
};
|
||||
|
@ -918,7 +968,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
};
|
||||
|
||||
renderContent = () => {
|
||||
const { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview } = this.state;
|
||||
const { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview, mentionLoading } =
|
||||
this.state;
|
||||
const {
|
||||
editing,
|
||||
message,
|
||||
|
@ -950,8 +1001,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
const commandsPreviewAndMentions = !recording ? (
|
||||
<>
|
||||
<CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} />
|
||||
{/* @ts-ignore*/}
|
||||
<Mentions mentions={mentions} trackingType={trackingType} theme={theme} />
|
||||
<Mentions mentions={mentions} trackingType={trackingType} theme={theme} loading={mentionLoading} />
|
||||
</>
|
||||
) : null;
|
||||
|
||||
|
@ -1039,7 +1089,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
user,
|
||||
baseUrl,
|
||||
onPressMention: this.onPressMention,
|
||||
onPressCommandPreview: this.onPressCommandPreview
|
||||
onPressCommandPreview: this.onPressCommandPreview,
|
||||
onPressNoMatchCanned: this.onPressNoMatchCanned
|
||||
}}>
|
||||
<KeyboardAccessoryView
|
||||
ref={(ref: any) => (this.tracking = ref)}
|
||||
|
|
|
@ -36,6 +36,30 @@ export default StyleSheet.create({
|
|||
width: 60,
|
||||
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: {
|
||||
maxHeight: MENTION_HEIGHT * 4
|
||||
},
|
||||
|
@ -67,6 +91,18 @@ export default StyleSheet.create({
|
|||
fontSize: 14,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
cannedMentionText: {
|
||||
flex: 1,
|
||||
fontSize: 14,
|
||||
paddingRight: 12,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
cannedItem: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textBold,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 8
|
||||
},
|
||||
emojiKeyboardContainer: {
|
||||
flex: 1,
|
||||
borderTopWidth: StyleSheet.hairlineWidth
|
||||
|
@ -103,7 +139,8 @@ export default StyleSheet.create({
|
|||
flex: 1,
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
recordingCancelText: {
|
||||
recordingDurationText: {
|
||||
width: 60,
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
|
|
|
@ -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);
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
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 TextInput from '../presentation/TextInput';
|
||||
|
@ -95,9 +95,9 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
|||
get iconRight() {
|
||||
const { iconRight, onIconRightPress, theme } = this.props;
|
||||
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} />
|
||||
</BorderlessButton>
|
||||
</Touchable>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -105,14 +105,14 @@ export default class RCTextInput extends React.PureComponent<IRCTextInputProps,
|
|||
const { showPassword } = this.state;
|
||||
const { testID, theme } = this.props;
|
||||
return (
|
||||
<BorderlessButton onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}>
|
||||
<Touchable onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}>
|
||||
<CustomIcon
|
||||
name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'}
|
||||
testID={testID ? `${testID}-icon-right` : null}
|
||||
style={{ color: themes[theme].auxiliaryText }}
|
||||
size={20}
|
||||
/>
|
||||
</BorderlessButton>
|
||||
</Touchable>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,18 +12,19 @@ interface IInput {
|
|||
onPress: Function;
|
||||
theme: string;
|
||||
inputStyle: object;
|
||||
disabled: boolean;
|
||||
placeholder: string;
|
||||
loading: boolean;
|
||||
disabled?: boolean | object;
|
||||
placeholder?: string;
|
||||
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
|
||||
onPress={onPress}
|
||||
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
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}
|
||||
{loading ? (
|
||||
<ActivityIndicator style={[styles.loading, styles.icon]} />
|
||||
|
|
|
@ -28,6 +28,7 @@ interface IMultiSelect {
|
|||
value?: any[];
|
||||
disabled?: boolean | object;
|
||||
theme: string;
|
||||
innerInputStyle?: object;
|
||||
}
|
||||
|
||||
const ANIMATION_DURATION = 200;
|
||||
|
@ -53,7 +54,8 @@ export const MultiSelect = React.memo(
|
|||
onClose = () => {},
|
||||
disabled,
|
||||
inputStyle,
|
||||
theme
|
||||
theme,
|
||||
innerInputStyle
|
||||
}: IMultiSelect) => {
|
||||
const [selected, select] = useState<any>(Array.isArray(values) ? values : []);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
@ -143,8 +145,13 @@ export const MultiSelect = React.memo(
|
|||
let button = multiselect ? (
|
||||
<Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} theme={theme} />
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Input onPress={onShow} theme={theme} loading={loading} disabled={disabled} inputStyle={inputStyle}>
|
||||
<Input
|
||||
onPress={onShow}
|
||||
theme={theme}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
inputStyle={inputStyle}
|
||||
innerInputStyle={innerInputStyle}>
|
||||
<Text style={[styles.pickerText, { color: currentValue ? themes[theme].titleText : themes[theme].auxiliaryText }]}>
|
||||
{currentValue || placeholder.text}
|
||||
</Text>
|
||||
|
@ -154,8 +161,13 @@ export const MultiSelect = React.memo(
|
|||
if (context === BLOCK_CONTEXT.FORM) {
|
||||
const items: any = options.filter((option: any) => selected.includes(option.value));
|
||||
button = (
|
||||
// @ts-ignore
|
||||
<Input onPress={onShow} theme={theme} loading={loading} disabled={disabled} inputStyle={inputStyle}>
|
||||
<Input
|
||||
onPress={onShow}
|
||||
theme={theme}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
inputStyle={inputStyle}
|
||||
innerInputStyle={innerInputStyle}>
|
||||
{items.length ? (
|
||||
<Chips items={items} onSelect={(item: any) => (disabled ? {} : onSelect(item))} theme={theme} />
|
||||
) : (
|
||||
|
|
|
@ -43,7 +43,7 @@ interface IMessageAudioState {
|
|||
const mode = {
|
||||
allowsRecordingIOS: false,
|
||||
playsInSilentModeIOS: true,
|
||||
staysActiveInBackground: false,
|
||||
staysActiveInBackground: true,
|
||||
shouldDuckAndroid: true,
|
||||
playThroughEarpieceAndroid: false,
|
||||
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
|
||||
|
|
|
@ -773,5 +773,14 @@
|
|||
"Converting_Team_To_Channel": "Converting Team to Channel",
|
||||
"Select_Team_Channels_To_Delete": "Select the Team’s 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",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -672,5 +672,13 @@
|
|||
"Left_The_Room_Successfully": "Saiu da sala com sucesso",
|
||||
"Deleted_The_Team_Successfully": "Time deletado 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"
|
||||
}
|
||||
|
|
|
@ -60,8 +60,7 @@ export const getDatabase = (database = '') => {
|
|||
Permission,
|
||||
SlashCommand,
|
||||
User
|
||||
],
|
||||
actionsEnabled: true
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -73,8 +72,7 @@ class DB {
|
|||
schema: serversSchema,
|
||||
migrations: serversMigrations
|
||||
}),
|
||||
modelClasses: [Server, LoggedUser, ServersHistory],
|
||||
actionsEnabled: true
|
||||
modelClasses: [Server, LoggedUser, ServersHistory]
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -117,8 +115,7 @@ class DB {
|
|||
FrequentlyUsedEmoji,
|
||||
Setting,
|
||||
User
|
||||
],
|
||||
actionsEnabled: true
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,8 @@ const PERMISSIONS = [
|
|||
'view-all-team-channels',
|
||||
'convert-team',
|
||||
'edit-omnichannel-contact',
|
||||
'edit-livechat-room-customfields'
|
||||
'edit-livechat-room-customfields',
|
||||
'view-canned-responses'
|
||||
];
|
||||
|
||||
export async function setPermissions() {
|
||||
|
|
|
@ -1049,7 +1049,7 @@ const RocketChat = {
|
|||
}
|
||||
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;
|
||||
if (compareServerVersion(serverVersion, '3.16.0', methods.greaterThanOrEqualTo)) {
|
||||
const params = {
|
||||
|
@ -1060,10 +1060,12 @@ const RocketChat = {
|
|||
...(filter && { filter })
|
||||
};
|
||||
// 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
|
||||
return this.methodCallWrapper('getUsersOfRoom', rid, allUsers, { skip, limit });
|
||||
const result = await this.methodCallWrapper('getUsersOfRoom', rid, allUsers, { skip, limit });
|
||||
return result?.records;
|
||||
},
|
||||
methodCallWrapper(method, ...params) {
|
||||
const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
|
||||
|
@ -1171,6 +1173,19 @@ const RocketChat = {
|
|||
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) {
|
||||
const { id: userId } = reduxStore.getState().login.user;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
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 { themes } from '../../constants/colors';
|
||||
|
@ -94,7 +94,7 @@ function runTiming(clock: any, value: any, dest: any, startStopClock: any = true
|
|||
const config = {
|
||||
toValue: new Value(0),
|
||||
duration: 300,
|
||||
easing: Easing.inOut(Easing.cubic)
|
||||
easing: EasingNode.inOut(EasingNode.cubic)
|
||||
};
|
||||
|
||||
return [
|
||||
|
@ -215,7 +215,9 @@ class Image extends React.PureComponent<IImageProps, any> {
|
|||
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
|
||||
// and changed to use FastImage animated component
|
||||
|
|
|
@ -7,7 +7,6 @@ const initialState = {
|
|||
server: '',
|
||||
version: null,
|
||||
loading: true,
|
||||
adding: false,
|
||||
previousServer: null,
|
||||
changingServer: false
|
||||
};
|
||||
|
@ -58,13 +57,11 @@ export default function server(state = initialState, action) {
|
|||
case SERVER.INIT_ADD:
|
||||
return {
|
||||
...state,
|
||||
adding: true,
|
||||
previousServer: action.previousServer
|
||||
};
|
||||
case SERVER.FINISH_ADD:
|
||||
return {
|
||||
...state,
|
||||
adding: false,
|
||||
previousServer: null
|
||||
};
|
||||
default:
|
||||
|
|
|
@ -8,7 +8,7 @@ import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'
|
|||
import database from '../lib/database';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
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 { goRoom } from '../utils/goRoom';
|
||||
import { loginRequest } from '../actions/login';
|
||||
|
@ -180,7 +180,7 @@ const handleOpen = function* handleOpen({ params }) {
|
|||
yield fallbackNavigation();
|
||||
return;
|
||||
}
|
||||
yield put(appStart({ root: ROOT_NEW_SERVER }));
|
||||
yield put(appStart({ root: ROOT_OUTSIDE }));
|
||||
yield put(serverInitAdd(server));
|
||||
yield delay(1000);
|
||||
EventEmitter.emit('NewServer', { server: host });
|
||||
|
|
|
@ -118,8 +118,6 @@ const fetchRooms = function* fetchRooms() {
|
|||
|
||||
const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||
try {
|
||||
const adding = yield select(state => state.server.adding);
|
||||
|
||||
RocketChat.getUserPresence(user.id);
|
||||
|
||||
const server = yield select(getServer);
|
||||
|
@ -170,25 +168,11 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
yield put(setUser(user));
|
||||
EventEmitter.emit('connected');
|
||||
|
||||
let currentRoot;
|
||||
if (adding) {
|
||||
yield put(serverFinishAdd());
|
||||
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);
|
||||
if (inviteLinkToken) {
|
||||
yield put(inviteLinksRequest(inviteLinkToken));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ import ThreadMessagesView from '../views/ThreadMessagesView';
|
|||
import TeamChannelsView from '../views/TeamChannelsView';
|
||||
import MarkdownTableView from '../views/MarkdownTableView';
|
||||
import ReadReceiptsView from '../views/ReadReceiptView';
|
||||
import CannedResponsesListView from '../views/CannedResponsesListView';
|
||||
import CannedResponseDetail from '../views/CannedResponseDetail';
|
||||
import { themes } from '../constants/colors';
|
||||
// Profile Stack
|
||||
import ProfileView from '../views/ProfileView';
|
||||
|
@ -138,6 +140,16 @@ const ChatsStackNavigator = () => {
|
|||
<ChatsStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} />
|
||||
<ChatsStack.Screen name='ReadReceiptsView' component={ReadReceiptsView} options={ReadReceiptsView.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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,6 +24,8 @@ import DirectoryView from '../../views/DirectoryView';
|
|||
import NotificationPrefView from '../../views/NotificationPreferencesView';
|
||||
import VisitorNavigationView from '../../views/VisitorNavigationView';
|
||||
import ForwardLivechatView from '../../views/ForwardLivechatView';
|
||||
import CannedResponsesListView from '../../views/CannedResponsesListView';
|
||||
import CannedResponseDetail from '../../views/CannedResponseDetail';
|
||||
import LivechatEditView from '../../views/LivechatEditView';
|
||||
import PickerView from '../../views/PickerView';
|
||||
import ThreadMessagesView from '../../views/ThreadMessagesView';
|
||||
|
@ -160,6 +162,16 @@ const ModalStackNavigator = React.memo(({ navigation }) => {
|
|||
component={ForwardLivechatView}
|
||||
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='PickerView' component={PickerView} options={PickerView.navigationOptions} />
|
||||
<ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} />
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { ThemeContext } from '../theme';
|
||||
import { ModalAnimation, StackAnimation, defaultHeader, themedHeader } from '../utils/navigation';
|
||||
|
||||
// Outside Stack
|
||||
import OnboardingView from '../views/OnboardingView';
|
||||
import NewServerView from '../views/NewServerView';
|
||||
import WorkspaceView from '../views/WorkspaceView';
|
||||
import LoginView from '../views/LoginView';
|
||||
|
@ -15,18 +13,14 @@ import ForgotPasswordView from '../views/ForgotPasswordView';
|
|||
import RegisterView from '../views/RegisterView';
|
||||
import LegalView from '../views/LegalView';
|
||||
import AuthenticationWebView from '../views/AuthenticationWebView';
|
||||
import { ROOT_OUTSIDE } from '../actions/app';
|
||||
|
||||
// Outside
|
||||
const Outside = createStackNavigator();
|
||||
const _OutsideStack = ({ root }) => {
|
||||
const _OutsideStack = () => {
|
||||
const { theme } = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<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='WorkspaceView' component={WorkspaceView} options={WorkspaceView.navigationOptions} />
|
||||
<Outside.Screen name='LoginView' component={LoginView} options={LoginView.navigationOptions} />
|
||||
|
@ -41,10 +35,6 @@ const mapStateToProps = state => ({
|
|||
root: state.app.root
|
||||
});
|
||||
|
||||
_OutsideStack.propTypes = {
|
||||
root: PropTypes.string
|
||||
};
|
||||
|
||||
const OutsideStack = connect(mapStateToProps)(_OutsideStack);
|
||||
|
||||
// OutsideStackModal
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
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
|
||||
NS_CONNECT_TO_WORKSPACE: 'ns_connect_to_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_FAVORITE: 'rl_group_channels_by_favorite',
|
||||
RL_GROUP_CHANNELS_BY_UNREAD: 'rl_group_channels_by_unread',
|
||||
RL_CREATE_NEW_WORKSPACE: 'rl_create_new_workspace',
|
||||
|
||||
// QUEUE LIST VIEW
|
||||
QL_GO_ROOM: 'ql_go_room',
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { Dimensions } from 'react-native';
|
||||
|
||||
import { isTablet } from './deviceInfo';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
|
||||
const guidelineBaseWidth = isTablet ? 600 : 375;
|
||||
const guidelineBaseHeight = isTablet ? 800 : 667;
|
||||
|
||||
const scale = size => (width / guidelineBaseWidth) * size;
|
||||
const verticalScale = size => (height / guidelineBaseHeight) * size;
|
||||
const moderateScale = (size, factor = 0.5) => size + (scale(size) - size) * factor;
|
||||
// TODO: we need to refactor this
|
||||
const scale = (size, width) => (width / guidelineBaseWidth) * size;
|
||||
const verticalScale = (size, height) => (height / guidelineBaseHeight) * size;
|
||||
const moderateScale = (size, factor = 0.5, width) => size + (scale(size, width) - size) * factor;
|
||||
|
||||
export { scale, verticalScale, moderateScale };
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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')}
|
||||
/>
|
||||
</>
|
||||
));
|
|
@ -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);
|
|
@ -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;
|
|
@ -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;
|
|
@ -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));
|
|
@ -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;
|
|
@ -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
|
||||
}
|
||||
});
|
|
@ -10,9 +10,7 @@ import Item from './Item';
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
zIndex: 1,
|
||||
marginTop: 24,
|
||||
marginBottom: 32
|
||||
zIndex: 1
|
||||
},
|
||||
inputContainer: {
|
||||
marginTop: 0,
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import React from 'react';
|
||||
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 { Base64 } from 'js-base64';
|
||||
import parse from 'url-parse';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import { TouchableOpacity } from 'react-native-gesture-handler';
|
||||
import Orientation from 'react-native-orientation-locker';
|
||||
|
||||
import UserPreferences from '../../lib/userPreferences';
|
||||
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 sharedStyles from '../Styles';
|
||||
import Button from '../../containers/Button';
|
||||
|
@ -27,31 +28,38 @@ import database from '../../lib/database';
|
|||
import { sanitizeLikeString } from '../../lib/database/utils';
|
||||
import SSLPinning from '../../utils/sslPinning';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import { isTablet } from '../../utils/deviceInfo';
|
||||
import { verticalScale, moderateScale } from '../../utils/scaling';
|
||||
import { withDimensions } from '../../dimensions';
|
||||
import ServerInput from './ServerInput';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
onboardingImage: {
|
||||
alignSelf: 'center',
|
||||
resizeMode: 'contain'
|
||||
},
|
||||
title: {
|
||||
...sharedStyles.textBold,
|
||||
fontSize: 22
|
||||
letterSpacing: 0,
|
||||
alignSelf: 'center'
|
||||
},
|
||||
subtitle: {
|
||||
...sharedStyles.textRegular,
|
||||
alignSelf: 'center'
|
||||
},
|
||||
certificatePicker: {
|
||||
marginBottom: 32,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
chooseCertificateTitle: {
|
||||
fontSize: 13,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
chooseCertificate: {
|
||||
fontSize: 13,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
description: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 14,
|
||||
textAlign: 'left',
|
||||
marginBottom: 24
|
||||
textAlign: 'center'
|
||||
},
|
||||
connectButton: {
|
||||
marginBottom: 0
|
||||
|
@ -59,23 +67,22 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class NewServerView extends React.Component {
|
||||
static navigationOptions = () => ({
|
||||
title: I18n.t('Workspaces')
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
connecting: PropTypes.bool.isRequired,
|
||||
connectServer: PropTypes.func.isRequired,
|
||||
selectServer: PropTypes.func.isRequired,
|
||||
adding: PropTypes.bool,
|
||||
previousServer: PropTypes.string,
|
||||
inviteLinksClear: PropTypes.func
|
||||
inviteLinksClear: PropTypes.func,
|
||||
serverFinishAdd: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (!isTablet) {
|
||||
Orientation.lockToPortrait();
|
||||
}
|
||||
this.setHeader();
|
||||
|
||||
this.state = {
|
||||
|
@ -92,25 +99,27 @@ class NewServerView extends React.Component {
|
|||
this.queryServerHistory();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { adding } = this.props;
|
||||
if (prevProps.adding !== adding) {
|
||||
this.setHeader();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.removeListener('NewServer', this.handleNewServerEvent);
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
const { previousServer, serverFinishAdd } = this.props;
|
||||
if (previousServer) {
|
||||
serverFinishAdd();
|
||||
}
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { adding, navigation } = this.props;
|
||||
if (adding) {
|
||||
navigation.setOptions({
|
||||
const { previousServer, navigation } = this.props;
|
||||
if (previousServer) {
|
||||
return navigation.setOptions({
|
||||
headerTitle: I18n.t('Workspaces'),
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} onPress={this.close} testID='new-server-view-close' />
|
||||
});
|
||||
}
|
||||
|
||||
return navigation.setOptions({
|
||||
headerShown: false
|
||||
});
|
||||
};
|
||||
|
||||
handleBackPress = () => {
|
||||
|
@ -273,16 +282,26 @@ class NewServerView extends React.Component {
|
|||
|
||||
renderCertificatePicker = () => {
|
||||
const { certificate } = this.state;
|
||||
const { theme } = this.props;
|
||||
const { theme, width, height, previousServer } = this.props;
|
||||
return (
|
||||
<View style={styles.certificatePicker}>
|
||||
<Text style={[styles.chooseCertificateTitle, { color: themes[theme].auxiliaryText }]}>
|
||||
<View
|
||||
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')}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={certificate ? this.handleRemove : this.chooseCertificate}
|
||||
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')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
@ -291,12 +310,48 @@ class NewServerView extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { connecting, theme } = this.props;
|
||||
const { connecting, theme, previousServer, width, height } = this.props;
|
||||
const { text, connectingOpen, serversHistory } = this.state;
|
||||
const marginTop = previousServer ? 0 : 35;
|
||||
|
||||
return (
|
||||
<FormContainer theme={theme} testID='new-server-view' keyboardShouldPersistTaps='never'>
|
||||
<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
|
||||
text={text}
|
||||
theme={theme}
|
||||
|
@ -312,12 +367,20 @@ class NewServerView extends React.Component {
|
|||
onPress={this.submit}
|
||||
disabled={!text || connecting}
|
||||
loading={!connectingOpen && connecting}
|
||||
style={styles.connectButton}
|
||||
style={[styles.connectButton, { marginTop: verticalScale(16, height) }]}
|
||||
theme={theme}
|
||||
testID='new-server-view-button'
|
||||
/>
|
||||
<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')}
|
||||
</Text>
|
||||
<Button
|
||||
|
@ -339,14 +402,14 @@ class NewServerView extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
connecting: state.server.connecting,
|
||||
adding: state.server.adding,
|
||||
previousServer: state.server.previousServer
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
connectServer: (...params) => dispatch(serverRequest(...params)),
|
||||
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)));
|
||||
|
|
|
@ -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);
|
|
@ -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)
|
||||
}
|
||||
});
|
|
@ -64,7 +64,8 @@ class RoomActionsView extends React.Component {
|
|||
transferLivechatGuestPermission: PropTypes.array,
|
||||
createTeamPermission: PropTypes.array,
|
||||
addTeamChannelPermission: PropTypes.array,
|
||||
convertTeamPermission: PropTypes.array
|
||||
convertTeamPermission: PropTypes.array,
|
||||
viewCannedResponsesPermission: PropTypes.array
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -74,6 +75,7 @@ class RoomActionsView extends React.Component {
|
|||
const member = props.route.params?.member;
|
||||
this.rid = props.route.params?.rid;
|
||||
this.t = props.route.params?.t;
|
||||
this.joined = props.route.params?.joined;
|
||||
this.state = {
|
||||
room: room || { rid: this.rid, t: this.t },
|
||||
membersCount: 0,
|
||||
|
@ -89,7 +91,8 @@ class RoomActionsView extends React.Component {
|
|||
canToggleEncryption: false,
|
||||
canCreateTeam: false,
|
||||
canAddChannelToTeam: false,
|
||||
canConvertTeam: false
|
||||
canConvertTeam: false,
|
||||
canViewCannedResponse: false
|
||||
};
|
||||
if (room && room.observe && room.rid) {
|
||||
this.roomObservable = room.observe();
|
||||
|
@ -157,7 +160,8 @@ class RoomActionsView extends React.Component {
|
|||
if (room.t === 'l') {
|
||||
const canForwardGuest = await this.canForwardGuest();
|
||||
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() {
|
||||
const { room } = this.state;
|
||||
return room.t === 'l' && room.status === 'queued';
|
||||
return room.t === 'l' && room.status === 'queued' && !this.joined;
|
||||
}
|
||||
|
||||
onPressTouchable = item => {
|
||||
|
@ -294,6 +298,14 @@ class RoomActionsView extends React.Component {
|
|||
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 () => {
|
||||
try {
|
||||
const { returnQueue } = await RocketChat.getRoutingConfig();
|
||||
|
@ -927,7 +939,8 @@ class RoomActionsView extends React.Component {
|
|||
joined,
|
||||
canAutoTranslate,
|
||||
canForwardGuest,
|
||||
canReturnQueue
|
||||
canReturnQueue,
|
||||
canViewCannedResponse
|
||||
} = this.state;
|
||||
const { rid, t, prid } = room;
|
||||
const isGroupChat = RocketChat.isGroupChat(room);
|
||||
|
@ -1145,6 +1158,18 @@ class RoomActionsView extends React.Component {
|
|||
{this.teamChannelActions(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 ? (
|
||||
<>
|
||||
<List.Item
|
||||
|
@ -1236,7 +1261,8 @@ const mapStateToProps = state => ({
|
|||
transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'],
|
||||
createTeamPermission: state.permissions['create-team'],
|
||||
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 => ({
|
||||
|
|
|
@ -434,7 +434,6 @@ class RoomMembersView extends React.Component {
|
|||
fetchMembers = async () => {
|
||||
const { rid, members, isLoading, allUsers, end, room, filtering } = this.state;
|
||||
const { t } = room;
|
||||
let newMembers;
|
||||
if (isLoading || end) {
|
||||
return;
|
||||
}
|
||||
|
@ -447,13 +446,13 @@ class RoomMembersView extends React.Component {
|
|||
type: allUsers ? 'all' : 'online',
|
||||
filter: filtering,
|
||||
skip: members.length,
|
||||
limit: PAGE_SIZE
|
||||
limit: PAGE_SIZE,
|
||||
allUsers
|
||||
});
|
||||
newMembers = membersResult.members;
|
||||
this.setState({
|
||||
members: members.concat(newMembers || []),
|
||||
members: members.concat(membersResult || []),
|
||||
isLoading: false,
|
||||
end: newMembers.length < PAGE_SIZE
|
||||
end: membersResult?.length < PAGE_SIZE
|
||||
});
|
||||
this.setHeader();
|
||||
} catch (e) {
|
||||
|
|
|
@ -433,7 +433,7 @@ class RoomView extends React.Component {
|
|||
|
||||
goRoomActionsView = screen => {
|
||||
logEvent(events.ROOM_GO_RA);
|
||||
const { room, member } = this.state;
|
||||
const { room, member, joined } = this.state;
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
if (isMasterDetail) {
|
||||
navigation.navigate('ModalStackNavigator', {
|
||||
|
@ -443,7 +443,8 @@ class RoomView extends React.Component {
|
|||
t: this.t,
|
||||
room,
|
||||
member,
|
||||
showCloseModal: !!screen
|
||||
showCloseModal: !!screen,
|
||||
joined
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -451,7 +452,8 @@ class RoomView extends React.Component {
|
|||
rid: this.rid,
|
||||
t: this.t,
|
||||
room,
|
||||
member
|
||||
member,
|
||||
joined
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -1057,8 +1059,10 @@ class RoomView extends React.Component {
|
|||
};
|
||||
|
||||
renderFooter = () => {
|
||||
const { joined, room, selectedMessage, editing, replying, replyWithMention, readOnly } = this.state;
|
||||
const { navigation, theme } = this.props;
|
||||
const { joined, room, selectedMessage, editing, replying, replyWithMention, readOnly, loading } = this.state;
|
||||
const { navigation, theme, route } = this.props;
|
||||
|
||||
const usedCannedResponse = route?.params?.usedCannedResponse;
|
||||
|
||||
if (!this.rid) {
|
||||
return null;
|
||||
|
@ -1074,6 +1078,7 @@ class RoomView extends React.Component {
|
|||
<Touch
|
||||
onPress={this.joinRoom}
|
||||
style={[styles.joinRoomButton, { backgroundColor: themes[theme].actionTintColor }]}
|
||||
enabled={!loading}
|
||||
theme={theme}>
|
||||
<Text style={[styles.joinRoomText, { color: themes[theme].buttonText }]} testID='room-view-join-button'>
|
||||
{I18n.t(this.isOmnichannel ? 'Take_it' : 'Join')}
|
||||
|
@ -1118,6 +1123,7 @@ class RoomView extends React.Component {
|
|||
replyCancel={this.onReplyCancel}
|
||||
getCustomEmoji={this.getCustomEmoji}
|
||||
navigation={navigation}
|
||||
usedCannedResponse={usedCannedResponse}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
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 { batch, connect } from 'react-redux';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import * as List from '../../containers/List';
|
||||
import Button from '../../containers/Button';
|
||||
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
|
||||
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 I18n from '../../i18n';
|
||||
import EventEmitter from '../../utils/events';
|
||||
|
@ -19,7 +20,7 @@ import { KEY_COMMAND, handleCommandSelectServer } from '../../commands';
|
|||
import { isTablet } from '../../utils/deviceInfo';
|
||||
import { localAuthenticate } from '../../utils/localAuthentication';
|
||||
import { showConfirmationAlert } from '../../utils/info';
|
||||
import { events, logEvent } from '../../utils/log';
|
||||
import log, { events, logEvent } from '../../utils/log';
|
||||
import { headerHeight } from '../../containers/Header';
|
||||
import { goRoom } from '../../utils/goRoom';
|
||||
import UserPreferences from '../../lib/userPreferences';
|
||||
|
@ -97,10 +98,19 @@ class ServerDropdown extends Component {
|
|||
}).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 => {
|
||||
const { appStart, initAdd } = this.props;
|
||||
batch(() => {
|
||||
appStart({ root: ROOT_NEW_SERVER });
|
||||
appStart({ root: ROOT_OUTSIDE });
|
||||
initAdd(previousServer);
|
||||
});
|
||||
};
|
||||
|
@ -181,7 +191,7 @@ class ServerDropdown extends Component {
|
|||
const { servers } = this.state;
|
||||
const { theme, isMasterDetail, insets } = this.props;
|
||||
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 heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
|
||||
const translateY = this.animatedValue.interpolate({
|
||||
|
@ -230,6 +240,17 @@ class ServerDropdown extends Component {
|
|||
ItemSeparatorComponent={List.Separator}
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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 Orientation from 'react-native-orientation-locker';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
@ -19,12 +19,13 @@ import {
|
|||
roomsRequest as roomsRequestAction,
|
||||
toggleSortDropdown as toggleSortDropdownAction
|
||||
} from '../../actions/rooms';
|
||||
import { appStart as appStartAction, ROOT_OUTSIDE } from '../../actions/app';
|
||||
import debounce from '../../utils/debounce';
|
||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
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 { withTheme } from '../../theme';
|
||||
import { themes } from '../../constants/colors';
|
||||
|
@ -133,7 +134,8 @@ class RoomsListView extends React.Component {
|
|||
insets: PropTypes.object,
|
||||
queueSize: PropTypes.number,
|
||||
inquiryEnabled: PropTypes.bool,
|
||||
encryptionBanner: PropTypes.string
|
||||
encryptionBanner: PropTypes.string,
|
||||
initAdd: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -763,7 +765,7 @@ class RoomsListView extends React.Component {
|
|||
};
|
||||
|
||||
handleCommands = ({ event }) => {
|
||||
const { navigation, server, isMasterDetail } = this.props;
|
||||
const { navigation, server, isMasterDetail, appStart, initAdd } = this.props;
|
||||
const { input } = event;
|
||||
if (handleCommandShowPreferences(event)) {
|
||||
navigation.navigate('SettingsView');
|
||||
|
@ -782,7 +784,10 @@ class RoomsListView extends React.Component {
|
|||
navigation.navigate('NewMessageStack');
|
||||
}
|
||||
} 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()),
|
||||
roomsRequest: params => dispatch(roomsRequestAction(params)),
|
||||
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))));
|
||||
|
|
|
@ -56,5 +56,10 @@ export default StyleSheet.create({
|
|||
marginRight: 12,
|
||||
paddingVertical: 10,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
buttonCreateWorkspace: {
|
||||
height: 46,
|
||||
justifyContent: 'center',
|
||||
marginBottom: 0
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import { Q } from '@nozbe/watermelondb';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { connect } from 'react-redux';
|
||||
import { HeaderBackButton } from '@react-navigation/stack';
|
||||
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import RoomHeader from '../containers/RoomHeader';
|
||||
|
@ -16,6 +17,7 @@ import * as HeaderButton from '../containers/HeaderButton';
|
|||
import BackgroundContainer from '../containers/BackgroundContainer';
|
||||
import SafeAreaView from '../containers/SafeAreaView';
|
||||
import ActivityIndicator from '../containers/ActivityIndicator';
|
||||
import SearchHeader from '../containers/SearchHeader';
|
||||
import RoomItem, { ROW_HEIGHT } from '../presentation/RoomItem';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import { withDimensions } from '../dimensions';
|
||||
|
@ -28,7 +30,6 @@ import { withActionSheet } from '../containers/ActionSheet';
|
|||
import { deleteRoom as deleteRoomAction } from '../actions/room';
|
||||
import { CustomIcon } from '../lib/Icons';
|
||||
import { themes } from '../constants/colors';
|
||||
import SearchHeader from '../containers/SearchHeader';
|
||||
|
||||
const API_FETCH_COUNT = 25;
|
||||
const PERMISSION_DELETE_C = 'delete-c';
|
||||
|
@ -157,9 +158,7 @@ class TeamChannelsView extends React.Component {
|
|||
|
||||
setHeader = () => {
|
||||
const { isSearching, showCreate, data } = this.state;
|
||||
const {
|
||||
navigation, isMasterDetail, insets, theme
|
||||
} = this.props;
|
||||
const { navigation, isMasterDetail, insets, theme } = this.props;
|
||||
|
||||
const { team } = this;
|
||||
if (!team) {
|
||||
|
@ -169,7 +168,7 @@ class TeamChannelsView extends React.Component {
|
|||
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 });
|
||||
|
||||
if (isSearching) {
|
||||
return {
|
||||
const options = {
|
||||
headerTitleAlign: 'left',
|
||||
headerLeft: () => (
|
||||
<HeaderButton.Container left>
|
||||
|
@ -190,6 +189,7 @@ class TeamChannelsView extends React.Component {
|
|||
},
|
||||
headerRight: () => null
|
||||
};
|
||||
return navigation.setOptions(options);
|
||||
}
|
||||
|
||||
const options = {
|
||||
|
@ -199,6 +199,9 @@ class TeamChannelsView extends React.Component {
|
|||
left: headerTitlePosition.left,
|
||||
right: headerTitlePosition.right
|
||||
},
|
||||
headerLeft: () => (
|
||||
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
|
||||
),
|
||||
headerTitle: () => (
|
||||
<RoomHeader
|
||||
title={RocketChat.getRoomTitle(team)}
|
||||
|
|
|
@ -96,7 +96,6 @@ class WorkspaceView extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
server: state.server.server,
|
||||
adding: state.server.adding,
|
||||
Site_Name: state.settings.Site_Name,
|
||||
Site_Url: state.settings.Site_Url,
|
||||
Assets_favicon_512: state.settings.Assets_favicon_512,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
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: {
|
||||
production: {
|
||||
plugins: ['transform-remove-console']
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
const data = require('../data');
|
||||
|
||||
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')))
|
||||
.toBeVisible()
|
||||
.withTimeout(60000);
|
||||
|
@ -16,9 +12,6 @@ async function navigateToWorkspace(server = data.server) {
|
|||
}
|
||||
|
||||
async function navigateToLogin(server) {
|
||||
await waitFor(element(by.id('onboarding-view')))
|
||||
.toBeVisible()
|
||||
.withTimeout(20000);
|
||||
await navigateToWorkspace(server);
|
||||
await element(by.id('workspace-view-login')).tap();
|
||||
await waitFor(element(by.id('login-view')))
|
||||
|
@ -28,9 +21,6 @@ async function navigateToLogin(server) {
|
|||
}
|
||||
|
||||
async function navigateToRegister(server) {
|
||||
await waitFor(element(by.id('onboarding-view')))
|
||||
.toBeVisible()
|
||||
.withTimeout(20000);
|
||||
await navigateToWorkspace(server);
|
||||
await element(by.id('workspace-view-register')).tap();
|
||||
await waitFor(element(by.id('register-view')))
|
||||
|
@ -70,10 +60,10 @@ async function logout() {
|
|||
.withTimeout(10000);
|
||||
await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist();
|
||||
await element(by.text('Logout')).tap();
|
||||
await waitFor(element(by.id('onboarding-view')))
|
||||
await waitFor(element(by.id('new-server-view')))
|
||||
.toBeVisible()
|
||||
.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) {
|
||||
|
|
|
@ -5,7 +5,7 @@ const reopenAndCheckServer = async server => {
|
|||
await device.launchApp({ permissions: { notifications: 'YES' } });
|
||||
await waitFor(element(by.id('rooms-list-view')))
|
||||
.toBeVisible()
|
||||
.withTimeout(6000);
|
||||
.withTimeout(10000);
|
||||
await checkServer(server);
|
||||
};
|
||||
|
||||
|
@ -19,13 +19,21 @@ describe('Change server', () => {
|
|||
.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 waitFor(element(by.id('rooms-list-header-server-dropdown')))
|
||||
.toBeVisible()
|
||||
.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')))
|
||||
.toBeVisible()
|
||||
.withTimeout(6000);
|
||||
|
|
|
@ -37,11 +37,10 @@ describe('i18n', () => {
|
|||
},
|
||||
delete: true
|
||||
});
|
||||
await waitFor(element(by.id('onboarding-view')))
|
||||
await waitFor(element(by.id('new-server-view')))
|
||||
.toBeVisible()
|
||||
.withTimeout(20000);
|
||||
await expect(element(by.id('join-workspace').and(by.label('Join a workspace')))).toBeVisible();
|
||||
await expect(element(by.id('create-workspace-button').and(by.label('Create a new workspace')))).toBeVisible();
|
||||
await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible();
|
||||
});
|
||||
|
||||
it("OS set to unavailable language and fallback to 'en'", async () => {
|
||||
|
@ -52,11 +51,10 @@ describe('i18n', () => {
|
|||
locale: 'es-MX'
|
||||
}
|
||||
});
|
||||
await waitFor(element(by.id('onboarding-view')))
|
||||
await waitFor(element(by.id('new-server-view')))
|
||||
.toBeVisible()
|
||||
.withTimeout(20000);
|
||||
await expect(element(by.id('join-workspace').and(by.label('Join a workspace')))).toBeVisible();
|
||||
await expect(element(by.id('create-workspace-button').and(by.label('Create a new workspace')))).toBeVisible();
|
||||
await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible();
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -71,9 +69,6 @@ describe('i18n', () => {
|
|||
// 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();
|
||||
// });
|
||||
});
|
||||
|
||||
|
|
|
@ -3,22 +3,18 @@ const data = require('../../data');
|
|||
describe('Onboarding', () => {
|
||||
before(async () => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await waitFor(element(by.id('onboarding-view')))
|
||||
await waitFor(element(by.id('new-server-view')))
|
||||
.toBeVisible()
|
||||
.withTimeout(20000);
|
||||
});
|
||||
|
||||
describe('Render', () => {
|
||||
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 () => {
|
||||
await expect(element(by.id('join-workspace'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have "Create a new workspace"', async () => {
|
||||
await expect(element(by.id('create-workspace-button'))).toBeVisible();
|
||||
it('should have "Join our open workspace"', async () => {
|
||||
await expect(element(by.id('new-server-view-open'))).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -27,13 +23,6 @@ describe('Onboarding', () => {
|
|||
// // 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 () => {
|
||||
await element(by.id('new-server-view-input')).typeText('invalidtest\n');
|
||||
const errorText = 'Oops!';
|
||||
|
@ -52,13 +41,9 @@ describe('Onboarding', () => {
|
|||
|
||||
it('should enter a valid server without login services and navigate to login', async () => {
|
||||
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')))
|
||||
.toBeVisible()
|
||||
.withTimeout(60000);
|
||||
.withTimeout(5000);
|
||||
await element(by.id('new-server-view-input')).typeText(`${data.server}\n`);
|
||||
await waitFor(element(by.id('workspace-view')))
|
||||
.toBeVisible()
|
||||
|
|
|
@ -11,7 +11,6 @@ describe('Server history', () => {
|
|||
await navigateToLogin();
|
||||
await login(data.users.regular.username, data.users.regular.password);
|
||||
await logout();
|
||||
await element(by.id('join-workspace')).tap();
|
||||
await waitFor(element(by.id('new-server-view')))
|
||||
.toBeVisible()
|
||||
.withTimeout(60000);
|
||||
|
|
|
@ -7,3 +7,10 @@
|
|||
#import <react-native-simple-crypto/Aes.h>
|
||||
#import <react-native-simple-crypto/Rsa.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"
|
|
@ -4,6 +4,9 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ
|
|||
require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
|
||||
|
||||
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!
|
||||
use_unimodules!
|
||||
use_react_native!(
|
||||
|
|
|
@ -541,7 +541,7 @@ PODS:
|
|||
- TOCropViewController
|
||||
- RNLocalize (2.1.1):
|
||||
- React-Core
|
||||
- RNReanimated (2.2.0):
|
||||
- RNReanimated (2.2.2):
|
||||
- DoubleConversion
|
||||
- FBLazyVector
|
||||
- FBReactNativeSpec
|
||||
|
@ -582,6 +582,7 @@ PODS:
|
|||
- SDWebImageWebPCoder (0.4.1):
|
||||
- libwebp (~> 1.0)
|
||||
- SDWebImage/Core (~> 5.5)
|
||||
- simdjson (0.9.6-fix2)
|
||||
- TOCropViewController (2.5.3)
|
||||
- UMAppLoader (1.2.0)
|
||||
- UMBarCodeScannerInterface (5.2.1)
|
||||
|
@ -600,6 +601,9 @@ PODS:
|
|||
- UMFontInterface
|
||||
- UMSensorsInterface (5.2.1)
|
||||
- UMTaskManagerInterface (5.2.1)
|
||||
- WatermelonDB (0.23.0):
|
||||
- React
|
||||
- React-jsi
|
||||
- Yoga (1.14.0)
|
||||
- YogaKit (1.18.1):
|
||||
- Yoga (~> 1.14)
|
||||
|
@ -708,6 +712,7 @@ DEPENDENCIES:
|
|||
- RNRootView (from `../node_modules/rn-root-view`)
|
||||
- RNScreens (from `../node_modules/react-native-screens`)
|
||||
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
||||
- "simdjson (from `../node_modules/@nozbe/simdjson`)"
|
||||
- UMAppLoader (from `../node_modules/unimodules-app-loader/ios`)
|
||||
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
|
||||
- UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`)
|
||||
|
@ -721,6 +726,7 @@ DEPENDENCIES:
|
|||
- "UMReactNativeAdapter (from `../node_modules/@unimodules/react-native-adapter/ios`)"
|
||||
- UMSensorsInterface (from `../node_modules/unimodules-sensors-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`)
|
||||
|
||||
SPEC REPOS:
|
||||
|
@ -916,6 +922,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native-screens"
|
||||
RNVectorIcons:
|
||||
:path: "../node_modules/react-native-vector-icons"
|
||||
simdjson:
|
||||
:path: "../node_modules/@nozbe/simdjson"
|
||||
UMAppLoader:
|
||||
:path: "../node_modules/unimodules-app-loader/ios"
|
||||
UMBarCodeScannerInterface:
|
||||
|
@ -942,6 +950,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/unimodules-sensors-interface/ios"
|
||||
UMTaskManagerInterface:
|
||||
:path: "../node_modules/unimodules-task-manager-interface/ios"
|
||||
WatermelonDB:
|
||||
:path: "../node_modules/@nozbe/watermelondb"
|
||||
Yoga:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
|
@ -1053,12 +1063,13 @@ SPEC CHECKSUMS:
|
|||
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
|
||||
RNImageCropPicker: 38865ab4af1b0b2146ad66061196bc0184946855
|
||||
RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b
|
||||
RNReanimated: 9c13c86454bfd54dab7505c1a054470bfecd2563
|
||||
RNReanimated: 241c586663f44f19a53883c63375fdd041253960
|
||||
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
|
||||
RNScreens: c526239bbe0e957b988dacc8d75ac94ec9cb19da
|
||||
RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4
|
||||
SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9
|
||||
SDWebImageWebPCoder: 36f8f47bd9879a8aea6044765c1351120fd8e3a8
|
||||
simdjson: 85016870cd17207312b718ef6652eb6a1cd6a2b0
|
||||
TOCropViewController: 20a14b6a7a098308bf369e7c8d700dc983a974e6
|
||||
UMAppLoader: 61049c8d55590b74e9ae1d5429bf68d96b4a2528
|
||||
UMBarCodeScannerInterface: e5e4c87797d3d01214e25cd1618866caf5d4f17f
|
||||
|
@ -1073,9 +1084,10 @@ SPEC CHECKSUMS:
|
|||
UMReactNativeAdapter: 538efe92e781b5d7678cf95b34c46f2d0989a557
|
||||
UMSensorsInterface: cb5bf31d52c4349f0ff9e3c049bbe4df0d80d383
|
||||
UMTaskManagerInterface: 80653f25c55d9e6d79d6a0a65589fa213feaee11
|
||||
WatermelonDB: 577c61fceff16e9f9103b59d14aee4850c0307b6
|
||||
Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac
|
||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||
|
||||
PODFILE CHECKSUM: 2cb6f38193d69310b4dfc466be3cf4ff7a5e98d3
|
||||
PODFILE CHECKSUM: 46fb1ed324f44252f8900c63e7c93e4391658bad
|
||||
|
||||
COCOAPODS: 1.10.1
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
1E068CFF24FD2DC700A0FFC1 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E068CFD24FD2DC700A0FFC1 /* AppGroup.swift */; };
|
||||
1E068D0124FD2E0500A0FFC1 /* 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 */; };
|
||||
1E1EA80A2326CD2200E22452 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1EA8092326CD2200E22452 /* AVFoundation.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 */; };
|
||||
1E51D962251263CD00DC95DE /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E51D961251263CD00DC95DE /* MessageType.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 */; };
|
||||
1E598AE725150660002BDFBD /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE625150660002BDFBD /* Data+Extensions.swift */; };
|
||||
1E598AE925151A63002BDFBD /* SendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E598AE825151A63002BDFBD /* SendMessage.swift */; };
|
||||
|
@ -158,13 +156,6 @@
|
|||
remoteGlobalIDString = 1EFEB5942493B6640072EDC0;
|
||||
remoteInfo = NotificationService;
|
||||
};
|
||||
7AAA749A23043AD300F1ADE9 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 290E43E48AD8418287FA99D6 /* WatermelonDB.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 6E660D5E213BCCD300189354;
|
||||
remoteInfo = WatermelonDB;
|
||||
};
|
||||
7AAB3E0F257E6A6E00707CF6 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
|
@ -265,7 +256,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
@ -321,7 +311,6 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1E55FDB32320675C0048D2F9 /* libWatermelonDB.a in Frameworks */,
|
||||
1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */,
|
||||
DC5529EC3B4695AC7319279F /* libPods-defaults-ShareRocketChatRN.a in Frameworks */,
|
||||
);
|
||||
|
@ -331,7 +320,6 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1E1C2F7E250FACB5005DCE7D /* libWatermelonDB.a in Frameworks */,
|
||||
EDE8A8F63A3ADB93A6640F8E /* libPods-defaults-NotificationService.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -503,18 +491,9 @@
|
|||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7AAA749723043AD300F1ADE9 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AAA749B23043AD300F1ADE9 /* libWatermelonDB.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
290E43E48AD8418287FA99D6 /* WatermelonDB.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
@ -747,12 +726,6 @@
|
|||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = 7AAA749723043AD300F1ADE9 /* Products */;
|
||||
ProjectRef = 290E43E48AD8418287FA99D6 /* WatermelonDB.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* RocketChatRN */,
|
||||
|
@ -763,16 +736,6 @@
|
|||
};
|
||||
/* 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 */
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
|
|
|
@ -103,6 +103,7 @@
|
|||
</dict>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>voip</string>
|
||||
</array>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"dependencies": {
|
||||
"@bugsnag/react-native": "^7.10.5",
|
||||
"@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/async-storage": "1.12.1",
|
||||
"@react-native-community/blur": "^3.6.0",
|
||||
|
@ -104,7 +104,7 @@
|
|||
"react-native-popover-view": "4.0.1",
|
||||
"react-native-progress": "4.1.2",
|
||||
"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-safe-area-context": "3.2.0",
|
||||
"react-native-screens": "2.9.0",
|
||||
|
|
|
@ -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
|
||||
index 43f2c9c..e24a24f 100644
|
||||
index b4d7151..429e318 100644
|
||||
--- a/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 SQLite3
|
||||
|
||||
-class Database {
|
||||
- typealias SQL = String
|
||||
|
@ -22,7 +45,7 @@ index 43f2c9c..e24a24f 100644
|
|||
self.path = path
|
||||
fmdb = FMDatabase(path: path)
|
||||
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)
|
||||
|
||||
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
|
@ -5,12 +5,6 @@ module.exports = {
|
|||
android: null
|
||||
}
|
||||
},
|
||||
'@nozbe/watermelondb': {
|
||||
platforms: {
|
||||
android: null,
|
||||
ios: null
|
||||
}
|
||||
},
|
||||
'@react-native-firebase/app': {
|
||||
platforms: {
|
||||
android: null
|
||||
|
|
|
@ -15,6 +15,7 @@ import './Avatar';
|
|||
import '../../app/containers/BackgroundContainer/index.stories.js';
|
||||
import '../../app/containers/RoomHeader/RoomHeader.stories.js';
|
||||
import '../../app/views/RoomView/LoadMore/LoadMore.stories';
|
||||
import '../../app/views/CannedResponsesListView/CannedResponseItem.stories';
|
||||
import '../../app/containers/TextInput.stories';
|
||||
|
||||
// Change here to see themed storybook
|
||||
|
|
101
yarn.lock
101
yarn.lock
|
@ -1428,6 +1428,13 @@
|
|||
dependencies:
|
||||
"@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":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz#d0b5faeac9e98597a161a9cf78c527ed934cdc45"
|
||||
|
@ -1792,6 +1799,13 @@
|
|||
dependencies:
|
||||
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":
|
||||
version "7.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
|
||||
|
@ -2840,23 +2854,37 @@
|
|||
"@nodelib/fs.scandir" "2.1.5"
|
||||
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":
|
||||
version "3.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@nozbe/sqlite/-/sqlite-3.31.1.tgz#ffd394ad7c188c6b73f89fd6e1ccb849a1b96dba"
|
||||
integrity sha512-z5+GdcHZl9OQ1g0pnygORAnwCYUlYw/gQxdW/8rS0HxD2Gnn/k3DBQOvqQIH4Z3Z3KWVMbGUYhcH1v4SqTAdwg==
|
||||
|
||||
"@nozbe/watermelondb@0.19.0":
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@nozbe/watermelondb/-/watermelondb-0.19.0.tgz#70dae4bfca9dde8d61819a6092846e178a50e2f7"
|
||||
integrity sha512-x7sclLu/4RDmLzANIYQioKjWdmoIxgqYw7OJnS7UtWEtJGn28l5Z69SWojll/RM0X2KOEem0BIcjqM46CA53GA==
|
||||
"@nozbe/watermelondb@0.23.0":
|
||||
version "0.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@nozbe/watermelondb/-/watermelondb-0.23.0.tgz#505b1dda6445c734708d1af0d82b10b7fcc58c07"
|
||||
integrity sha512-nQCQCZe9jthWlVPDI5WfOPc9lx3hI+sxG74vlyDH3NXgzjJiLEkVmpV+zF71U6VQLHsFRE7pGntys4SdsuEmzg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.11.2"
|
||||
"@nozbe/simdjson" "0.9.6-fix2"
|
||||
"@nozbe/sqlite" "3.31.1"
|
||||
lokijs "npm:@nozbe/lokijs@1.5.10-wmelon0"
|
||||
rambdax "2.15.0"
|
||||
rxjs "^6.2.2"
|
||||
rxjs-compat "^6.3.2"
|
||||
"@nozbe/with-observables" "1.4.0"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
lokijs "npm:@nozbe/lokijs@1.5.12-wmelon2"
|
||||
rxjs "^6.5.3"
|
||||
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":
|
||||
version "1.3.3"
|
||||
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"
|
||||
integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==
|
||||
|
||||
fbjs@1.0.0, fbjs@^1.0.0:
|
||||
fbjs@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a"
|
||||
integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==
|
||||
|
@ -11212,10 +11240,10 @@ logkitty@^0.7.1:
|
|||
dayjs "^1.8.15"
|
||||
yargs "^15.1.0"
|
||||
|
||||
"lokijs@npm:@nozbe/lokijs@1.5.10-wmelon0":
|
||||
version "1.5.10-wmelon0"
|
||||
resolved "https://registry.yarnpkg.com/@nozbe/lokijs/-/lokijs-1.5.10-wmelon0.tgz#dcad682083b85c238d14cdcd1f016125fff54336"
|
||||
integrity sha512-GSLyRUTxCPwWLLKxMxA8/Pujm2xAgPGSehze9ICHZDG9yyMMfuWYae+qY+fQctgA1HuKiwoRUEvrrKBCQj9jzA==
|
||||
"lokijs@npm:@nozbe/lokijs@1.5.12-wmelon2":
|
||||
version "1.5.12-wmelon2"
|
||||
resolved "https://registry.yarnpkg.com/@nozbe/lokijs/-/lokijs-1.5.12-wmelon2.tgz#31bc7b2d0c62edc1ccabd50ba795635d40e3e8c9"
|
||||
integrity sha512-/YkZghPWKmyXgVpZ2MaIe3y/t/IYm/wQxXyEoi0G8JrjTrO9tlrAs2uiHUhbdY/2ZSPMLKbw1q9EqXZxA8ZqnQ==
|
||||
|
||||
long@~3:
|
||||
version "3.2.0"
|
||||
|
@ -11904,6 +11932,11 @@ mocha@9.0.1:
|
|||
yargs-parser "20.2.4"
|
||||
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:
|
||||
version "2.29.1"
|
||||
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"
|
||||
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:
|
||||
version "0.21.0"
|
||||
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"
|
||||
integrity sha512-4JoyEaT2ZnK9IH+tDFpbTiQBgva8UIFGQf4/Uw/tnEVWBERlVlzcs5B82T9BkeEhEqXhp89JaiSBnLWj30lciw==
|
||||
|
||||
react-native-reanimated@1.9.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-1.9.0.tgz#38676c99dd585504fdc7331efb45e5f48ec7339a"
|
||||
integrity sha512-Aj+spgIHRiVv7ezGADxnSH1EoKrQRD2+XaSiGY0MiB/pvRNNrZPSJ+3NVpvLwWf9lZMOP7dwqqyJIzoZgBDt8w==
|
||||
react-native-reanimated@2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.2.2.tgz#8bc81c7ee93d599991507bb826050a5eeee1e7f2"
|
||||
integrity sha512-Lfv4ogbNLU9x3DqhXUFza9pnzyTvPrw5xGC1wWA6aGXqZgyaikNLgC5dNWzxVbfEwRdOuLPFsai3LAcIM92TCg==
|
||||
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:
|
||||
version "12.6.1"
|
||||
|
@ -14599,18 +14630,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
|
|||
dependencies:
|
||||
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:
|
||||
version "6.6.7"
|
||||
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:
|
||||
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:
|
||||
version "5.1.1"
|
||||
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"
|
||||
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:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
|
||||
|
|
Loading…
Reference in New Issue