Update dependencies (#431)
* Update dependencies * Lint and test * Added react-native fork * rn 57 * Lint and tests updated * Update xcode on circleci * Use legacy build system * Update tests
2
.babelrc
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"presets": ["react-native"],
|
"presets": ["module:metro-react-native-babel-preset"],
|
||||||
"plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]],
|
"plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]],
|
||||||
"env": {
|
"env": {
|
||||||
"production": {
|
"production": {
|
||||||
|
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
|
|
||||||
e2e-test:
|
e2e-test:
|
||||||
macos:
|
macos:
|
||||||
xcode: "9.0"
|
xcode: "10.0.0"
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
BASH_ENV: "~/.nvm/nvm.sh"
|
BASH_ENV: "~/.nvm/nvm.sh"
|
||||||
|
@ -87,7 +87,8 @@ jobs:
|
||||||
- image: circleci/android:api-27-node8-alpha
|
- image: circleci/android:api-27-node8-alpha
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"
|
# GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"
|
||||||
|
GRADLE_OPTS: -Xmx2048m -Dorg.gradle.daemon=false
|
||||||
JVM_OPTS: -Xmx4096m
|
JVM_OPTS: -Xmx4096m
|
||||||
TERM: dumb
|
TERM: dumb
|
||||||
BASH_ENV: "~/.nvm/nvm.sh"
|
BASH_ENV: "~/.nvm/nvm.sh"
|
||||||
|
@ -170,7 +171,7 @@ jobs:
|
||||||
|
|
||||||
ios-build:
|
ios-build:
|
||||||
macos:
|
macos:
|
||||||
xcode: "9.0"
|
xcode: "10.0.0"
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
BASH_ENV: "~/.nvm/nvm.sh"
|
BASH_ENV: "~/.nvm/nvm.sh"
|
||||||
|
@ -236,7 +237,7 @@ jobs:
|
||||||
|
|
||||||
ios-testflight:
|
ios-testflight:
|
||||||
macos:
|
macos:
|
||||||
xcode: "9.0"
|
xcode: "10.0.0"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
|
|
@ -13,7 +13,8 @@ module.exports = {
|
||||||
"ecmaVersion": 2017,
|
"ecmaVersion": 2017,
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"experimentalObjectRestSpread" : true,
|
"experimentalObjectRestSpread" : true,
|
||||||
"jsx": true
|
"jsx": true,
|
||||||
|
"legacyDecorators": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
@ -65,6 +66,7 @@ module.exports = {
|
||||||
"no-dupe-args": 2,
|
"no-dupe-args": 2,
|
||||||
"no-dupe-class-members": 2,
|
"no-dupe-class-members": 2,
|
||||||
"no-duplicate-case": 2,
|
"no-duplicate-case": 2,
|
||||||
|
"no-else-return": [0, {allowElseIf: true}],
|
||||||
"no-empty": 2,
|
"no-empty": 2,
|
||||||
"no-empty-character-class": 2,
|
"no-empty-character-class": 2,
|
||||||
"no-ex-assign": 2,
|
"no-ex-assign": 2,
|
||||||
|
@ -125,7 +127,8 @@ module.exports = {
|
||||||
"object-shorthand": 2,
|
"object-shorthand": 2,
|
||||||
"consistent-return": 0,
|
"consistent-return": 0,
|
||||||
"global-require": "off",
|
"global-require": "off",
|
||||||
"react-native/no-unused-styles": 2
|
"react-native/no-unused-styles": 2,
|
||||||
|
"react/jsx-one-expression-per-line": 0
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"__DEV__": true
|
"__DEV__": true
|
||||||
|
|
|
@ -4,7 +4,6 @@ exports[`render channel 1`] = `
|
||||||
<View>
|
<View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="general, last message Nov 10"
|
accessibilityLabel="general, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -200,7 +199,6 @@ exports[`render no icon 1`] = `
|
||||||
<View>
|
<View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="name, last message Nov 10"
|
accessibilityLabel="name, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -396,7 +394,6 @@ exports[`render private group 1`] = `
|
||||||
<View>
|
<View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="private-group, last message Nov 10"
|
accessibilityLabel="private-group, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -593,7 +590,6 @@ exports[`render unread +999 1`] = `
|
||||||
<View>
|
<View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="name, 1000 alerts, last message Nov 10"
|
accessibilityLabel="name, 1000 alerts, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -856,7 +852,6 @@ exports[`render unread 1`] = `
|
||||||
<View>
|
<View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="name, 1 alert, last message Nov 10"
|
accessibilityLabel="name, 1 alert, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -1119,7 +1114,6 @@ exports[`renders correctly 1`] = `
|
||||||
<View>
|
<View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="name, last message Nov 10"
|
accessibilityLabel="name, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
|
|
@ -300,7 +300,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
<View>
|
<View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="rocket.cat, last message Nov 10"
|
accessibilityLabel="rocket.cat, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -532,7 +531,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="rocket.cat, last message Nov 10"
|
accessibilityLabel="rocket.cat, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -768,7 +766,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="rocket.cat, 1 alert, last message Nov 10"
|
accessibilityLabel="rocket.cat, 1 alert, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -1026,7 +1023,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 9 alerts, last message Nov 10"
|
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 9 alerts, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -1288,7 +1284,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 99 alerts, last message Nov 10"
|
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 99 alerts, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -1546,7 +1541,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100 alerts, last message Nov 10"
|
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100 alerts, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -1804,7 +1798,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100000 alerts, last message Nov 10"
|
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100000 alerts, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -2062,7 +2055,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100000 alerts, you were mentioned, last message Nov 10"
|
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100000 alerts, you were mentioned, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -2320,7 +2312,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="W, last message Nov 10"
|
accessibilityLabel="W, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -2552,7 +2543,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel="WW, last message Nov 10"
|
accessibilityLabel="WW, last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
@ -2784,7 +2774,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
accessibilityLabel=", last message Nov 10"
|
accessibilityLabel=", last message Nov 10"
|
||||||
accessibilityTraits="selected"
|
|
||||||
accessible={true}
|
accessible={true}
|
||||||
isTVSelectable={true}
|
isTVSelectable={true}
|
||||||
onResponderGrant={[Function]}
|
onResponderGrant={[Function]}
|
||||||
|
|
|
@ -198,24 +198,23 @@ dependencies {
|
||||||
implementation project(':react-native-audio')
|
implementation project(':react-native-audio')
|
||||||
implementation project(":reactnativekeyboardinput")
|
implementation project(":reactnativekeyboardinput")
|
||||||
implementation project(':react-native-video')
|
implementation project(':react-native-video')
|
||||||
implementation project(':react-native-svg')
|
|
||||||
implementation project(':react-native-vector-icons')
|
implementation project(':react-native-vector-icons')
|
||||||
implementation project(':rn-fetch-blob')
|
implementation project(':rn-fetch-blob')
|
||||||
implementation project(':react-native-zeroconf')
|
|
||||||
implementation project(':@remobile/react-native-toast')
|
implementation project(':@remobile/react-native-toast')
|
||||||
implementation project(':react-native-fast-image')
|
implementation project(':react-native-fast-image')
|
||||||
implementation project(':realm')
|
implementation project(':realm')
|
||||||
implementation project(':react-native-navigation')
|
implementation project(':react-native-navigation')
|
||||||
implementation project(':reactnativenotifications')
|
implementation project(':reactnativenotifications')
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
implementation "com.android.support:appcompat-v7:27.1.0"
|
implementation "com.android.support:appcompat-v7:27.1.1"
|
||||||
implementation "com.android.support:support-v4:27.1.+"
|
implementation "com.android.support:support-v4:27.1.1"
|
||||||
implementation 'com.android.support:customtabs:27.1.0'
|
implementation 'com.android.support:customtabs:27.1.1'
|
||||||
implementation 'com.android.support:design:27.1.0'
|
implementation 'com.android.support:design:27.1.1'
|
||||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||||
implementation 'com.facebook.fresco:animated-gif:1.9.0'
|
implementation 'com.facebook.fresco:fresco:1.10.0'
|
||||||
implementation 'com.facebook.fresco:animated-webp:1.9.0'
|
implementation 'com.facebook.fresco:animated-gif:1.10.0'
|
||||||
implementation 'com.facebook.fresco:webpsupport:1.9.0'
|
implementation 'com.facebook.fresco:animated-webp:1.10.0'
|
||||||
|
implementation 'com.facebook.fresco:webpsupport:1.10.0'
|
||||||
implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') {
|
implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') {
|
||||||
transitive = true;
|
transitive = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,11 @@ import android.os.Bundle;
|
||||||
import com.AlexanderZaytsev.RNI18n.RNI18nPackage;
|
import com.AlexanderZaytsev.RNI18n.RNI18nPackage;
|
||||||
import com.reactnative.ivpusic.imagepicker.PickerPackage;
|
import com.reactnative.ivpusic.imagepicker.PickerPackage;
|
||||||
import com.RNFetchBlob.RNFetchBlobPackage;
|
import com.RNFetchBlob.RNFetchBlobPackage;
|
||||||
import com.balthazargronon.RCTZeroconf.ZeroconfReactPackage;
|
|
||||||
import com.brentvatne.react.ReactVideoPackage;
|
import com.brentvatne.react.ReactVideoPackage;
|
||||||
import com.crashlytics.android.Crashlytics;
|
import com.crashlytics.android.Crashlytics;
|
||||||
import com.dylanvann.fastimage.FastImageViewPackage;
|
import com.dylanvann.fastimage.FastImageViewPackage;
|
||||||
import com.facebook.react.ReactPackage;
|
import com.facebook.react.ReactPackage;
|
||||||
import com.facebook.react.shell.MainReactPackage;
|
import com.facebook.react.shell.MainReactPackage;
|
||||||
import com.horcrux.svg.SvgPackage;
|
|
||||||
import com.oblador.vectoricons.VectorIconsPackage;
|
import com.oblador.vectoricons.VectorIconsPackage;
|
||||||
import com.reactnativenavigation.NavigationApplication;
|
import com.reactnativenavigation.NavigationApplication;
|
||||||
import com.remobile.toast.RCTToastPackage;
|
import com.remobile.toast.RCTToastPackage;
|
||||||
|
@ -62,10 +60,8 @@ public class MainApplication extends NavigationApplication implements INotificat
|
||||||
new RNDeviceInfo(),
|
new RNDeviceInfo(),
|
||||||
new RNGestureHandlerPackage(),
|
new RNGestureHandlerPackage(),
|
||||||
new PickerPackage(),
|
new PickerPackage(),
|
||||||
new SvgPackage(),
|
|
||||||
new VectorIconsPackage(),
|
new VectorIconsPackage(),
|
||||||
new RNFetchBlobPackage(),
|
new RNFetchBlobPackage(),
|
||||||
new ZeroconfReactPackage(),
|
|
||||||
new RealmReactPackage(),
|
new RealmReactPackage(),
|
||||||
new ReactVideoPackage(),
|
new ReactVideoPackage(),
|
||||||
new RCTToastPackage(),
|
new RCTToastPackage(),
|
||||||
|
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 33 KiB |
|
@ -21,12 +21,8 @@ include ':reactnativekeyboardinput'
|
||||||
project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android')
|
project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android')
|
||||||
include ':react-native-video'
|
include ':react-native-video'
|
||||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
|
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
|
||||||
include ':react-native-svg'
|
|
||||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
|
||||||
include ':react-native-vector-icons'
|
include ':react-native-vector-icons'
|
||||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||||
include ':react-native-zeroconf'
|
|
||||||
project(':react-native-zeroconf').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-zeroconf/android')
|
|
||||||
include ':realm'
|
include ':realm'
|
||||||
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
|
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
|
||||||
include ':react-native-navigation'
|
include ':react-native-navigation'
|
||||||
|
|
|
@ -3,12 +3,17 @@ class NavigationActionsClass {
|
||||||
this.navigator = navigator;
|
this.navigator = navigator;
|
||||||
}
|
}
|
||||||
|
|
||||||
push = params => this.navigator && this.navigator.push(params);
|
push = params => this.navigator && this.navigator.push(params)
|
||||||
pop = params => this.navigator && this.navigator.pop(params);
|
|
||||||
popToRoot = params => this.navigator && this.navigator.popToRoot(params);
|
pop = params => this.navigator && this.navigator.pop(params)
|
||||||
resetTo = params => this.navigator && this.navigator.resetTo(params);
|
|
||||||
toggleDrawer = params => this.navigator && this.navigator.toggleDrawer(params);
|
popToRoot = params => this.navigator && this.navigator.popToRoot(params)
|
||||||
dismissModal = params => this.navigator && this.navigator.dismissModal(params);
|
|
||||||
|
resetTo = params => this.navigator && this.navigator.resetTo(params)
|
||||||
|
|
||||||
|
toggleDrawer = params => this.navigator && this.navigator.toggleDrawer(params)
|
||||||
|
|
||||||
|
dismissModal = params => this.navigator && this.navigator.dismissModal(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NavigationActions = new NavigationActionsClass();
|
export const NavigationActions = new NavigationActionsClass();
|
||||||
|
|
|
@ -103,4 +103,3 @@ export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL
|
||||||
|
|
||||||
export const INCREMENT = 'INCREMENT';
|
export const INCREMENT = 'INCREMENT';
|
||||||
export const DECREMENT = 'DECREMENT';
|
export const DECREMENT = 'DECREMENT';
|
||||||
|
|
||||||
|
|
|
@ -26,4 +26,3 @@ export function setLoading(loading) {
|
||||||
loading
|
loading
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ export default class Panel extends React.Component {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
style: PropTypes.object
|
style: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -18,15 +19,22 @@ export default class Panel extends React.Component {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
this.opacity = 0;
|
this.opacity = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const initialValue = !this.props.open ? this.height : 0;
|
const { animation } = this.state;
|
||||||
this.state.animation.setValue(initialValue);
|
const { open } = this.props;
|
||||||
|
const initialValue = !open ? this.height : 0;
|
||||||
|
animation.setValue(initialValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const { animation } = this.state;
|
||||||
|
const { open } = this.props;
|
||||||
|
|
||||||
if (this.first) {
|
if (this.first) {
|
||||||
this.first = false;
|
this.first = false;
|
||||||
if (!this.props.open) {
|
if (!open) {
|
||||||
this.state.animation.setValue(0);
|
animation.setValue(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,9 +45,9 @@ export default class Panel extends React.Component {
|
||||||
const initialValue = !nextProps.open ? this.height : 0;
|
const initialValue = !nextProps.open ? this.height : 0;
|
||||||
const finalValue = !nextProps.open ? 0 : this.height;
|
const finalValue = !nextProps.open ? 0 : this.height;
|
||||||
|
|
||||||
this.state.animation.setValue(initialValue);
|
animation.setValue(initialValue);
|
||||||
Animated.timing(
|
Animated.timing(
|
||||||
this.state.animation,
|
animation,
|
||||||
{
|
{
|
||||||
toValue: finalValue,
|
toValue: finalValue,
|
||||||
duration: 150,
|
duration: 150,
|
||||||
|
@ -47,16 +55,21 @@ export default class Panel extends React.Component {
|
||||||
}
|
}
|
||||||
).start();
|
).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
set _height(h) {
|
set _height(h) {
|
||||||
this.height = h || this.height;
|
this.height = h || this.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { animation } = this.state;
|
||||||
|
const { style, children } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[{ height: this.state.animation }, this.props.style]}
|
style={[{ height: animation }, style]}
|
||||||
>
|
>
|
||||||
<View onLayout={({ nativeEvent }) => this._height = nativeEvent.layout.height} style={{ position: !this.first ? 'relative' : 'absolute' }}>
|
<View onLayout={({ nativeEvent }) => this._height = nativeEvent.layout.height} style={{ position: !this.first ? 'relative' : 'absolute' }}>
|
||||||
{this.props.children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,10 +14,11 @@ export default class Fade extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
const { visible } = this.props;
|
||||||
this.state = {
|
this.state = {
|
||||||
visible: props.visible
|
visible
|
||||||
};
|
};
|
||||||
this._visibility = new Animated.Value(this.props.visible ? 1 : 0);
|
this._visibility = new Animated.Value(visible ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
@ -34,6 +35,7 @@ export default class Fade extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { visible } = this.state;
|
||||||
const { style, children, ...rest } = this.props;
|
const { style, children, ...rest } = this.props;
|
||||||
|
|
||||||
const containerStyle = {
|
const containerStyle = {
|
||||||
|
@ -53,8 +55,8 @@ export default class Fade extends React.Component {
|
||||||
|
|
||||||
const combinedStyle = [containerStyle, style];
|
const combinedStyle = [containerStyle, style];
|
||||||
return (
|
return (
|
||||||
<Animated.View style={this.state.visible ? combinedStyle : containerStyle} {...rest}>
|
<Animated.View style={visible ? combinedStyle : containerStyle} {...rest}>
|
||||||
<Text>{this.state.visible ? children : null}</Text>
|
<Text>{visible ? children : null}</Text>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, Text, View, ViewPropTypes } from 'react-native';
|
import {
|
||||||
|
StyleSheet, Text, View, ViewPropTypes
|
||||||
|
} from 'react-native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
||||||
|
|
||||||
|
@ -29,14 +31,16 @@ export default class Avatar extends React.PureComponent {
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
children: PropTypes.object,
|
children: PropTypes.object,
|
||||||
forceInitials: PropTypes.bool
|
forceInitials: PropTypes.bool
|
||||||
};
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
text: '',
|
text: '',
|
||||||
size: 25,
|
size: 25,
|
||||||
type: 'd',
|
type: 'd',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
forceInitials: false
|
forceInitials: false
|
||||||
};
|
}
|
||||||
|
|
||||||
state = { showInitials: true };
|
state = { showInitials: true };
|
||||||
|
|
||||||
// componentDidMount() {
|
// componentDidMount() {
|
||||||
|
@ -93,8 +97,9 @@ export default class Avatar extends React.PureComponent {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { showInitials } = this.state;
|
||||||
const {
|
const {
|
||||||
text, size, baseUrl, borderRadius, style, avatar, type, forceInitials
|
text, size, baseUrl, borderRadius, style, avatar, type, forceInitials, children
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
||||||
|
|
||||||
|
@ -133,17 +138,19 @@ export default class Avatar extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.iconContainer, iconContainerStyle, style]}>
|
<View style={[styles.iconContainer, iconContainerStyle, style]}>
|
||||||
{this.state.showInitials ?
|
{showInitials
|
||||||
<Text
|
? (
|
||||||
style={[styles.avatarInitials, avatarInitialsStyle]}
|
<Text
|
||||||
allowFontScaling={false}
|
style={[styles.avatarInitials, avatarInitialsStyle]}
|
||||||
>
|
allowFontScaling={false}
|
||||||
{initials}
|
>
|
||||||
</Text>
|
{initials}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{image}
|
{image}
|
||||||
{this.props.children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, View, Text, Platform, ActivityIndicator } from 'react-native';
|
import {
|
||||||
|
StyleSheet, View, Text, Platform, ActivityIndicator
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
import { COLOR_BUTTON_PRIMARY, COLOR_TEXT } from '../../constants/colors';
|
import { COLOR_BUTTON_PRIMARY, COLOR_TEXT } from '../../constants/colors';
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
|
@ -92,9 +94,9 @@ export default class Button extends React.PureComponent {
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
loading ?
|
loading
|
||||||
<ActivityIndicator color={colors[`text_color_${ type }`]} /> :
|
? <ActivityIndicator color={colors[`text_color_${ type }`]} />
|
||||||
<Text style={[styles.text, styles[`text_color_${ type }`]]}>{title}</Text>
|
: <Text style={[styles.text, styles[`text_color_${ type }`]]}>{title}</Text>
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
</Touch>
|
</Touch>
|
||||||
|
|
|
@ -8,9 +8,11 @@ export default class CustomEmoji extends React.Component {
|
||||||
emoji: PropTypes.object.isRequired,
|
emoji: PropTypes.object.isRequired,
|
||||||
style: ViewPropTypes.style
|
style: ViewPropTypes.style
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
shouldComponentUpdate() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { baseUrl, emoji, style } = this.props;
|
const { baseUrl, emoji, style } = this.props;
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -8,7 +8,7 @@ import styles from './styles';
|
||||||
import CustomEmoji from './CustomEmoji';
|
import CustomEmoji from './CustomEmoji';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
|
||||||
const emojisPerRow = Platform.OS === 'ios' ? 8 : 9;
|
const EMOJIS_PER_ROW = Platform.OS === 'ios' ? 8 : 9;
|
||||||
|
|
||||||
const renderEmoji = (emoji, size, baseUrl) => {
|
const renderEmoji = (emoji, size, baseUrl) => {
|
||||||
if (emoji.isCustom) {
|
if (emoji.isCustom) {
|
||||||
|
@ -31,12 +31,14 @@ export default class EmojiCategory extends React.Component {
|
||||||
onEmojiSelected: PropTypes.func,
|
onEmojiSelected: PropTypes.func,
|
||||||
emojisPerRow: PropTypes.number,
|
emojisPerRow: PropTypes.number,
|
||||||
width: PropTypes.number
|
width: PropTypes.number
|
||||||
};
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const { width, height } = this.props.window;
|
const { window, width, emojisPerRow } = this.props;
|
||||||
|
const { width: widthWidth, height: windowHeight } = window;
|
||||||
|
|
||||||
this.size = Math.min(this.props.width || width, height) / (this.props.emojisPerRow || emojisPerRow);
|
this.size = Math.min(width || widthWidth, windowHeight) / (emojisPerRow || EMOJIS_PER_ROW);
|
||||||
this.emojis = props.emojis;
|
this.emojis = props.emojis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,12 +47,12 @@ export default class EmojiCategory extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem(emoji, size) {
|
renderItem(emoji, size) {
|
||||||
const { baseUrl } = this.props;
|
const { baseUrl, onEmojiSelected } = this.props;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
key={emoji.isCustom ? emoji.content : emoji}
|
key={emoji.isCustom ? emoji.content : emoji}
|
||||||
onPress={() => this.props.onEmojiSelected(emoji)}
|
onPress={() => onEmojiSelected(emoji)}
|
||||||
testID={`reaction-picker-${ emoji.isCustom ? emoji.content : emoji }`}
|
testID={`reaction-picker-${ emoji.isCustom ? emoji.content : emoji }`}
|
||||||
>
|
>
|
||||||
{renderEmoji(emoji, size, baseUrl)}
|
{renderEmoji(emoji, size, baseUrl)}
|
||||||
|
@ -58,12 +60,14 @@ export default class EmojiCategory extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { emojis } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptimizedFlatList
|
<OptimizedFlatList
|
||||||
keyExtractor={item => (item.isCustom && item.content) || item}
|
keyExtractor={item => (item.isCustom && item.content) || item}
|
||||||
data={this.props.emojis}
|
data={emojis}
|
||||||
renderItem={({ item }) => this.renderItem(item, this.size)}
|
renderItem={({ item }) => this.renderItem(item, this.size)}
|
||||||
numColumns={emojisPerRow}
|
numColumns={EMOJIS_PER_ROW}
|
||||||
initialNumToRender={45}
|
initialNumToRender={45}
|
||||||
getItemLayout={(data, index) => ({ length: this.size, offset: this.size * index, index })}
|
getItemLayout={(data, index) => ({ length: this.size, offset: this.size * index, index })}
|
||||||
removeClippedSubviews
|
removeClippedSubviews
|
||||||
|
|
|
@ -12,18 +12,22 @@ export default class TabBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
tabs, goToPage, tabEmojiStyle, activeTab
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.tabsContainer}>
|
<View style={styles.tabsContainer}>
|
||||||
{this.props.tabs.map((tab, i) => (
|
{tabs.map((tab, i) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
key={tab}
|
key={tab}
|
||||||
onPress={() => this.props.goToPage(i)}
|
onPress={() => goToPage(i)}
|
||||||
style={styles.tab}
|
style={styles.tab}
|
||||||
testID={`reaction-picker-${ tab }`}
|
testID={`reaction-picker-${ tab }`}
|
||||||
>
|
>
|
||||||
<Text style={[styles.tabEmoji, this.props.tabEmojiStyle]}>{tab}</Text>
|
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
|
||||||
{this.props.activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />}
|
{activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -49,36 +49,41 @@ export default class EmojiPicker extends Component {
|
||||||
this.updateFrequentlyUsed();
|
this.updateFrequentlyUsed();
|
||||||
this.updateCustomEmojis();
|
this.updateCustomEmojis();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.frequentlyUsed.removeAllListeners();
|
this.frequentlyUsed.removeAllListeners();
|
||||||
this.customEmojis.removeAllListeners();
|
this.customEmojis.removeAllListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmojiSelected(emoji) {
|
onEmojiSelected(emoji) {
|
||||||
|
const { onEmojiSelected } = this.props;
|
||||||
if (emoji.isCustom) {
|
if (emoji.isCustom) {
|
||||||
const count = this._getFrequentlyUsedCount(emoji.content);
|
const count = this._getFrequentlyUsedCount(emoji.content);
|
||||||
this._addFrequentlyUsed({
|
this._addFrequentlyUsed({
|
||||||
content: emoji.content, extension: emoji.extension, count, isCustom: true
|
content: emoji.content, extension: emoji.extension, count, isCustom: true
|
||||||
});
|
});
|
||||||
this.props.onEmojiSelected(`:${ emoji.content }:`);
|
onEmojiSelected(`:${ emoji.content }:`);
|
||||||
} else {
|
} else {
|
||||||
const content = emoji;
|
const content = emoji;
|
||||||
const count = this._getFrequentlyUsedCount(content);
|
const count = this._getFrequentlyUsedCount(content);
|
||||||
this._addFrequentlyUsed({ content, count, isCustom: false });
|
this._addFrequentlyUsed({ content, count, isCustom: false });
|
||||||
const shortname = `:${ emoji }:`;
|
const shortname = `:${ emoji }:`;
|
||||||
this.props.onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
|
onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
_addFrequentlyUsed = protectedFunction((emoji) => {
|
_addFrequentlyUsed = protectedFunction((emoji) => {
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
database.create('frequentlyUsedEmoji', emoji, true);
|
database.create('frequentlyUsedEmoji', emoji, true);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
_getFrequentlyUsedCount = (content) => {
|
_getFrequentlyUsedCount = (content) => {
|
||||||
const emojiRow = this.frequentlyUsed.filtered('content == $0', content);
|
const emojiRow = this.frequentlyUsed.filtered('content == $0', content);
|
||||||
return emojiRow.length ? emojiRow[0].count + 1 : 1;
|
return emojiRow.length ? emojiRow[0].count + 1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFrequentlyUsed() {
|
updateFrequentlyUsed() {
|
||||||
const frequentlyUsed = map(this.frequentlyUsed.slice(), (item) => {
|
const frequentlyUsed = map(this.frequentlyUsed.slice(), (item) => {
|
||||||
if (item.isCustom) {
|
if (item.isCustom) {
|
||||||
|
@ -90,17 +95,19 @@ export default class EmojiPicker extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCustomEmojis() {
|
updateCustomEmojis() {
|
||||||
const customEmojis = map(this.customEmojis.slice(), item =>
|
const customEmojis = map(this.customEmojis.slice(), item => ({ content: item.name, extension: item.extension, isCustom: true }));
|
||||||
({ content: item.name, extension: item.extension, isCustom: true }));
|
|
||||||
this.setState({ customEmojis });
|
this.setState({ customEmojis });
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCategory(category, i) {
|
renderCategory(category, i) {
|
||||||
|
const { frequentlyUsed, customEmojis } = this.state;
|
||||||
|
const { emojisPerRow, width, baseUrl } = this.props;
|
||||||
|
|
||||||
let emojis = [];
|
let emojis = [];
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
emojis = this.state.frequentlyUsed;
|
emojis = frequentlyUsed;
|
||||||
} else if (i === 1) {
|
} else if (i === 1) {
|
||||||
emojis = this.state.customEmojis;
|
emojis = customEmojis;
|
||||||
} else {
|
} else {
|
||||||
emojis = emojisByCategory[category];
|
emojis = emojisByCategory[category];
|
||||||
}
|
}
|
||||||
|
@ -109,21 +116,23 @@ export default class EmojiPicker extends Component {
|
||||||
emojis={emojis}
|
emojis={emojis}
|
||||||
onEmojiSelected={emoji => this.onEmojiSelected(emoji)}
|
onEmojiSelected={emoji => this.onEmojiSelected(emoji)}
|
||||||
style={styles.categoryContainer}
|
style={styles.categoryContainer}
|
||||||
size={this.props.emojisPerRow}
|
size={emojisPerRow}
|
||||||
width={this.props.width}
|
width={width}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.state.show) {
|
const { show } = this.state;
|
||||||
|
const { tabEmojiStyle } = this.props;
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
// <View style={styles.container}>
|
|
||||||
<ScrollableTabView
|
<ScrollableTabView
|
||||||
renderTabBar={() => <TabBar tabEmojiStyle={this.props.tabEmojiStyle} />}
|
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} />}
|
||||||
contentProps={scrollProps}
|
contentProps={scrollProps}
|
||||||
style={styles.background}
|
style={styles.background}
|
||||||
>
|
>
|
||||||
|
@ -140,7 +149,6 @@ export default class EmojiPicker extends Component {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ScrollableTabView>
|
</ScrollableTabView>
|
||||||
// </View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, View, Modal, Animated } from 'react-native';
|
import {
|
||||||
|
StyleSheet, View, Modal, Animated
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -27,8 +29,11 @@ export default class Loading extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const { opacity, scale } = this.state;
|
||||||
|
const { visible } = this.props;
|
||||||
|
|
||||||
this.opacityAnimation = Animated.timing(
|
this.opacityAnimation = Animated.timing(
|
||||||
this.state.opacity,
|
opacity,
|
||||||
{
|
{
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
|
@ -37,7 +42,7 @@ export default class Loading extends React.PureComponent {
|
||||||
);
|
);
|
||||||
this.scaleAnimation = Animated.loop(Animated.sequence([
|
this.scaleAnimation = Animated.loop(Animated.sequence([
|
||||||
Animated.timing(
|
Animated.timing(
|
||||||
this.state.scale,
|
scale,
|
||||||
{
|
{
|
||||||
toValue: 0,
|
toValue: 0,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
|
@ -45,7 +50,7 @@ export default class Loading extends React.PureComponent {
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Animated.timing(
|
Animated.timing(
|
||||||
this.state.scale,
|
scale,
|
||||||
{
|
{
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
|
@ -54,13 +59,14 @@ export default class Loading extends React.PureComponent {
|
||||||
)
|
)
|
||||||
]));
|
]));
|
||||||
|
|
||||||
if (this.props.visible) {
|
if (visible) {
|
||||||
this.startAnimations();
|
this.startAnimations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.visible && this.props.visible !== prevProps.visible) {
|
const { visible } = this.props;
|
||||||
|
if (visible && visible !== prevProps.visible) {
|
||||||
this.startAnimations();
|
this.startAnimations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,13 +90,16 @@ export default class Loading extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const scale = this.state.scale.interpolate({
|
const { opacity, scale } = this.state;
|
||||||
|
const { visible } = this.props;
|
||||||
|
|
||||||
|
const scaleAnimation = scale.interpolate({
|
||||||
inputRange: [0, 0.5, 1],
|
inputRange: [0, 0.5, 1],
|
||||||
outputRange: [1, 1.1, 1]
|
outputRange: [1, 1.1, 1]
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={this.props.visible}
|
visible={visible}
|
||||||
transparent
|
transparent
|
||||||
onRequestClose={() => {}}
|
onRequestClose={() => {}}
|
||||||
>
|
>
|
||||||
|
@ -98,9 +107,9 @@ export default class Loading extends React.PureComponent {
|
||||||
<Animated.Image
|
<Animated.Image
|
||||||
source={require('../static/images/logo.png')}
|
source={require('../static/images/logo.png')}
|
||||||
style={[styles.image, {
|
style={[styles.image, {
|
||||||
opacity: this.state.opacity,
|
opacity,
|
||||||
transform: [{
|
transform: [{
|
||||||
scale
|
scale: scaleAnimation
|
||||||
}]
|
}]
|
||||||
}]}
|
}]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Alert, Clipboard, Vibration, Share } from 'react-native';
|
import {
|
||||||
|
Alert, Clipboard, Vibration, Share
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ActionSheet from 'react-native-actionsheet';
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteRequest,
|
actionsHide as actionsHideAction,
|
||||||
editInit,
|
deleteRequest as deleteRequestAction,
|
||||||
toggleStarRequest,
|
editInit as editInitAction,
|
||||||
togglePinRequest,
|
replyInit as replyInitAction,
|
||||||
actionsHide,
|
togglePinRequest as togglePinRequestAction,
|
||||||
toggleReactionPicker,
|
toggleReactionPicker as toggleReactionPickerAction,
|
||||||
replyInit
|
toggleStarRequest as toggleStarRequestAction
|
||||||
} from '../actions/messages';
|
} from '../actions/messages';
|
||||||
import { showToast } from '../utils/info';
|
import { showToast } from '../utils/info';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
@ -29,13 +31,13 @@ import I18n from '../i18n';
|
||||||
Message_AllowStarring: state.settings.Message_AllowStarring
|
Message_AllowStarring: state.settings.Message_AllowStarring
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
actionsHide: () => dispatch(actionsHide()),
|
actionsHide: () => dispatch(actionsHideAction()),
|
||||||
deleteRequest: message => dispatch(deleteRequest(message)),
|
deleteRequest: message => dispatch(deleteRequestAction(message)),
|
||||||
editInit: message => dispatch(editInit(message)),
|
editInit: message => dispatch(editInitAction(message)),
|
||||||
toggleStarRequest: message => dispatch(toggleStarRequest(message)),
|
toggleStarRequest: message => dispatch(toggleStarRequestAction(message)),
|
||||||
togglePinRequest: message => dispatch(togglePinRequest(message)),
|
togglePinRequest: message => dispatch(togglePinRequestAction(message)),
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message)),
|
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
||||||
replyInit: (message, mention) => dispatch(replyInit(message, mention))
|
replyInit: (message, mention) => dispatch(replyInitAction(message, mention))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
export default class MessageActions extends React.Component {
|
export default class MessageActions extends React.Component {
|
||||||
|
@ -63,6 +65,8 @@ export default class MessageActions extends React.Component {
|
||||||
this.handleActionPress = this.handleActionPress.bind(this);
|
this.handleActionPress = this.handleActionPress.bind(this);
|
||||||
this.setPermissions();
|
this.setPermissions();
|
||||||
|
|
||||||
|
const { Message_AllowStarring, Message_AllowPinning } = this.props;
|
||||||
|
|
||||||
// Cancel
|
// Cancel
|
||||||
this.options = [I18n.t('Cancel')];
|
this.options = [I18n.t('Cancel')];
|
||||||
this.CANCEL_INDEX = 0;
|
this.CANCEL_INDEX = 0;
|
||||||
|
@ -98,13 +102,13 @@ export default class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Star
|
// Star
|
||||||
if (this.props.Message_AllowStarring) {
|
if (Message_AllowStarring) {
|
||||||
this.options.push(I18n.t(props.actionMessage.starred ? 'Unstar' : 'Star'));
|
this.options.push(I18n.t(props.actionMessage.starred ? 'Unstar' : 'Star'));
|
||||||
this.STAR_INDEX = this.options.length - 1;
|
this.STAR_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pin
|
// Pin
|
||||||
if (this.props.Message_AllowPinning) {
|
if (Message_AllowPinning) {
|
||||||
this.options.push(I18n.t(props.actionMessage.pinned ? 'Unpin' : 'Pin'));
|
this.options.push(I18n.t(props.actionMessage.pinned ? 'Unpin' : 'Pin'));
|
||||||
this.PIN_INDEX = this.options.length - 1;
|
this.PIN_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
@ -129,8 +133,9 @@ export default class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
setPermissions() {
|
setPermissions() {
|
||||||
|
const { room } = this.props;
|
||||||
const permissions = ['edit-message', 'delete-message', 'force-delete-message'];
|
const permissions = ['edit-message', 'delete-message', 'force-delete-message'];
|
||||||
const result = RocketChat.hasPermission(permissions, this.props.room.rid);
|
const result = RocketChat.hasPermission(permissions, room.rid);
|
||||||
this.hasEditPermission = result[permissions[0]];
|
this.hasEditPermission = result[permissions[0]];
|
||||||
this.hasDeletePermission = result[permissions[1]];
|
this.hasDeletePermission = result[permissions[1]];
|
||||||
this.hasForceDeletePermission = result[permissions[2]];
|
this.hasForceDeletePermission = result[permissions[2]];
|
||||||
|
@ -146,20 +151,27 @@ export default class MessageActions extends React.Component {
|
||||||
|
|
||||||
isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id;
|
isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id;
|
||||||
|
|
||||||
isRoomReadOnly = () => this.props.room.ro;
|
isRoomReadOnly = () => {
|
||||||
|
const { room } = this.props;
|
||||||
|
return room.ro;
|
||||||
|
}
|
||||||
|
|
||||||
canReactWhenReadOnly = () => this.props.room.reactWhenReadOnly;
|
canReactWhenReadOnly = () => {
|
||||||
|
const { room } = this.props;
|
||||||
|
return room.reactWhenReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
allowEdit = (props) => {
|
allowEdit = (props) => {
|
||||||
if (this.isRoomReadOnly()) {
|
if (this.isRoomReadOnly()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const editOwn = this.isOwn(props);
|
const editOwn = this.isOwn(props);
|
||||||
const { Message_AllowEditing: isEditAllowed } = this.props;
|
const { Message_AllowEditing: isEditAllowed, Message_AllowEditing_BlockEditInMinutes } = this.props;
|
||||||
|
|
||||||
if (!(this.hasEditPermission || (isEditAllowed && editOwn))) {
|
if (!(this.hasEditPermission || (isEditAllowed && editOwn))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const blockEditInMinutes = this.props.Message_AllowEditing_BlockEditInMinutes;
|
const blockEditInMinutes = Message_AllowEditing_BlockEditInMinutes;
|
||||||
if (blockEditInMinutes) {
|
if (blockEditInMinutes) {
|
||||||
let msgTs;
|
let msgTs;
|
||||||
if (props.actionMessage.ts != null) {
|
if (props.actionMessage.ts != null) {
|
||||||
|
@ -179,14 +191,14 @@ export default class MessageActions extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const deleteOwn = this.isOwn(props);
|
const deleteOwn = this.isOwn(props);
|
||||||
const { Message_AllowDeleting: isDeleteAllowed } = this.props;
|
const { Message_AllowDeleting: isDeleteAllowed, Message_AllowDeleting_BlockDeleteInMinutes } = this.props;
|
||||||
if (!(this.hasDeletePermission || (isDeleteAllowed && deleteOwn) || this.hasForceDeletePermission)) {
|
if (!(this.hasDeletePermission || (isDeleteAllowed && deleteOwn) || this.hasForceDeletePermission)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.hasForceDeletePermission) {
|
if (this.hasForceDeletePermission) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const blockDeleteInMinutes = this.props.Message_AllowDeleting_BlockDeleteInMinutes;
|
const blockDeleteInMinutes = Message_AllowDeleting_BlockDeleteInMinutes;
|
||||||
if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
|
if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
|
||||||
let msgTs;
|
let msgTs;
|
||||||
if (props.actionMessage.ts != null) {
|
if (props.actionMessage.ts != null) {
|
||||||
|
@ -201,7 +213,8 @@ export default class MessageActions extends React.Component {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDelete() {
|
handleDelete = () => {
|
||||||
|
const { deleteRequest, actionMessage } = this.props;
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
I18n.t('Are_you_sure_question_mark'),
|
I18n.t('Are_you_sure_question_mark'),
|
||||||
I18n.t('You_will_not_be_able_to_recover_this_message'),
|
I18n.t('You_will_not_be_able_to_recover_this_message'),
|
||||||
|
@ -213,56 +226,66 @@ export default class MessageActions extends React.Component {
|
||||||
{
|
{
|
||||||
text: I18n.t('Yes_action_it', { action: 'delete' }),
|
text: I18n.t('Yes_action_it', { action: 'delete' }),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => this.props.deleteRequest(this.props.actionMessage)
|
onPress: () => deleteRequest(actionMessage)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
{ cancelable: false }
|
{ cancelable: false }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEdit() {
|
handleEdit = () => {
|
||||||
const { _id, msg, rid } = this.props.actionMessage;
|
const { actionMessage, editInit } = this.props;
|
||||||
this.props.editInit({ _id, msg, rid });
|
const { _id, msg, rid } = actionMessage;
|
||||||
|
editInit({ _id, msg, rid });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCopy = async() => {
|
handleCopy = async() => {
|
||||||
await Clipboard.setString(this.props.actionMessage.msg);
|
const { actionMessage } = this.props;
|
||||||
|
await Clipboard.setString(actionMessage.msg);
|
||||||
showToast(I18n.t('Copied_to_clipboard'));
|
showToast(I18n.t('Copied_to_clipboard'));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShare = async() => {
|
handleShare = async() => {
|
||||||
|
const { actionMessage } = this.props;
|
||||||
Share.share({
|
Share.share({
|
||||||
message: this.props.actionMessage.msg.content.replace(/<(?:.|\n)*?>/gm, '')
|
message: actionMessage.msg.content.replace(/<(?:.|\n)*?>/gm, '')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleStar() {
|
handleStar = () => {
|
||||||
this.props.toggleStarRequest(this.props.actionMessage);
|
const { actionMessage, toggleStarRequest } = this.props;
|
||||||
|
toggleStarRequest(actionMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePermalink() {
|
handlePermalink = async() => {
|
||||||
const permalink = await this.getPermalink(this.props.actionMessage);
|
const { actionMessage } = this.props;
|
||||||
|
const permalink = await this.getPermalink(actionMessage);
|
||||||
Clipboard.setString(permalink);
|
Clipboard.setString(permalink);
|
||||||
showToast(I18n.t('Permalink_copied_to_clipboard'));
|
showToast(I18n.t('Permalink_copied_to_clipboard'));
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePin() {
|
handlePin = () => {
|
||||||
this.props.togglePinRequest(this.props.actionMessage);
|
const { actionMessage, togglePinRequest } = this.props;
|
||||||
|
togglePinRequest(actionMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReply() {
|
handleReply = () => {
|
||||||
this.props.replyInit(this.props.actionMessage, true);
|
const { actionMessage, replyInit } = this.props;
|
||||||
|
replyInit(actionMessage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQuote() {
|
handleQuote = () => {
|
||||||
this.props.replyInit(this.props.actionMessage, false);
|
const { actionMessage, replyInit } = this.props;
|
||||||
|
replyInit(actionMessage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReaction() {
|
handleReaction = () => {
|
||||||
this.props.toggleReactionPicker(this.props.actionMessage);
|
const { actionMessage, toggleReactionPicker } = this.props;
|
||||||
|
toggleReactionPicker(actionMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleActionPress = (actionIndex) => {
|
handleActionPress = (actionIndex) => {
|
||||||
|
const { actionsHide } = this.props;
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
case this.REPLY_INDEX:
|
case this.REPLY_INDEX:
|
||||||
this.handleReply();
|
this.handleReply();
|
||||||
|
@ -297,7 +320,7 @@ export default class MessageActions extends React.Component {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.props.actionsHide();
|
actionsHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -12,9 +12,11 @@ export default class EmojiKeyboard extends React.PureComponent {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
this.baseUrl = state.settings.Site_Url || state.server ? state.server.server : '';
|
this.baseUrl = state.settings.Site_Url || state.server ? state.server.server : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmojiSelected = (emoji) => {
|
onEmojiSelected = (emoji) => {
|
||||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<View style={styles.emojiKeyboardContainer} testID='messagebox-keyboard-emoji'>
|
<View style={styles.emojiKeyboardContainer} testID='messagebox-keyboard-emoji'>
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default class FilesActions extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleActionPress = (actionIndex) => {
|
handleActionPress = (actionIndex) => {
|
||||||
const { takePhoto, chooseFromLibrary } = this.props;
|
const { takePhoto, chooseFromLibrary, hideActions } = this.props;
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
case this.PHOTO_INDEX:
|
case this.PHOTO_INDEX:
|
||||||
takePhoto();
|
takePhoto();
|
||||||
|
@ -45,7 +45,7 @@ export default class FilesActions extends Component {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.props.hideActions();
|
hideActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, SafeAreaView, Platform, PermissionsAndroid, Text } from 'react-native';
|
import {
|
||||||
|
View, SafeAreaView, Platform, PermissionsAndroid, Text
|
||||||
|
} from 'react-native';
|
||||||
import { AudioRecorder, AudioUtils } from 'react-native-audio';
|
import { AudioRecorder, AudioUtils } from 'react-native-audio';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -61,7 +63,7 @@ export default class extends React.PureComponent {
|
||||||
//
|
//
|
||||||
AudioRecorder.onFinished = (data) => {
|
AudioRecorder.onFinished = (data) => {
|
||||||
if (!this.recordingCanceled && Platform.OS === 'ios') {
|
if (!this.recordingCanceled && Platform.OS === 'ios') {
|
||||||
this._finishRecording(data.status === 'OK', data.audioFileURL);
|
this.finishRecording(data.status === 'OK', data.audioFileURL);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
AudioRecorder.startRecording();
|
AudioRecorder.startRecording();
|
||||||
|
@ -73,9 +75,10 @@ export default class extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_finishRecording(didSucceed, filePath) {
|
finishRecording = (didSucceed, filePath) => {
|
||||||
|
const { onFinish } = this.props;
|
||||||
if (!didSucceed) {
|
if (!didSucceed) {
|
||||||
return this.props.onFinish && this.props.onFinish(didSucceed);
|
return onFinish && onFinish(didSucceed);
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = filePath.startsWith('file://') ? filePath.split('file://')[1] : filePath;
|
const path = filePath.startsWith('file://') ? filePath.split('file://')[1] : filePath;
|
||||||
|
@ -84,7 +87,7 @@ export default class extends React.PureComponent {
|
||||||
store: 'Uploads',
|
store: 'Uploads',
|
||||||
path
|
path
|
||||||
};
|
};
|
||||||
return this.props.onFinish && this.props.onFinish(fileInfo);
|
return onFinish && onFinish(fileInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
finishAudioMessage = async() => {
|
finishAudioMessage = async() => {
|
||||||
|
@ -92,10 +95,10 @@ export default class extends React.PureComponent {
|
||||||
this.recording = false;
|
this.recording = false;
|
||||||
const filePath = await AudioRecorder.stopRecording();
|
const filePath = await AudioRecorder.stopRecording();
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
this._finishRecording(true, filePath);
|
this.finishRecording(true, filePath);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._finishRecording(false);
|
this.finishRecording(false);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,10 +107,12 @@ export default class extends React.PureComponent {
|
||||||
this.recording = false;
|
this.recording = false;
|
||||||
this.recordingCanceled = true;
|
this.recordingCanceled = true;
|
||||||
await AudioRecorder.stopRecording();
|
await AudioRecorder.stopRecording();
|
||||||
return this._finishRecording(false);
|
return this.finishRecording(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { currentTime } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
key='messagebox-recording'
|
key='messagebox-recording'
|
||||||
|
@ -123,7 +128,7 @@ export default class extends React.PureComponent {
|
||||||
accessibilityTraits='button'
|
accessibilityTraits='button'
|
||||||
onPress={this.cancelAudioMessage}
|
onPress={this.cancelAudioMessage}
|
||||||
/>
|
/>
|
||||||
<Text key='currentTime' style={styles.textBoxInput}>{this.state.currentTime}</Text>
|
<Text key='currentTime' style={styles.textBoxInput}>{currentTime}</Text>
|
||||||
<Icon
|
<Icon
|
||||||
style={[styles.actionButtons, { color: 'green' }]}
|
style={[styles.actionButtons, { color: 'green' }]}
|
||||||
name='check'
|
name='check'
|
||||||
|
|
|
@ -57,7 +57,8 @@ export default class ReplyPreview extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
this.props.close();
|
const { close } = this.props;
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { View, Text, StyleSheet, Image, ScrollView, Platform } from 'react-native';
|
import {
|
||||||
|
View, Text, StyleSheet, Image, ScrollView, Platform
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
import { responsive } from 'react-native-responsive-ui';
|
||||||
|
@ -85,9 +87,9 @@ export default class UploadModal extends Component {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
style={{ alignItems: 'center' }} // TODO: need this?
|
style={{ alignItems: 'center' }}
|
||||||
onBackdropPress={() => this.props.close()}
|
onBackdropPress={() => close()}
|
||||||
onBackButtonPress={() => this.props.close()}
|
onBackButtonPress={() => close()}
|
||||||
animationIn='fadeIn'
|
animationIn='fadeIn'
|
||||||
animationOut='fadeOut'
|
animationOut='fadeOut'
|
||||||
useNativeDriver
|
useNativeDriver
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, TextInput, FlatList, Text, TouchableOpacity, Alert } from 'react-native';
|
import {
|
||||||
|
View, TextInput, FlatList, Text, TouchableOpacity, Alert
|
||||||
|
} from 'react-native';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
|
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
|
||||||
import ImagePicker from 'react-native-image-crop-picker';
|
import ImagePicker from 'react-native-image-crop-picker';
|
||||||
|
|
||||||
import { userTyping } from '../../actions/room';
|
import { userTyping as userTypingAction } from '../../actions/room';
|
||||||
|
import {
|
||||||
|
editRequest as editRequestAction,
|
||||||
|
editCancel as editCancelAction,
|
||||||
|
replyCancel as replyCancelAction
|
||||||
|
} from '../../actions/messages';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import { editRequest, editCancel, replyCancel } from '../../actions/messages';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import MyIcon from '../icons';
|
import MyIcon from '../icons';
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
|
@ -48,10 +54,10 @@ const imagePickerConfig = {
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
username: state.login.user && state.login.user.username
|
username: state.login.user && state.login.user.username
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
editCancel: () => dispatch(editCancel()),
|
editCancel: () => dispatch(editCancelAction()),
|
||||||
editRequest: message => dispatch(editRequest(message)),
|
editRequest: message => dispatch(editRequestAction(message)),
|
||||||
typing: status => dispatch(userTyping(status)),
|
typing: status => dispatch(userTypingAction(status)),
|
||||||
closeReply: () => dispatch(replyCancel())
|
closeReply: () => dispatch(replyCancelAction())
|
||||||
}))
|
}))
|
||||||
export default class MessageBox extends React.PureComponent {
|
export default class MessageBox extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -86,13 +92,15 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.rooms = [];
|
this.rooms = [];
|
||||||
this.emojis = [];
|
this.emojis = [];
|
||||||
this.customEmojis = [];
|
this.customEmojis = [];
|
||||||
this._onEmojiSelected = this._onEmojiSelected.bind(this);
|
this.onEmojiSelected = this.onEmojiSelected.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.message !== nextProps.message && nextProps.message.msg) {
|
const { message, replyMessage } = this.props;
|
||||||
|
if (message !== nextProps.message && nextProps.message.msg) {
|
||||||
this.setState({ text: nextProps.message.msg });
|
this.setState({ text: nextProps.message.msg });
|
||||||
this.component.focus();
|
this.component.focus();
|
||||||
} else if (this.props.replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) {
|
} else if (replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) {
|
||||||
this.component.focus();
|
this.component.focus();
|
||||||
} else if (!nextProps.message) {
|
} else if (!nextProps.message) {
|
||||||
this.setState({ text: '' });
|
this.setState({ text: '' });
|
||||||
|
@ -100,8 +108,10 @@ export default class MessageBox extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeText(text) {
|
onChangeText(text) {
|
||||||
|
const { typing } = this.props;
|
||||||
|
|
||||||
this.setState({ text });
|
this.setState({ text });
|
||||||
this.props.typing(text.length > 0);
|
typing(text.length > 0);
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const { start, end } = this.component._lastNativeSelection;
|
const { start, end } = this.component._lastNativeSelection;
|
||||||
|
@ -126,45 +136,95 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.closeEmoji();
|
this.closeEmoji();
|
||||||
}
|
}
|
||||||
|
|
||||||
get leftButtons() {
|
onPressMention(item) {
|
||||||
const { editing } = this.props;
|
const { trackingType } = this.state;
|
||||||
if (editing) {
|
|
||||||
return (<Icon
|
const msg = this.component._lastNativeText;
|
||||||
style={styles.actionButtons}
|
|
||||||
name='close'
|
const { start, end } = this.component._lastNativeSelection;
|
||||||
accessibilityLabel={I18n.t('Cancel_editing')}
|
|
||||||
accessibilityTraits='button'
|
const cursor = Math.max(start, end);
|
||||||
onPress={() => this.editCancel()}
|
|
||||||
testID='messagebox-cancel-editing'
|
const regexp = /([a-z0-9._-]+)$/im;
|
||||||
/>);
|
|
||||||
}
|
const result = msg.substr(0, cursor).replace(regexp, '');
|
||||||
return !this.state.showEmojiKeyboard ? (<Icon
|
const mentionName = trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
|
||||||
style={styles.actionButtons}
|
? `${ item.name || item }:`
|
||||||
onPress={() => this.openEmoji()}
|
: (item.username || item.name);
|
||||||
accessibilityLabel={I18n.t('Open_emoji_selector')}
|
const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
|
||||||
accessibilityTraits='button'
|
this.component.setNativeProps({ text });
|
||||||
name='mood'
|
this.setState({ text });
|
||||||
testID='messagebox-open-emoji'
|
this.component.focus();
|
||||||
/>) : (<Icon
|
requestAnimationFrame(() => this.stopTrackingMention());
|
||||||
onPress={() => this.closeEmoji()}
|
|
||||||
style={styles.actionButtons}
|
|
||||||
accessibilityLabel={I18n.t('Close_emoji_selector')}
|
|
||||||
accessibilityTraits='button'
|
|
||||||
name='keyboard'
|
|
||||||
testID='messagebox-close-emoji'
|
|
||||||
/>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEmojiSelected(keyboardId, params) {
|
||||||
|
const { text } = this.state;
|
||||||
|
const { emoji } = params;
|
||||||
|
let newText = '';
|
||||||
|
|
||||||
|
// if messagebox has an active cursor
|
||||||
|
if (this.component._lastNativeSelection) {
|
||||||
|
const { start, end } = this.component._lastNativeSelection;
|
||||||
|
const cursor = Math.max(start, end);
|
||||||
|
newText = `${ text.substr(0, cursor) }${ emoji }${ text.substr(cursor) }`;
|
||||||
|
} else {
|
||||||
|
// if messagebox doesn't have a cursor, just append selected emoji
|
||||||
|
newText = `${ text }${ emoji }`;
|
||||||
|
}
|
||||||
|
this.component.setNativeProps({ text: newText });
|
||||||
|
this.setState({ text: newText });
|
||||||
|
}
|
||||||
|
|
||||||
|
get leftButtons() {
|
||||||
|
const { showEmojiKeyboard } = this.state;
|
||||||
|
const { editing } = this.props;
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
style={styles.actionButtons}
|
||||||
|
name='close'
|
||||||
|
accessibilityLabel={I18n.t('Cancel_editing')}
|
||||||
|
accessibilityTraits='button'
|
||||||
|
onPress={() => this.editCancel()}
|
||||||
|
testID='messagebox-cancel-editing'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return !showEmojiKeyboard
|
||||||
|
? (
|
||||||
|
<Icon
|
||||||
|
style={styles.actionButtons}
|
||||||
|
onPress={() => this.openEmoji()}
|
||||||
|
accessibilityLabel={I18n.t('Open_emoji_selector')}
|
||||||
|
accessibilityTraits='button'
|
||||||
|
name='mood'
|
||||||
|
testID='messagebox-open-emoji'
|
||||||
|
/>)
|
||||||
|
: (
|
||||||
|
<Icon
|
||||||
|
onPress={() => this.closeEmoji()}
|
||||||
|
style={styles.actionButtons}
|
||||||
|
accessibilityLabel={I18n.t('Close_emoji_selector')}
|
||||||
|
accessibilityTraits='button'
|
||||||
|
name='keyboard'
|
||||||
|
testID='messagebox-close-emoji'
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
|
||||||
get rightButtons() {
|
get rightButtons() {
|
||||||
|
const { text } = this.state;
|
||||||
const icons = [];
|
const icons = [];
|
||||||
|
|
||||||
if (this.state.text) {
|
if (text) {
|
||||||
icons.push(<MyIcon
|
icons.push(<MyIcon
|
||||||
style={[styles.actionButtons, { color: '#1D74F5' }]}
|
style={[styles.actionButtons, { color: '#1D74F5' }]}
|
||||||
name='send'
|
name='send'
|
||||||
key='sendIcon'
|
key='sendIcon'
|
||||||
accessibilityLabel={I18n.t('Send message')}
|
accessibilityLabel={I18n.t('Send message')}
|
||||||
accessibilityTraits='button'
|
accessibilityTraits='button'
|
||||||
onPress={() => this.submit(this.state.text)}
|
onPress={() => this.submit(text)}
|
||||||
testID='messagebox-send-message'
|
testID='messagebox-send-message'
|
||||||
/>);
|
/>);
|
||||||
return icons;
|
return icons;
|
||||||
|
@ -198,123 +258,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFilesActions = () => {
|
getFixedMentions(keyword) {
|
||||||
this.setState(prevState => ({ showFilesAction: !prevState.showFilesAction }));
|
|
||||||
}
|
|
||||||
|
|
||||||
sendImageMessage = async(file) => {
|
|
||||||
this.setState({ file: { isVisible: false } });
|
|
||||||
const fileInfo = {
|
|
||||||
name: file.name,
|
|
||||||
description: file.description,
|
|
||||||
size: file.size,
|
|
||||||
type: file.mime,
|
|
||||||
store: 'Uploads',
|
|
||||||
path: file.path
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
await RocketChat.sendFileMessage(this.props.rid, fileInfo);
|
|
||||||
} catch (e) {
|
|
||||||
log('sendImageMessage', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
takePhoto = async() => {
|
|
||||||
try {
|
|
||||||
const image = await ImagePicker.openCamera(imagePickerConfig);
|
|
||||||
this.showUploadModal(image);
|
|
||||||
} catch (e) {
|
|
||||||
log('takePhoto', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chooseFromLibrary = async() => {
|
|
||||||
try {
|
|
||||||
const image = await ImagePicker.openPicker(imagePickerConfig);
|
|
||||||
this.showUploadModal(image);
|
|
||||||
} catch (e) {
|
|
||||||
log('chooseFromLibrary', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showUploadModal = (file) => {
|
|
||||||
this.setState({ file: { ...file, isVisible: true } });
|
|
||||||
}
|
|
||||||
|
|
||||||
editCancel() {
|
|
||||||
this.props.editCancel();
|
|
||||||
this.setState({ text: '' });
|
|
||||||
}
|
|
||||||
|
|
||||||
async openEmoji() {
|
|
||||||
await this.setState({
|
|
||||||
showEmojiKeyboard: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async recordAudioMessage() {
|
|
||||||
const recording = await Recording.permission();
|
|
||||||
this.setState({ recording });
|
|
||||||
}
|
|
||||||
|
|
||||||
finishAudioMessage = async(fileInfo) => {
|
|
||||||
this.setState({
|
|
||||||
recording: false
|
|
||||||
});
|
|
||||||
if (fileInfo) {
|
|
||||||
try {
|
|
||||||
await RocketChat.sendFileMessage(this.props.rid, fileInfo);
|
|
||||||
} catch (e) {
|
|
||||||
if (e && e.error === 'error-file-too-large') {
|
|
||||||
return Alert.alert(I18n.t(e.error));
|
|
||||||
}
|
|
||||||
log('finishAudioMessage', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeEmoji() {
|
|
||||||
this.setState({ showEmojiKeyboard: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit(message) {
|
|
||||||
this.setState({ text: '' });
|
|
||||||
this.closeEmoji();
|
|
||||||
this.stopTrackingMention();
|
|
||||||
this.props.typing(false);
|
|
||||||
if (message.trim() === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// if is editing a message
|
|
||||||
const {
|
|
||||||
editing, replying
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (editing) {
|
|
||||||
const { _id, rid } = this.props.message;
|
|
||||||
this.props.editRequest({ _id, msg: message, rid });
|
|
||||||
} else if (replying) {
|
|
||||||
const {
|
|
||||||
username, replyMessage, roomType, closeReply
|
|
||||||
} = this.props;
|
|
||||||
const permalink = await this.getPermalink(replyMessage);
|
|
||||||
let msg = `[ ](${ permalink }) `;
|
|
||||||
|
|
||||||
// if original message wasn't sent by current user and neither from a direct room
|
|
||||||
if (username !== replyMessage.u.username && roomType !== 'd' && replyMessage.mention) {
|
|
||||||
msg += `@${ replyMessage.u.username } `;
|
|
||||||
}
|
|
||||||
|
|
||||||
msg = `${ msg } ${ message }`;
|
|
||||||
this.props.onSubmit(msg);
|
|
||||||
closeReply();
|
|
||||||
} else {
|
|
||||||
// if is submiting a new message
|
|
||||||
this.props.onSubmit(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getFixedMentions(keyword) {
|
|
||||||
if ('all'.indexOf(keyword) !== -1) {
|
if ('all'.indexOf(keyword) !== -1) {
|
||||||
this.users = [{ _id: -1, username: 'all' }, ...this.users];
|
this.users = [{ _id: -1, username: 'all' }, ...this.users];
|
||||||
}
|
}
|
||||||
|
@ -323,12 +267,12 @@ export default class MessageBox extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getUsers(keyword) {
|
async getUsers(keyword) {
|
||||||
this.users = database.objects('users');
|
this.users = database.objects('users');
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
this.users = this.users.filtered('username CONTAINS[c] $0', keyword);
|
this.users = this.users.filtered('username CONTAINS[c] $0', keyword);
|
||||||
}
|
}
|
||||||
this._getFixedMentions(keyword);
|
this.getFixedMentions(keyword);
|
||||||
this.setState({ mentions: this.users.slice() });
|
this.setState({ mentions: this.users.slice() });
|
||||||
|
|
||||||
const usernames = [];
|
const usernames = [];
|
||||||
|
@ -359,12 +303,12 @@ export default class MessageBox extends React.PureComponent {
|
||||||
} finally {
|
} finally {
|
||||||
delete this.oldPromise;
|
delete this.oldPromise;
|
||||||
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice();
|
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice();
|
||||||
this._getFixedMentions(keyword);
|
this.getFixedMentions(keyword);
|
||||||
this.setState({ mentions: this.users });
|
this.setState({ mentions: this.users });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getRooms(keyword = '') {
|
async getRooms(keyword = '') {
|
||||||
this.roomsCache = this.roomsCache || [];
|
this.roomsCache = this.roomsCache || [];
|
||||||
this.rooms = database.objects('subscriptions')
|
this.rooms = database.objects('subscriptions')
|
||||||
.filtered('t != $0', 'd');
|
.filtered('t != $0', 'd');
|
||||||
|
@ -406,7 +350,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getEmojis(keyword) {
|
getEmojis(keyword) {
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
this.customEmojis = database.objects('customEmojis').filtered('name CONTAINS[c] $0', keyword).slice(0, 4);
|
this.customEmojis = database.objects('customEmojis').filtered('name CONTAINS[c] $0', keyword).slice(0, 4);
|
||||||
this.emojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, 4);
|
this.emojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, 4);
|
||||||
|
@ -415,6 +359,149 @@ export default class MessageBox extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleFilesActions = () => {
|
||||||
|
this.setState(prevState => ({ showFilesAction: !prevState.showFilesAction }));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendImageMessage = async(file) => {
|
||||||
|
const { rid } = this.props;
|
||||||
|
|
||||||
|
this.setState({ file: { isVisible: false } });
|
||||||
|
const fileInfo = {
|
||||||
|
name: file.name,
|
||||||
|
description: file.description,
|
||||||
|
size: file.size,
|
||||||
|
type: file.mime,
|
||||||
|
store: 'Uploads',
|
||||||
|
path: file.path
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await RocketChat.sendFileMessage(rid, fileInfo);
|
||||||
|
} catch (e) {
|
||||||
|
log('sendImageMessage', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
takePhoto = async() => {
|
||||||
|
try {
|
||||||
|
const image = await ImagePicker.openCamera(imagePickerConfig);
|
||||||
|
this.showUploadModal(image);
|
||||||
|
} catch (e) {
|
||||||
|
log('takePhoto', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chooseFromLibrary = async() => {
|
||||||
|
try {
|
||||||
|
const image = await ImagePicker.openPicker(imagePickerConfig);
|
||||||
|
this.showUploadModal(image);
|
||||||
|
} catch (e) {
|
||||||
|
log('chooseFromLibrary', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showUploadModal = (file) => {
|
||||||
|
this.setState({ file: { ...file, isVisible: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
editCancel = () => {
|
||||||
|
const { editCancel } = this.props;
|
||||||
|
editCancel();
|
||||||
|
this.setState({ text: '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
openEmoji = async() => {
|
||||||
|
await this.setState({
|
||||||
|
showEmojiKeyboard: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
recordAudioMessage = async() => {
|
||||||
|
const recording = await Recording.permission();
|
||||||
|
this.setState({ recording });
|
||||||
|
}
|
||||||
|
|
||||||
|
finishAudioMessage = async(fileInfo) => {
|
||||||
|
const { rid } = this.props;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
recording: false
|
||||||
|
});
|
||||||
|
if (fileInfo) {
|
||||||
|
try {
|
||||||
|
await RocketChat.sendFileMessage(rid, fileInfo);
|
||||||
|
} catch (e) {
|
||||||
|
if (e && e.error === 'error-file-too-large') {
|
||||||
|
return Alert.alert(I18n.t(e.error));
|
||||||
|
}
|
||||||
|
log('finishAudioMessage', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeEmoji = () => {
|
||||||
|
this.setState({ showEmojiKeyboard: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
submit = async(message) => {
|
||||||
|
const {
|
||||||
|
typing, message: editingMessage, editRequest, onSubmit
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
this.setState({ text: '' });
|
||||||
|
this.closeEmoji();
|
||||||
|
this.stopTrackingMention();
|
||||||
|
typing(false);
|
||||||
|
if (message.trim() === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if is editing a message
|
||||||
|
const {
|
||||||
|
editing, replying
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
const { _id, rid } = editingMessage;
|
||||||
|
editRequest({ _id, msg: message, rid });
|
||||||
|
} else if (replying) {
|
||||||
|
const {
|
||||||
|
username, replyMessage, roomType, closeReply
|
||||||
|
} = this.props;
|
||||||
|
const permalink = await this.getPermalink(replyMessage);
|
||||||
|
let msg = `[ ](${ permalink }) `;
|
||||||
|
|
||||||
|
// if original message wasn't sent by current user and neither from a direct room
|
||||||
|
if (username !== replyMessage.u.username && roomType !== 'd' && replyMessage.mention) {
|
||||||
|
msg += `@${ replyMessage.u.username } `;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = `${ msg } ${ message }`;
|
||||||
|
onSubmit(msg);
|
||||||
|
closeReply();
|
||||||
|
} else {
|
||||||
|
// if is submiting a new message
|
||||||
|
onSubmit(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMentions = (keyword, type) => {
|
||||||
|
if (type === MENTIONS_TRACKING_TYPE_USERS) {
|
||||||
|
this.getUsers(keyword);
|
||||||
|
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
|
||||||
|
this.getEmojis(keyword);
|
||||||
|
} else {
|
||||||
|
this.getRooms(keyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
identifyMentionKeyword(keyword, type) {
|
||||||
|
this.setState({
|
||||||
|
showEmojiKeyboard: false,
|
||||||
|
trackingType: type
|
||||||
|
});
|
||||||
|
this.updateMentions(keyword, type);
|
||||||
|
}
|
||||||
|
|
||||||
stopTrackingMention() {
|
stopTrackingMention() {
|
||||||
this.setState({
|
this.setState({
|
||||||
mentions: [],
|
mentions: [],
|
||||||
|
@ -426,76 +513,26 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.emojis = [];
|
this.emojis = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
identifyMentionKeyword(keyword, type) {
|
|
||||||
this.setState({
|
|
||||||
showEmojiKeyboard: false,
|
|
||||||
trackingType: type
|
|
||||||
});
|
|
||||||
this.updateMentions(keyword, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMentions = (keyword, type) => {
|
|
||||||
if (type === MENTIONS_TRACKING_TYPE_USERS) {
|
|
||||||
this._getUsers(keyword);
|
|
||||||
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
|
|
||||||
this._getEmojis(keyword);
|
|
||||||
} else {
|
|
||||||
this._getRooms(keyword);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onPressMention(item) {
|
|
||||||
const msg = this.component._lastNativeText;
|
|
||||||
|
|
||||||
const { start, end } = this.component._lastNativeSelection;
|
|
||||||
|
|
||||||
const cursor = Math.max(start, end);
|
|
||||||
|
|
||||||
const regexp = /([a-z0-9._-]+)$/im;
|
|
||||||
|
|
||||||
const result = msg.substr(0, cursor).replace(regexp, '');
|
|
||||||
const mentionName = this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
|
|
||||||
`${ item.name || item }:` : (item.username || item.name);
|
|
||||||
const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
|
|
||||||
this.component.setNativeProps({ text });
|
|
||||||
this.setState({ text });
|
|
||||||
this.component.focus();
|
|
||||||
requestAnimationFrame(() => this.stopTrackingMention());
|
|
||||||
}
|
|
||||||
_onEmojiSelected(keyboardId, params) {
|
|
||||||
const { text } = this.state;
|
|
||||||
const { emoji } = params;
|
|
||||||
let newText = '';
|
|
||||||
|
|
||||||
// if messagebox has an active cursor
|
|
||||||
if (this.component._lastNativeSelection) {
|
|
||||||
const { start, end } = this.component._lastNativeSelection;
|
|
||||||
const cursor = Math.max(start, end);
|
|
||||||
newText = `${ text.substr(0, cursor) }${ emoji }${ text.substr(cursor) }`;
|
|
||||||
} else {
|
|
||||||
// if messagebox doesn't have a cursor, just append selected emoji
|
|
||||||
newText = `${ text }${ emoji }`;
|
|
||||||
}
|
|
||||||
this.component.setNativeProps({ text: newText });
|
|
||||||
this.setState({ text: newText });
|
|
||||||
}
|
|
||||||
renderFixedMentionItem = item => (
|
renderFixedMentionItem = item => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.mentionItem}
|
style={styles.mentionItem}
|
||||||
onPress={() => this._onPressMention(item)}
|
onPress={() => this.onPressMention(item)}
|
||||||
>
|
>
|
||||||
<Text style={styles.fixedMentionAvatar}>{item.username}</Text>
|
<Text style={styles.fixedMentionAvatar}>{item.username}</Text>
|
||||||
<Text>{item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')}</Text>
|
<Text>{item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderMentionEmoji = (item) => {
|
renderMentionEmoji = (item) => {
|
||||||
|
const { baseUrl } = this.props;
|
||||||
|
|
||||||
if (item.name) {
|
if (item.name) {
|
||||||
return (
|
return (
|
||||||
<CustomEmoji
|
<CustomEmoji
|
||||||
key='mention-item-avatar'
|
key='mention-item-avatar'
|
||||||
style={styles.mentionItemCustomEmoji}
|
style={styles.mentionItemCustomEmoji}
|
||||||
emoji={item}
|
emoji={item}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -508,18 +545,22 @@ export default class MessageBox extends React.PureComponent {
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMentionItem = (item) => {
|
renderMentionItem = (item) => {
|
||||||
|
const { trackingType } = this.state;
|
||||||
|
const { baseUrl } = this.props;
|
||||||
|
|
||||||
if (item.username === 'all' || item.username === 'here') {
|
if (item.username === 'all' || item.username === 'here') {
|
||||||
return this.renderFixedMentionItem(item);
|
return this.renderFixedMentionItem(item);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.mentionItem}
|
style={styles.mentionItem}
|
||||||
onPress={() => this._onPressMention(item)}
|
onPress={() => this.onPressMention(item)}
|
||||||
testID={`mention-item-${ this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ? item.name || item : item.username || item.name }`}
|
testID={`mention-item-${ trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ? item.name || item : item.username || item.name }`}
|
||||||
>
|
>
|
||||||
{this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
|
{trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
|
||||||
[
|
? [
|
||||||
this.renderMentionEmoji(item),
|
this.renderMentionEmoji(item),
|
||||||
<Text key='mention-item-name'>:{ item.name || item }:</Text>
|
<Text key='mention-item-name'>:{ item.name || item }:</Text>
|
||||||
]
|
]
|
||||||
|
@ -530,7 +571,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
text={item.username || item.name}
|
text={item.username || item.name}
|
||||||
size={30}
|
size={30}
|
||||||
type={item.username ? 'd' : 'c'}
|
type={item.username ? 'd' : 'c'}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
/>,
|
/>,
|
||||||
<Text key='mention-item-name'>{ item.username || item.name }</Text>
|
<Text key='mention-item-name'>{ item.username || item.name }</Text>
|
||||||
]
|
]
|
||||||
|
@ -538,6 +579,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMentions = () => {
|
renderMentions = () => {
|
||||||
const { mentions, trackingType } = this.state;
|
const { mentions, trackingType } = this.state;
|
||||||
if (!trackingType) {
|
if (!trackingType) {
|
||||||
|
@ -567,7 +609,9 @@ export default class MessageBox extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderFilesActions = () => {
|
renderFilesActions = () => {
|
||||||
if (!this.state.showFilesAction) {
|
const { showFilesAction } = this.state;
|
||||||
|
|
||||||
|
if (!showFilesAction) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -581,7 +625,10 @@ export default class MessageBox extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
if (this.state.recording) {
|
const { recording, text } = this.state;
|
||||||
|
const { editing } = this.props;
|
||||||
|
|
||||||
|
if (recording) {
|
||||||
return (<Recording onFinish={this.finishAudioMessage} />);
|
return (<Recording onFinish={this.finishAudioMessage} />);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -590,7 +637,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
<View style={styles.composer} key='messagebox'>
|
<View style={styles.composer} key='messagebox'>
|
||||||
{this.renderReplyPreview()}
|
{this.renderReplyPreview()}
|
||||||
<View
|
<View
|
||||||
style={[styles.textArea, this.props.editing && styles.editing]}
|
style={[styles.textArea, editing && styles.editing]}
|
||||||
testID='messagebox'
|
testID='messagebox'
|
||||||
>
|
>
|
||||||
{this.leftButtons}
|
{this.leftButtons}
|
||||||
|
@ -601,8 +648,8 @@ export default class MessageBox extends React.PureComponent {
|
||||||
keyboardType='twitter'
|
keyboardType='twitter'
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
placeholder={I18n.t('New_Message')}
|
placeholder={I18n.t('New_Message')}
|
||||||
onChangeText={text => this.onChangeText(text)}
|
onChangeText={t => this.onChangeText(t)}
|
||||||
value={this.state.text}
|
value={text}
|
||||||
underlineColorAndroid='transparent'
|
underlineColorAndroid='transparent'
|
||||||
defaultValue=''
|
defaultValue=''
|
||||||
multiline
|
multiline
|
||||||
|
@ -617,15 +664,16 @@ export default class MessageBox extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { showEmojiKeyboard, file } = this.state;
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
<KeyboardAccessoryView
|
<KeyboardAccessoryView
|
||||||
key='input'
|
key='input'
|
||||||
renderContent={() => this.renderContent()}
|
renderContent={() => this.renderContent()}
|
||||||
kbInputRef={this.component}
|
kbInputRef={this.component}
|
||||||
kbComponent={this.state.showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
||||||
onKeyboardResigned={() => this.onKeyboardResigned()}
|
onKeyboardResigned={() => this.onKeyboardResigned()}
|
||||||
onItemSelected={this._onEmojiSelected}
|
onItemSelected={this.onEmojiSelected}
|
||||||
trackInteractive
|
trackInteractive
|
||||||
// revealKeyboardInteractive
|
// revealKeyboardInteractive
|
||||||
requiresSameParentToManageScrollView
|
requiresSameParentToManageScrollView
|
||||||
|
@ -634,8 +682,8 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.renderFilesActions(),
|
this.renderFilesActions(),
|
||||||
<UploadModal
|
<UploadModal
|
||||||
key='upload-modal'
|
key='upload-modal'
|
||||||
isVisible={(this.state.file && this.state.file.isVisible)}
|
isVisible={(file && file.isVisible)}
|
||||||
file={this.state.file}
|
file={file}
|
||||||
close={() => this.setState({ file: {} })}
|
close={() => this.setState({ file: {} })}
|
||||||
submit={this.sendImageMessage}
|
submit={this.sendImageMessage}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ActionSheet from 'react-native-actionsheet';
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
|
|
||||||
import { errorActionsHide } from '../actions/messages';
|
import { errorActionsHide as errorActionsHideAction } from '../actions/messages';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||||
|
@ -14,7 +14,7 @@ import I18n from '../i18n';
|
||||||
actionMessage: state.messages.actionMessage
|
actionMessage: state.messages.actionMessage
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
errorActionsHide: () => dispatch(errorActionsHide())
|
errorActionsHide: () => dispatch(errorActionsHideAction())
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
export default class MessageErrorActions extends React.Component {
|
export default class MessageErrorActions extends React.Component {
|
||||||
|
@ -23,6 +23,7 @@ export default class MessageErrorActions extends React.Component {
|
||||||
actionMessage: PropTypes.object
|
actionMessage: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleActionPress = this.handleActionPress.bind(this);
|
this.handleActionPress = this.handleActionPress.bind(this);
|
||||||
|
@ -35,16 +36,21 @@ export default class MessageErrorActions extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResend = protectedFunction(() => RocketChat.resendMessage(this.props.actionMessage._id));
|
handleResend = protectedFunction(() => {
|
||||||
|
const { actionMessage } = this.props;
|
||||||
|
RocketChat.resendMessage(actionMessage._id);
|
||||||
|
});
|
||||||
|
|
||||||
handleDelete = protectedFunction(() => {
|
handleDelete = protectedFunction(() => {
|
||||||
|
const { actionMessage } = this.props;
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
const msg = database.objects('messages').filtered('_id = $0', this.props.actionMessage._id);
|
const msg = database.objects('messages').filtered('_id = $0', actionMessage._id);
|
||||||
database.delete(msg);
|
database.delete(msg);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
handleActionPress = (actionIndex) => {
|
handleActionPress = (actionIndex) => {
|
||||||
|
const { errorActionsHide } = this.props;
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
case this.RESEND_INDEX:
|
case this.RESEND_INDEX:
|
||||||
this.handleResend();
|
this.handleResend();
|
||||||
|
@ -55,7 +61,7 @@ export default class MessageErrorActions extends React.Component {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.props.errorActionsHide();
|
errorActionsHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, StyleSheet, Image, TextInput, Platform } from 'react-native';
|
import {
|
||||||
|
View, StyleSheet, Image, TextInput, Platform
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
|
|
||||||
import { appStart } from '../actions';
|
import { appStart as appStartAction } from '../actions';
|
||||||
import { logout } from '../actions/login';
|
import { logout as logoutAction } from '../actions/login';
|
||||||
import Avatar from '../containers/Avatar';
|
import Avatar from './Avatar';
|
||||||
import Status from '../containers/status';
|
import Status from './status';
|
||||||
import Touch from '../utils/touch';
|
import Touch from '../utils/touch';
|
||||||
import { STATUS_COLORS } from '../constants/colors';
|
import { STATUS_COLORS } from '../constants/colors';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
@ -87,8 +89,8 @@ const keyExtractor = item => item.id;
|
||||||
},
|
},
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
logout: () => dispatch(logout()),
|
logout: () => dispatch(logoutAction()),
|
||||||
appStart: () => dispatch(appStart('outside'))
|
appStart: () => dispatch(appStartAction('outside'))
|
||||||
}))
|
}))
|
||||||
export default class Sidebar extends Component {
|
export default class Sidebar extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -112,7 +114,8 @@ export default class Sidebar extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.user && this.props.user && this.props.user.language !== nextProps.user.language) {
|
const { user } = this.props;
|
||||||
|
if (nextProps.user && user && user.language !== nextProps.user.language) {
|
||||||
this.setStatus();
|
this.setStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +141,8 @@ export default class Sidebar extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDrawer = () => {
|
closeDrawer = () => {
|
||||||
this.props.navigator.toggleDrawer({
|
const { navigator } = this.props;
|
||||||
|
navigator.toggleDrawer({
|
||||||
side: 'left',
|
side: 'left',
|
||||||
animated: true,
|
animated: true,
|
||||||
to: 'close'
|
to: 'close'
|
||||||
|
@ -147,7 +151,7 @@ export default class Sidebar extends Component {
|
||||||
|
|
||||||
toggleStatus = () => {
|
toggleStatus = () => {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
this.setState({ showStatus: !this.state.showStatus });
|
this.setState(prevState => ({ showStatus: !prevState.showStatus }));
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebarNavigate = (screen, title) => {
|
sidebarNavigate = (screen, title) => {
|
||||||
|
@ -178,67 +182,79 @@ export default class Sidebar extends Component {
|
||||||
</Touch>
|
</Touch>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderStatusItem = ({ item }) => (
|
renderStatusItem = ({ item }) => {
|
||||||
this.renderItem({
|
const { user } = this.props;
|
||||||
text: item.name,
|
return (
|
||||||
left: <View style={[styles.status, { backgroundColor: STATUS_COLORS[item.id] }]} />,
|
this.renderItem({
|
||||||
selected: this.props.user.status === item.id,
|
text: item.name,
|
||||||
onPress: () => {
|
left: <View style={[styles.status, { backgroundColor: STATUS_COLORS[item.id] }]} />,
|
||||||
this.closeDrawer();
|
selected: user.status === item.id,
|
||||||
this.toggleStatus();
|
onPress: () => {
|
||||||
if (this.props.user.status !== item.id) {
|
this.closeDrawer();
|
||||||
try {
|
this.toggleStatus();
|
||||||
RocketChat.setUserPresenceDefaultStatus(item.id);
|
if (user.status !== item.id) {
|
||||||
} catch (e) {
|
try {
|
||||||
log('setUserPresenceDefaultStatus', e);
|
RocketChat.setUserPresenceDefaultStatus(item.id);
|
||||||
|
} catch (e) {
|
||||||
|
log('setUserPresenceDefaultStatus', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
renderNavigation = () => (
|
|
||||||
[
|
|
||||||
this.renderItem({
|
|
||||||
text: I18n.t('Chats'),
|
|
||||||
left: <Icon name='chat-bubble' size={20} />,
|
|
||||||
onPress: () => this.sidebarNavigate('RoomsListView', I18n.t('Messages')),
|
|
||||||
testID: 'sidebar-chats'
|
|
||||||
}),
|
|
||||||
this.renderItem({
|
|
||||||
text: I18n.t('Profile'),
|
|
||||||
left: <Icon name='person' size={20} />,
|
|
||||||
onPress: () => this.sidebarNavigate('ProfileView', I18n.t('Profile')),
|
|
||||||
testID: 'sidebar-profile'
|
|
||||||
}),
|
|
||||||
this.renderItem({
|
|
||||||
text: I18n.t('Settings'),
|
|
||||||
left: <Icon name='settings' size={20} />,
|
|
||||||
onPress: () => this.sidebarNavigate('SettingsView', I18n.t('Settings')),
|
|
||||||
testID: 'sidebar-settings'
|
|
||||||
}),
|
|
||||||
this.renderSeparator('separator-logout'),
|
|
||||||
this.renderItem({
|
|
||||||
text: I18n.t('Logout'),
|
|
||||||
left: <Icon name='exit-to-app' size={20} />,
|
|
||||||
onPress: () => this.props.logout(),
|
|
||||||
testID: 'sidebar-logout'
|
|
||||||
})
|
})
|
||||||
]
|
);
|
||||||
)
|
}
|
||||||
|
|
||||||
renderStatus = () => (
|
renderNavigation = () => {
|
||||||
<FlatList
|
const { logout } = this.props;
|
||||||
key='status-list'
|
return (
|
||||||
data={this.state.status}
|
[
|
||||||
extraData={this.props.user}
|
this.renderItem({
|
||||||
renderItem={this.renderStatusItem}
|
text: I18n.t('Chats'),
|
||||||
keyExtractor={keyExtractor}
|
left: <Icon name='chat-bubble' size={20} />,
|
||||||
/>
|
onPress: () => this.sidebarNavigate('RoomsListView', I18n.t('Messages')),
|
||||||
)
|
testID: 'sidebar-chats'
|
||||||
|
}),
|
||||||
|
this.renderItem({
|
||||||
|
text: I18n.t('Profile'),
|
||||||
|
left: <Icon name='person' size={20} />,
|
||||||
|
onPress: () => this.sidebarNavigate('ProfileView', I18n.t('Profile')),
|
||||||
|
testID: 'sidebar-profile'
|
||||||
|
}),
|
||||||
|
this.renderItem({
|
||||||
|
text: I18n.t('Settings'),
|
||||||
|
left: <Icon name='settings' size={20} />,
|
||||||
|
onPress: () => this.sidebarNavigate('SettingsView', I18n.t('Settings')),
|
||||||
|
testID: 'sidebar-settings'
|
||||||
|
}),
|
||||||
|
this.renderSeparator('separator-logout'),
|
||||||
|
this.renderItem({
|
||||||
|
text: I18n.t('Logout'),
|
||||||
|
left: <Icon name='exit-to-app' size={20} />,
|
||||||
|
onPress: () => logout(),
|
||||||
|
testID: 'sidebar-logout'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStatus = () => {
|
||||||
|
const { status } = this.state;
|
||||||
|
const { user } = this.props;
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
key='status-list'
|
||||||
|
data={status}
|
||||||
|
extraData={user}
|
||||||
|
renderItem={this.renderStatusItem}
|
||||||
|
keyExtractor={keyExtractor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { showStatus } = this.state;
|
||||||
const { user, server, baseUrl } = this.props;
|
const { user, server, baseUrl } = this.props;
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -266,7 +282,7 @@ export default class Sidebar extends Component {
|
||||||
<Text style={styles.currentServerText} numberOfLines={1}>{server}</Text>
|
<Text style={styles.currentServerText} numberOfLines={1}>{server}</Text>
|
||||||
</View>
|
</View>
|
||||||
<Icon
|
<Icon
|
||||||
name={this.state.showStatus ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
|
name={showStatus ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
|
||||||
size={30}
|
size={30}
|
||||||
style={{ paddingHorizontal: 10 }}
|
style={{ paddingHorizontal: 10 }}
|
||||||
/>
|
/>
|
||||||
|
@ -275,8 +291,8 @@ export default class Sidebar extends Component {
|
||||||
|
|
||||||
{this.renderSeparator('separator-header')}
|
{this.renderSeparator('separator-header')}
|
||||||
|
|
||||||
{!this.state.showStatus ? this.renderNavigation() : null}
|
{!showStatus ? this.renderNavigation() : null}
|
||||||
{this.state.showStatus ? this.renderStatus() : null}
|
{showStatus ? this.renderStatus() : null}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<Text style={styles.version}>
|
<Text style={styles.version}>
|
||||||
{DeviceInfo.getReadableVersion()}
|
{DeviceInfo.getReadableVersion()}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, StyleSheet, Text, TextInput, ViewPropTypes, Platform } from 'react-native';
|
import {
|
||||||
|
View, StyleSheet, Text, TextInput, ViewPropTypes, Platform
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
|
||||||
|
@ -68,9 +70,11 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
iconLeft: PropTypes.string,
|
iconLeft: PropTypes.string,
|
||||||
placeholder: PropTypes.string
|
placeholder: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
error: {}
|
error: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
showPassword: false
|
showPassword: false
|
||||||
}
|
}
|
||||||
|
@ -82,21 +86,30 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
testID
|
testID
|
||||||
}) => <Icon name={name} style={[styles.icon, style]} size={20} onPress={onPress} testID={testID} />
|
}) => <Icon name={name} style={[styles.icon, style]} size={20} onPress={onPress} testID={testID} />
|
||||||
|
|
||||||
iconLeft = name => this.icon({
|
iconLeft = (name) => {
|
||||||
name,
|
const { testID } = this.props;
|
||||||
onPress: null,
|
return this.icon({
|
||||||
style: { left: 0 },
|
name,
|
||||||
testID: this.props.testID ? `${ this.props.testID }-icon-left` : null
|
onPress: null,
|
||||||
});
|
style: { left: 0 },
|
||||||
|
testID: testID ? `${ testID }-icon-left` : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
iconPassword = name => this.icon({
|
iconPassword = (name) => {
|
||||||
name,
|
const { testID } = this.props;
|
||||||
onPress: () => this.tooglePassword(),
|
return this.icon({
|
||||||
style: { right: 0 },
|
name,
|
||||||
testID: this.props.testID ? `${ this.props.testID }-icon-right` : null
|
onPress: () => this.tooglePassword(),
|
||||||
});
|
style: { right: 0 },
|
||||||
|
testID: testID ? `${ testID }-icon-right` : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
tooglePassword = () => this.setState({ showPassword: !this.state.showPassword });
|
tooglePassword = () => {
|
||||||
|
const { showPassword } = this.state;
|
||||||
|
this.setState({ showPassword: !showPassword });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, StyleSheet, Text, Keyboard, LayoutAnimation } from 'react-native';
|
import {
|
||||||
|
View, StyleSheet, Text, Keyboard, LayoutAnimation
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
|
||||||
|
@ -22,18 +24,24 @@ const styles = StyleSheet.create({
|
||||||
}))
|
}))
|
||||||
export default class Typing extends React.Component {
|
export default class Typing extends React.Component {
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return this.props.usersTyping.join() !== nextProps.usersTyping.join();
|
const { usersTyping } = this.props;
|
||||||
|
return usersTyping.join() !== nextProps.usersTyping.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate() {
|
componentWillUpdate() {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
get usersTyping() {
|
get usersTyping() {
|
||||||
const users = this.props.usersTyping.filter(_username => this.props.username !== _username);
|
const { usersTyping, username } = this.props;
|
||||||
|
const users = usersTyping.filter(_username => username !== _username);
|
||||||
return users.length ? `${ users.join(', ') } ${ users.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }` : '';
|
return users.length ? `${ users.join(', ') } ${ users.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }` : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { usersTyping } = this;
|
const { usersTyping } = this;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, StyleSheet, Text, Easing, Image } from 'react-native';
|
import {
|
||||||
|
View, StyleSheet, Text, Easing, Image
|
||||||
|
} from 'react-native';
|
||||||
import Video from 'react-native-video';
|
import Video from 'react-native-video';
|
||||||
import Slider from 'react-native-slider';
|
import Slider from 'react-native-slider';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
@ -72,7 +74,8 @@ export default class Audio extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgress(data) {
|
onProgress(data) {
|
||||||
if (data.currentTime <= this.state.duration) {
|
const { duration } = this.state;
|
||||||
|
if (data.currentTime <= duration) {
|
||||||
this.setState({ currentTime: data.currentTime });
|
this.setState({ currentTime: data.currentTime });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,15 +88,19 @@ export default class Audio extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
getDuration() {
|
getDuration() {
|
||||||
return formatTime(this.state.duration);
|
const { duration } = this.state;
|
||||||
|
return formatTime(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePlayPause() {
|
togglePlayPause() {
|
||||||
this.setState({ paused: !this.state.paused });
|
const { paused } = this.state;
|
||||||
|
this.setState({ paused: !paused });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { uri, paused } = this.state;
|
const {
|
||||||
|
uri, paused, currentTime, duration
|
||||||
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
user, baseUrl, customEmojis, file
|
user, baseUrl, customEmojis, file
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -122,15 +129,15 @@ export default class Audio extends React.PureComponent {
|
||||||
onPress={() => this.togglePlayPause()}
|
onPress={() => this.togglePlayPause()}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
paused ?
|
paused
|
||||||
<Image source={{ uri: 'play' }} style={styles.playPauseImage} /> :
|
? <Image source={{ uri: 'play' }} style={styles.playPauseImage} />
|
||||||
<Image source={{ uri: 'pause' }} style={styles.playPauseImage} />
|
: <Image source={{ uri: 'pause' }} style={styles.playPauseImage} />
|
||||||
}
|
}
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
<Slider
|
<Slider
|
||||||
style={styles.slider}
|
style={styles.slider}
|
||||||
value={this.state.currentTime}
|
value={currentTime}
|
||||||
maximumValue={this.state.duration}
|
maximumValue={duration}
|
||||||
minimumValue={0}
|
minimumValue={0}
|
||||||
animateTransitions
|
animateTransitions
|
||||||
animationConfig={{
|
animationConfig={{
|
||||||
|
|
|
@ -14,7 +14,8 @@ export default class Emoji extends React.PureComponent {
|
||||||
PropTypes.array,
|
PropTypes.array,
|
||||||
PropTypes.object
|
PropTypes.object
|
||||||
])
|
])
|
||||||
};
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
content, standardEmojiStyle, customEmojiStyle, customEmojis, baseUrl
|
content, standardEmojiStyle, customEmojiStyle, customEmojis, baseUrl
|
||||||
|
|
|
@ -40,7 +40,7 @@ export default class extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isPressed } = this.state;
|
const { modalVisible, isPressed } = this.state;
|
||||||
const { baseUrl, file, user } = this.props;
|
const { baseUrl, file, user } = this.props;
|
||||||
const img = `${ baseUrl }${ file.image_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
const img = `${ baseUrl }${ file.image_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ export default class extends React.PureComponent {
|
||||||
title={file.title}
|
title={file.title}
|
||||||
description={file.description}
|
description={file.description}
|
||||||
image={img}
|
image={img}
|
||||||
isVisible={this.state.modalVisible}
|
isVisible={modalVisible}
|
||||||
onClose={() => this.setState({ modalVisible: false })}
|
onClose={() => this.setState({ modalVisible: false })}
|
||||||
/>
|
/>
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,16 +9,17 @@ import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
import MarkdownEmojiPlugin from './MarkdownEmojiPlugin';
|
import MarkdownEmojiPlugin from './MarkdownEmojiPlugin';
|
||||||
|
|
||||||
// Support <http://link|Text>
|
// Support <http://link|Text>
|
||||||
const formatText = text =>
|
const formatText = text => text.replace(
|
||||||
text.replace(
|
new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
|
||||||
new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
|
(match, url, title) => `[${ title }](${ url })`
|
||||||
(match, url, title) => `[${ title }](${ url })`
|
);
|
||||||
);
|
|
||||||
|
|
||||||
export default class Markdown extends React.Component {
|
export default class Markdown extends React.Component {
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return nextProps.msg !== this.props.msg;
|
const { msg } = this.props;
|
||||||
|
return nextProps.msg !== msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
msg, customEmojis, style, rules, baseUrl, username, edited
|
msg, customEmojis, style, rules, baseUrl, username, edited
|
||||||
|
@ -33,8 +34,10 @@ export default class Markdown extends React.Component {
|
||||||
<MarkdownRenderer
|
<MarkdownRenderer
|
||||||
rules={{
|
rules={{
|
||||||
paragraph: (node, children) => (
|
paragraph: (node, children) => (
|
||||||
|
// eslint-disable-next-line
|
||||||
<Text key={node.key} style={styles.paragraph}>
|
<Text key={node.key} style={styles.paragraph}>
|
||||||
{children}{edited ? <Text style={styles.edited}> (edited)</Text> : null}
|
{children}
|
||||||
|
{edited ? <Text style={styles.edited}> (edited)</Text> : null}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
mention: (node) => {
|
mention: (node) => {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, ViewPropTypes, Image as ImageRN } from 'react-native';
|
import {
|
||||||
|
View, Text, ViewPropTypes, Image as ImageRN
|
||||||
|
} from 'react-native';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { KeyboardUtils } from 'react-native-keyboard-input';
|
import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||||
|
@ -137,22 +139,29 @@ export default class Message extends PureComponent {
|
||||||
KeyboardUtils.dismiss();
|
KeyboardUtils.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
isInfoMessage() {
|
isInfoMessage = () => {
|
||||||
return SYSTEM_MESSAGES.includes(this.props.type);
|
const { type } = this.props;
|
||||||
|
return SYSTEM_MESSAGES.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
isOwn = () => this.props.author._id === this.props.user.id;
|
isOwn = () => {
|
||||||
|
const { author, user } = this.props;
|
||||||
|
return author._id === user.id;
|
||||||
|
}
|
||||||
|
|
||||||
isDeleted() {
|
isDeleted() {
|
||||||
return this.props.type === 'rm';
|
const { type } = this.props;
|
||||||
|
return type === 'rm';
|
||||||
}
|
}
|
||||||
|
|
||||||
isTemp() {
|
isTemp() {
|
||||||
return this.props.status === messagesStatus.TEMP || this.props.status === messagesStatus.ERROR;
|
const { status } = this.props;
|
||||||
|
return status === messagesStatus.TEMP || status === messagesStatus.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasError() {
|
hasError() {
|
||||||
return this.props.status === messagesStatus.ERROR;
|
const { status } = this.props;
|
||||||
|
return status === messagesStatus.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAvatar = () => {
|
renderAvatar = () => {
|
||||||
|
@ -242,19 +251,23 @@ export default class Message extends PureComponent {
|
||||||
if (!this.hasError()) {
|
if (!this.hasError()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <Icon name='error-outline' color='red' size={20} style={styles.errorIcon} onPress={this.props.onErrorPress} />;
|
const { onErrorPress } = this.props;
|
||||||
|
return <Icon name='error-outline' color='red' size={20} style={styles.errorIcon} onPress={onErrorPress} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderReaction = (reaction) => {
|
renderReaction = (reaction) => {
|
||||||
const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
|
const {
|
||||||
|
user, onReactionLongPress, onReactionPress, customEmojis, baseUrl
|
||||||
|
} = this.props;
|
||||||
|
const reacted = reaction.usernames.findIndex(item => item.value === user.username) !== -1;
|
||||||
const underlayColor = reacted ? '#fff' : '#e1e5e8';
|
const underlayColor = reacted ? '#fff' : '#e1e5e8';
|
||||||
return (
|
return (
|
||||||
<LongPressGestureHandler
|
<LongPressGestureHandler
|
||||||
key={reaction.emoji}
|
key={reaction.emoji}
|
||||||
onHandlerStateChange={({ nativeEvent }) => nativeEvent.state === State.ACTIVE && this.props.onReactionLongPress()}
|
onHandlerStateChange={({ nativeEvent }) => nativeEvent.state === State.ACTIVE && onReactionLongPress()}
|
||||||
>
|
>
|
||||||
<RectButton
|
<RectButton
|
||||||
onPress={() => this.props.onReactionPress(reaction.emoji)}
|
onPress={() => onReactionPress(reaction.emoji)}
|
||||||
testID={`message-reaction-${ reaction.emoji }`}
|
testID={`message-reaction-${ reaction.emoji }`}
|
||||||
style={[styles.reactionButton, reacted && { backgroundColor: '#e8f2ff' }]}
|
style={[styles.reactionButton, reacted && { backgroundColor: '#e8f2ff' }]}
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
|
@ -263,10 +276,10 @@ export default class Message extends PureComponent {
|
||||||
<View style={[styles.reactionContainer, reacted && styles.reactedContainer]}>
|
<View style={[styles.reactionContainer, reacted && styles.reactedContainer]}>
|
||||||
<Emoji
|
<Emoji
|
||||||
content={reaction.emoji}
|
content={reaction.emoji}
|
||||||
customEmojis={this.props.customEmojis}
|
customEmojis={customEmojis}
|
||||||
standardEmojiStyle={styles.reactionEmoji}
|
standardEmojiStyle={styles.reactionEmoji}
|
||||||
customEmojiStyle={styles.reactionCustomEmoji}
|
customEmojiStyle={styles.reactionCustomEmoji}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.reactionCount}>{ reaction.usernames.length }</Text>
|
<Text style={styles.reactionCount}>{ reaction.usernames.length }</Text>
|
||||||
</View>
|
</View>
|
||||||
|
@ -276,7 +289,7 @@ export default class Message extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderReactions() {
|
renderReactions() {
|
||||||
const { reactions } = this.props;
|
const { reactions, toggleReactionPicker } = this.props;
|
||||||
if (reactions.length === 0) {
|
if (reactions.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -284,7 +297,7 @@ export default class Message extends PureComponent {
|
||||||
<View style={styles.reactionsContainer}>
|
<View style={styles.reactionsContainer}>
|
||||||
{reactions.map(this.renderReaction)}
|
{reactions.map(this.renderReaction)}
|
||||||
<RectButton
|
<RectButton
|
||||||
onPress={this.props.toggleReactionPicker}
|
onPress={toggleReactionPicker}
|
||||||
key='message-add-reaction'
|
key='message-add-reaction'
|
||||||
testID='message-add-reaction'
|
testID='message-add-reaction'
|
||||||
style={styles.reactionButton}
|
style={styles.reactionButton}
|
||||||
|
@ -300,10 +313,11 @@ export default class Message extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBroadcastReply() {
|
renderBroadcastReply() {
|
||||||
if (this.props.broadcast && !this.isOwn()) {
|
const { broadcast, replyBroadcast } = this.props;
|
||||||
|
if (broadcast && !this.isOwn()) {
|
||||||
return (
|
return (
|
||||||
<RectButton
|
<RectButton
|
||||||
onPress={this.props.replyBroadcast}
|
onPress={replyBroadcast}
|
||||||
style={styles.broadcastButton}
|
style={styles.broadcastButton}
|
||||||
activeOpacity={0.5}
|
activeOpacity={0.5}
|
||||||
underlayColor='#fff'
|
underlayColor='#fff'
|
||||||
|
@ -349,15 +363,17 @@ export default class Message extends PureComponent {
|
||||||
{this.renderBroadcastReply()}
|
{this.renderBroadcastReply()}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{reactionsModal ?
|
{reactionsModal
|
||||||
<ReactionsModal
|
? (
|
||||||
isVisible={reactionsModal}
|
<ReactionsModal
|
||||||
reactions={reactions}
|
isVisible={reactionsModal}
|
||||||
user={user}
|
reactions={reactions}
|
||||||
customEmojis={customEmojis}
|
user={user}
|
||||||
baseUrl={baseUrl}
|
customEmojis={customEmojis}
|
||||||
close={closeReactions}
|
baseUrl={baseUrl}
|
||||||
/>
|
close={closeReactions}
|
||||||
|
/>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableWithoutFeedback, ActivityIndicator, StyleSheet } from 'react-native';
|
import {
|
||||||
|
View, Text, TouchableWithoutFeedback, ActivityIndicator, StyleSheet
|
||||||
|
} from 'react-native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
|
@ -45,6 +47,7 @@ export default class PhotoModal extends React.PureComponent {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
window: PropTypes.object
|
window: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
image, isVisible, onClose, title, description, window: { width, height }
|
image, isVisible, onClose, title, description, window: { width, height }
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableWithoutFeedback, FlatList, StyleSheet } from 'react-native';
|
import {
|
||||||
|
View, Text, TouchableWithoutFeedback, FlatList, StyleSheet
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
|
@ -66,10 +68,12 @@ export default class ReactionsModal extends React.PureComponent {
|
||||||
PropTypes.object
|
PropTypes.object
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = (item) => {
|
renderItem = (item) => {
|
||||||
|
const { user, customEmojis, baseUrl } = this.props;
|
||||||
const count = item.usernames.length;
|
const count = item.usernames.length;
|
||||||
let usernames = item.usernames.slice(0, 3)
|
let usernames = item.usernames.slice(0, 3)
|
||||||
.map(username => (username.value === this.props.user.username ? I18n.t('you') : username.value)).join(', ');
|
.map(username => (username.value === user.username ? I18n.t('you') : username.value)).join(', ');
|
||||||
if (count > 3) {
|
if (count > 3) {
|
||||||
usernames = `${ usernames } ${ I18n.t('and_more') } ${ count - 3 }`;
|
usernames = `${ usernames } ${ I18n.t('and_more') } ${ count - 3 }`;
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,8 +86,8 @@ export default class ReactionsModal extends React.PureComponent {
|
||||||
content={item.emoji}
|
content={item.emoji}
|
||||||
standardEmojiStyle={standardEmojiStyle}
|
standardEmojiStyle={standardEmojiStyle}
|
||||||
customEmojiStyle={customEmojiStyle}
|
customEmojiStyle={customEmojiStyle}
|
||||||
customEmojis={this.props.customEmojis}
|
customEmojis={customEmojis}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.peopleItemContainer}>
|
<View style={styles.peopleItemContainer}>
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default class User extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
username, alias, ts, temp
|
username, alias, ts, temp, timeFormat
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const extraStyle = {};
|
const extraStyle = {};
|
||||||
|
@ -50,7 +50,7 @@ export default class User extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
const aliasUsername = alias ? (<Text style={styles.alias}>@{username}</Text>) : null;
|
const aliasUsername = alias ? (<Text style={styles.alias}>@{username}</Text>) : null;
|
||||||
const time = moment(ts).format(this.props.timeFormat);
|
const time = moment(ts).format(timeFormat);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.usernameView}>
|
<View style={styles.usernameView}>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, Image, Platform, View } from 'react-native';
|
import {
|
||||||
|
StyleSheet, Image, Platform, View
|
||||||
|
} from 'react-native';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import VideoPlayer from 'react-native-video-controls';
|
import VideoPlayer from 'react-native-video-controls';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
|
@ -42,19 +44,20 @@ export default class Video extends React.PureComponent {
|
||||||
state = { isVisible: false };
|
state = { isVisible: false };
|
||||||
|
|
||||||
get uri() {
|
get uri() {
|
||||||
const { video_url } = this.props.file;
|
const { baseUrl, user, file } = this.props;
|
||||||
const { baseUrl, user } = this.props;
|
const { video_url } = file;
|
||||||
return `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
return `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleModal() {
|
toggleModal() {
|
||||||
this.setState({
|
this.setState(prevState => ({
|
||||||
isVisible: !this.state.isVisible
|
isVisible: !prevState.isVisible
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
if (isTypeSupported(this.props.file.video_type)) {
|
const { file } = this.props;
|
||||||
|
if (isTypeSupported(file.video_type)) {
|
||||||
return this.toggleModal();
|
return this.toggleModal();
|
||||||
}
|
}
|
||||||
openLink(this.uri);
|
openLink(this.uri);
|
||||||
|
@ -62,8 +65,10 @@ export default class Video extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isVisible } = this.state;
|
const { isVisible } = this.state;
|
||||||
const { description } = this.props.file;
|
const {
|
||||||
const { baseUrl, user, customEmojis } = this.props;
|
baseUrl, user, customEmojis, file
|
||||||
|
} = this.props;
|
||||||
|
const { description } = file;
|
||||||
|
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -5,7 +5,11 @@ import { connect } from 'react-redux';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
import { errorActionsShow, toggleReactionPicker, replyBroadcast } from '../../actions/messages';
|
import {
|
||||||
|
errorActionsShow as errorActionsShowAction,
|
||||||
|
toggleReactionPicker as toggleReactionPickerAction,
|
||||||
|
replyBroadcast as replyBroadcastAction
|
||||||
|
} from '../../actions/messages';
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
|
@ -16,9 +20,9 @@ import { errorActionsShow, toggleReactionPicker, replyBroadcast } from '../../ac
|
||||||
message: state.messages.message,
|
message: state.messages.message,
|
||||||
useRealName: state.settings.UI_Use_Real_Name
|
useRealName: state.settings.UI_Use_Real_Name
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
|
errorActionsShow: actionMessage => dispatch(errorActionsShowAction(actionMessage)),
|
||||||
replyBroadcast: message => dispatch(replyBroadcast(message)),
|
replyBroadcast: message => dispatch(replyBroadcastAction(message)),
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
|
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message))
|
||||||
}))
|
}))
|
||||||
export default class MessageContainer extends React.Component {
|
export default class MessageContainer extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -67,39 +71,47 @@ export default class MessageContainer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
if (this.state.reactionsModal !== nextState.reactionsModal) {
|
const { reactionsModal } = this.state;
|
||||||
|
const {
|
||||||
|
status, reactions, broadcast, editing, _updatedAt
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (reactionsModal !== nextState.reactionsModal) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.props.status !== nextProps.status) {
|
if (status !== nextProps.status) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
if (!!this.props._updatedAt ^ !!nextProps._updatedAt) {
|
if (!!_updatedAt ^ !!nextProps._updatedAt) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!equal(this.props.reactions, nextProps.reactions)) {
|
if (!equal(reactions, nextProps.reactions)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.props.broadcast !== nextProps.broadcast) {
|
if (broadcast !== nextProps.broadcast) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.props.editing !== nextProps.editing) {
|
if (editing !== nextProps.editing) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return this.props._updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
return _updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onLongPress = () => {
|
onLongPress = () => {
|
||||||
this.props.onLongPress(this.parseMessage());
|
const { onLongPress } = this.props;
|
||||||
|
onLongPress(this.parseMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
onErrorPress = () => {
|
onErrorPress = () => {
|
||||||
this.props.errorActionsShow(this.parseMessage());
|
const { errorActionsShow } = this.props;
|
||||||
|
errorActionsShow(this.parseMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
onReactionPress = (emoji) => {
|
onReactionPress = (emoji) => {
|
||||||
this.props.onReactionPress(emoji, this.props.item._id);
|
const { onReactionPress, item } = this.props;
|
||||||
|
onReactionPress(emoji, item._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,30 +130,38 @@ export default class MessageContainer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
isHeader = () => {
|
isHeader = () => {
|
||||||
const { item, previousItem } = this.props;
|
const {
|
||||||
|
item, previousItem, broadcast, Message_GroupingPeriod
|
||||||
|
} = this.props;
|
||||||
if (previousItem && (
|
if (previousItem && (
|
||||||
(previousItem.ts.toDateString() === item.ts.toDateString()) &&
|
(previousItem.ts.toDateString() === item.ts.toDateString())
|
||||||
(previousItem.u.username === item.u.username) &&
|
&& (previousItem.u.username === item.u.username)
|
||||||
!(previousItem.groupable === false || item.groupable === false || this.props.broadcast === true) &&
|
&& !(previousItem.groupable === false || item.groupable === false || broadcast === true)
|
||||||
(previousItem.status === item.status) &&
|
&& (previousItem.status === item.status)
|
||||||
(item.ts - previousItem.ts < this.props.Message_GroupingPeriod * 1000)
|
&& (item.ts - previousItem.ts < Message_GroupingPeriod * 1000)
|
||||||
)) {
|
)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
|
parseMessage = () => {
|
||||||
|
const { item } = this.props;
|
||||||
|
return JSON.parse(JSON.stringify(item));
|
||||||
|
}
|
||||||
|
|
||||||
toggleReactionPicker = () => {
|
toggleReactionPicker = () => {
|
||||||
this.props.toggleReactionPicker(this.parseMessage());
|
const { toggleReactionPicker } = this.props;
|
||||||
|
toggleReactionPicker(this.parseMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
replyBroadcast = () => {
|
replyBroadcast = () => {
|
||||||
this.props.replyBroadcast(this.parseMessage());
|
const { replyBroadcast } = this.props;
|
||||||
|
replyBroadcast(this.parseMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { reactionsModal } = this.state;
|
||||||
const {
|
const {
|
||||||
item, message, editing, user, style, archived, baseUrl, customEmojis, useRealName, broadcast
|
item, message, editing, user, style, archived, baseUrl, customEmojis, useRealName, broadcast
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -171,7 +191,7 @@ export default class MessageContainer extends React.Component {
|
||||||
broadcast={broadcast}
|
broadcast={broadcast}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
customEmojis={customEmojis}
|
customEmojis={customEmojis}
|
||||||
reactionsModal={this.state.reactionsModal}
|
reactionsModal={reactionsModal}
|
||||||
useRealName={useRealName}
|
useRealName={useRealName}
|
||||||
role={role}
|
role={role}
|
||||||
closeReactions={this.closeReactions}
|
closeReactions={this.closeReactions}
|
||||||
|
|
|
@ -42,6 +42,7 @@ export default class Status extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (<View style={[styles.status, this.props.style, { backgroundColor: STATUS_COLORS[this.status] }]} />);
|
const { style } = this.props;
|
||||||
|
return (<View style={[styles.status, style, { backgroundColor: STATUS_COLORS[this.status] }]} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ class EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.events = {};
|
this.events = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
on(event, listener) {
|
on(event, listener) {
|
||||||
if (typeof this.events[event] !== 'object') {
|
if (typeof this.events[event] !== 'object') {
|
||||||
this.events[event] = [];
|
this.events[event] = [];
|
||||||
|
@ -31,6 +32,7 @@ class EventEmitter {
|
||||||
this.events[event].push(listener);
|
this.events[event].push(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListener(event, listener) {
|
removeListener(event, listener) {
|
||||||
if (typeof this.events[event] === 'object') {
|
if (typeof this.events[event] === 'object') {
|
||||||
const idx = this.events[event].indexOf(listener);
|
const idx = this.events[event].indexOf(listener);
|
||||||
|
@ -42,6 +44,7 @@ class EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(event, ...args) {
|
emit(event, ...args) {
|
||||||
if (typeof this.events[event] === 'object') {
|
if (typeof this.events[event] === 'object') {
|
||||||
this.events[event].forEach((listener) => {
|
this.events[event].forEach((listener) => {
|
||||||
|
@ -53,6 +56,7 @@ class EventEmitter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
once(event, listener) {
|
once(event, listener) {
|
||||||
return this.on(event, function g(...args) {
|
return this.on(event, function g(...args) {
|
||||||
this.removeListener(event, g);
|
this.removeListener(event, g);
|
||||||
|
@ -135,6 +139,7 @@ export default class Socket extends EventEmitter {
|
||||||
|
|
||||||
this._connect().catch(e => log('ddp.constructor._connect', e));
|
this._connect().catch(e => log('ddp.constructor._connect', e));
|
||||||
}
|
}
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
if (!this.lastping) {
|
if (!this.lastping) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -144,6 +149,7 @@ export default class Socket extends EventEmitter {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(params) {
|
async login(params) {
|
||||||
try {
|
try {
|
||||||
this.emit('login', params);
|
this.emit('login', params);
|
||||||
|
@ -165,6 +171,7 @@ export default class Socket extends EventEmitter {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(obj, ignore) {
|
async send(obj, ignore) {
|
||||||
console.log('send', obj);
|
console.log('send', obj);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -187,9 +194,11 @@ export default class Socket extends EventEmitter {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get status() {
|
get status() {
|
||||||
return this.connection && this.connection.readyState === 1 && this.check() && !!this._logged;
|
return this.connection && this.connection.readyState === 1 && this.check() && !!this._logged;
|
||||||
}
|
}
|
||||||
|
|
||||||
_close() {
|
_close() {
|
||||||
try {
|
try {
|
||||||
// this.connection && this.connection.readyState > 1 && this.connection.close && this.connection.close(300, 'disconnect');
|
// this.connection && this.connection.readyState > 1 && this.connection.close && this.connection.close(300, 'disconnect');
|
||||||
|
@ -201,6 +210,7 @@ export default class Socket extends EventEmitter {
|
||||||
// console.log(e);
|
// console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_connect() {
|
_connect() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.lastping = new Date();
|
this.lastping = new Date();
|
||||||
|
@ -240,12 +250,14 @@ export default class Socket extends EventEmitter {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
this._login = null;
|
this._login = null;
|
||||||
return this.call('logout')
|
return this.call('logout')
|
||||||
.catch(e => log('logout', e))
|
.catch(e => log('logout', e))
|
||||||
.finally(() => this.subscriptions = {});
|
.finally(() => this.subscriptions = {});
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this._logged = false;
|
this._logged = false;
|
||||||
this._login = null;
|
this._login = null;
|
||||||
|
@ -256,6 +268,7 @@ export default class Socket extends EventEmitter {
|
||||||
clearTimeout(this.timeout);
|
clearTimeout(this.timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async reconnect() {
|
async reconnect() {
|
||||||
if (this._timer || this.forceDisconnect) {
|
if (this._timer || this.forceDisconnect) {
|
||||||
return;
|
return;
|
||||||
|
@ -272,6 +285,7 @@ export default class Socket extends EventEmitter {
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
call(method, ...params) {
|
call(method, ...params) {
|
||||||
return this.send({
|
return this.send({
|
||||||
msg: 'method', method, params
|
msg: 'method', method, params
|
||||||
|
@ -283,6 +297,7 @@ export default class Socket extends EventEmitter {
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe(id) {
|
unsubscribe(id) {
|
||||||
if (!this.subscriptions[id]) {
|
if (!this.subscriptions[id]) {
|
||||||
return Promise.reject(id);
|
return Promise.reject(id);
|
||||||
|
@ -296,6 +311,7 @@ export default class Socket extends EventEmitter {
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(name, ...params) {
|
subscribe(name, ...params) {
|
||||||
console.log(name, params);
|
console.log(name, params);
|
||||||
return this.send({
|
return this.send({
|
||||||
|
|
|
@ -21,9 +21,11 @@ export default async function() {
|
||||||
return permission;
|
return permission;
|
||||||
});
|
});
|
||||||
|
|
||||||
InteractionManager.runAfterInteractions(() =>
|
InteractionManager.runAfterInteractions(
|
||||||
database.write(() =>
|
() => database.write(
|
||||||
permissions.forEach(permission => database.create('permissions', permission, true))));
|
() => permissions.forEach(permission => database.create('permissions', permission, true))
|
||||||
|
)
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('getPermissions', e);
|
log('getPermissions', e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,15 +27,17 @@ export default async function() {
|
||||||
|
|
||||||
const filteredSettings = this._prepareSettings(this._filterSettings(data));
|
const filteredSettings = this._prepareSettings(this._filterSettings(data));
|
||||||
|
|
||||||
InteractionManager.runAfterInteractions(() =>
|
InteractionManager.runAfterInteractions(
|
||||||
database.write(() =>
|
() => database.write(
|
||||||
filteredSettings.forEach((setting) => {
|
() => filteredSettings.forEach((setting) => {
|
||||||
database.create('settings', { ...setting, _updatedAt: new Date() }, true);
|
database.create('settings', { ...setting, _updatedAt: new Date() }, true);
|
||||||
|
|
||||||
if (setting._id === 'Site_Name') {
|
if (setting._id === 'Site_Name') {
|
||||||
updateServer.call(this, { name: setting.valueAsString });
|
updateServer.call(this, { name: setting.valueAsString });
|
||||||
}
|
}
|
||||||
})));
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
|
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
|
||||||
|
|
||||||
const iconSetting = data.find(item => item._id === 'Assets_favicon_512');
|
const iconSetting = data.find(item => item._id === 'Assets_favicon_512');
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default async function readMessages(rid) {
|
||||||
const { database: db } = database;
|
const { database: db } = database;
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const data = await (this.ddp.status ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid));
|
const data = await (this.ddp.status && false ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid));
|
||||||
const [subscription] = db.objects('subscriptions').filtered('rid = $0', rid);
|
const [subscription] = db.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
db.write(() => {
|
db.write(() => {
|
||||||
subscription.open = true;
|
subscription.open = true;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Random from 'react-native-meteor/lib/Random';
|
import Random from 'react-native-meteor/lib/Random';
|
||||||
|
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
import buildMessage from '../methods/helpers/buildMessage';
|
import buildMessage from './helpers/buildMessage';
|
||||||
import { post } from './helpers/rest';
|
import { post } from './helpers/rest';
|
||||||
import database from '../realm';
|
import database from '../realm';
|
||||||
import reduxStore from '../createStore';
|
import reduxStore from '../createStore';
|
||||||
|
@ -46,7 +46,7 @@ function sendMessageByDDP(message) {
|
||||||
export async function _sendMessageCall(message) {
|
export async function _sendMessageCall(message) {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const data = await (this.ddp.status ? sendMessageByDDP.call(this, message) : sendMessageByRest.call(this, message));
|
const data = await (this.ddp.status && false ? sendMessageByDDP.call(this, message) : sendMessageByRest.call(this, message));
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
|
|
|
@ -9,10 +9,9 @@ const subscribe = (ddp, rid) => Promise.all([
|
||||||
ddp.subscribe('stream-room-messages', rid, false),
|
ddp.subscribe('stream-room-messages', rid, false),
|
||||||
ddp.subscribe('stream-notify-room', `${ rid }/typing`, false)
|
ddp.subscribe('stream-notify-room', `${ rid }/typing`, false)
|
||||||
]);
|
]);
|
||||||
const unsubscribe = subscriptions =>
|
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch((e) => {
|
||||||
subscriptions.forEach(sub => sub.unsubscribe().catch((e) => {
|
log('unsubscribeRoom', e);
|
||||||
log('unsubscribeRoom', e);
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
let timer = null;
|
let timer = null;
|
||||||
let promises;
|
let promises;
|
||||||
|
|
|
@ -309,22 +309,28 @@ class DB {
|
||||||
],
|
],
|
||||||
deleteRealmIfMigrationNeeded: true
|
deleteRealmIfMigrationNeeded: true
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
deleteAll(...args) {
|
deleteAll(...args) {
|
||||||
return this.database.write(() => this.database.deleteAll(...args));
|
return this.database.write(() => this.database.deleteAll(...args));
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(...args) {
|
delete(...args) {
|
||||||
return this.database.delete(...args);
|
return this.database.delete(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
write(...args) {
|
write(...args) {
|
||||||
return this.database.write(...args);
|
return this.database.write(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
create(...args) {
|
create(...args) {
|
||||||
return this.database.create(...args);
|
return this.database.create(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
objects(...args) {
|
objects(...args) {
|
||||||
return this.database.objects(...args);
|
return this.database.objects(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
get database() {
|
get database() {
|
||||||
return this.databases.activeDB;
|
return this.databases.activeDB;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,9 @@ import database from './realm';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
// import * as actions from '../actions';
|
// import * as actions from '../actions';
|
||||||
|
|
||||||
import { setUser, setLoginServices, removeLoginServices, loginRequest, loginSuccess, loginFailure, logout } from '../actions/login';
|
import {
|
||||||
|
setUser, setLoginServices, removeLoginServices, loginRequest, loginSuccess, loginFailure, logout
|
||||||
|
} from '../actions/login';
|
||||||
import { disconnect, connectSuccess, connectFailure } from '../actions/connect';
|
import { disconnect, connectSuccess, connectFailure } from '../actions/connect';
|
||||||
import { setActiveUser } from '../actions/activeUsers';
|
import { setActiveUser } from '../actions/activeUsers';
|
||||||
import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
|
import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
|
||||||
|
@ -598,8 +600,8 @@ const RocketChat = {
|
||||||
getPermissions,
|
getPermissions,
|
||||||
getCustomEmoji,
|
getCustomEmoji,
|
||||||
parseSettings: settings => settings.reduce((ret, item) => {
|
parseSettings: settings => settings.reduce((ret, item) => {
|
||||||
ret[item._id] = item[defaultSettings[item._id].type] || item.valueAsString || item.valueAsNumber ||
|
ret[item._id] = item[defaultSettings[item._id].type] || item.valueAsString || item.valueAsNumber
|
||||||
item.valueAsBoolean || item.value;
|
|| item.valueAsBoolean || item.value;
|
||||||
return ret;
|
return ret;
|
||||||
}, {}),
|
}, {}),
|
||||||
_prepareSettings(settings) {
|
_prepareSettings(settings) {
|
||||||
|
|
|
@ -17,17 +17,21 @@ export default class KeyboardView extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
style, contentContainerStyle, scrollEnabled, keyboardVerticalOffset, children
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAwareScrollView
|
<KeyboardAwareScrollView
|
||||||
{...scrollPersistTaps}
|
{...scrollPersistTaps}
|
||||||
style={this.props.style}
|
style={style}
|
||||||
contentContainerStyle={this.props.contentContainerStyle}
|
contentContainerStyle={contentContainerStyle}
|
||||||
scrollEnabled={this.props.scrollEnabled}
|
scrollEnabled={scrollEnabled}
|
||||||
alwaysBounceVertical={false}
|
alwaysBounceVertical={false}
|
||||||
extraHeight={this.props.keyboardVerticalOffset}
|
extraHeight={keyboardVerticalOffset}
|
||||||
behavior='position'
|
behavior='position'
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{children}
|
||||||
</KeyboardAwareScrollView>
|
</KeyboardAwareScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, StyleSheet, Image, Platform } from 'react-native';
|
import {
|
||||||
|
View, Text, StyleSheet, Image, Platform
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
|
|
||||||
|
@ -151,18 +153,22 @@ export default class RoomItem extends React.Component {
|
||||||
showLastMessage: true,
|
showLastMessage: true,
|
||||||
avatarSize: 48
|
avatarSize: 48
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
const oldlastMessage = this.props.lastMessage;
|
const { lastMessage, _updatedAt } = this.props;
|
||||||
|
const oldlastMessage = lastMessage;
|
||||||
const newLastmessage = nextProps.lastMessage;
|
const newLastmessage = nextProps.lastMessage;
|
||||||
|
|
||||||
if (oldlastMessage && newLastmessage && oldlastMessage.ts.toGMTString() !== newLastmessage.ts.toGMTString()) {
|
if (oldlastMessage && newLastmessage && oldlastMessage.ts.toGMTString() !== newLastmessage.ts.toGMTString()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.props._updatedAt && nextProps._updatedAt && nextProps._updatedAt.toGMTString() !== this.props._updatedAt.toGMTString()) {
|
if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt.toGMTString() !== _updatedAt.toGMTString()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react/destructuring-assignment
|
||||||
return attrs.some(key => nextProps[key] !== this.props[key]);
|
return attrs.some(key => nextProps[key] !== this.props[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
get avatar() {
|
get avatar() {
|
||||||
const {
|
const {
|
||||||
type, name, avatarSize, baseUrl
|
type, name, avatarSize, baseUrl
|
||||||
|
@ -172,10 +178,10 @@ export default class RoomItem extends React.Component {
|
||||||
|
|
||||||
get lastMessage() {
|
get lastMessage() {
|
||||||
const {
|
const {
|
||||||
lastMessage, type, showLastMessage
|
lastMessage, type, showLastMessage, StoreLastMessage, username
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!this.props.StoreLastMessage || !showLastMessage) {
|
if (!StoreLastMessage || !showLastMessage) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (!lastMessage) {
|
if (!lastMessage) {
|
||||||
|
@ -184,7 +190,7 @@ export default class RoomItem extends React.Component {
|
||||||
|
|
||||||
let prefix = '';
|
let prefix = '';
|
||||||
|
|
||||||
if (lastMessage.u.username === this.props.username) {
|
if (lastMessage.u.username === username) {
|
||||||
prefix = I18n.t('You_colon');
|
prefix = I18n.t('You_colon');
|
||||||
} else if (type !== 'd') {
|
} else if (type !== 'd') {
|
||||||
prefix = `${ lastMessage.u.username }: `;
|
prefix = `${ lastMessage.u.username }: `;
|
||||||
|
@ -223,7 +229,7 @@ export default class RoomItem extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
favorite, unread, userMentions, name, _updatedAt, alert, testID, height
|
favorite, unread, userMentions, name, _updatedAt, alert, testID, height, onPress, onLongPress
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const date = this.formatDate(_updatedAt);
|
const date = this.formatDate(_updatedAt);
|
||||||
|
@ -245,8 +251,8 @@ export default class RoomItem extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Touch
|
<Touch
|
||||||
onPress={this.props.onPress}
|
onPress={onPress}
|
||||||
onLongPress={this.props.onLongPress}
|
onLongPress={onLongPress}
|
||||||
underlayColor='#FFFFFF'
|
underlayColor='#FFFFFF'
|
||||||
activeOpacity={0.5}
|
activeOpacity={0.5}
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View, StyleSheet, Platform, ViewPropTypes, Image } from 'react-native';
|
import {
|
||||||
|
Text, View, StyleSheet, Platform, ViewPropTypes, Image
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Avatar from '../containers/Avatar';
|
import Avatar from '../containers/Avatar';
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { call, takeLatest, select, put } from 'redux-saga/effects';
|
import {
|
||||||
|
call, takeLatest, select, put
|
||||||
|
} from 'redux-saga/effects';
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { METEOR } from '../actions/actionsTypes';
|
import { METEOR } from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { delay } from 'redux-saga';
|
import { delay } from 'redux-saga';
|
||||||
import { select, put, call, take, takeLatest } from 'redux-saga/effects';
|
import {
|
||||||
|
select, put, call, take, takeLatest
|
||||||
|
} from 'redux-saga/effects';
|
||||||
import { NavigationActions } from '../Navigation';
|
import { NavigationActions } from '../Navigation';
|
||||||
|
|
||||||
import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes';
|
import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes';
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { takeLatest, take, select, put } from 'redux-saga/effects';
|
import {
|
||||||
|
takeLatest, take, select, put
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { appStart } from '../actions';
|
import { appStart } from '../actions';
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { delay } from 'redux-saga';
|
import { delay } from 'redux-saga';
|
||||||
import { put, call, take, takeLatest, select, all } from 'redux-saga/effects';
|
import {
|
||||||
|
put, call, take, takeLatest, select, all
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { appStart } from '../actions';
|
import { appStart } from '../actions';
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
import { put, call, takeLatest, take, select, race, fork, cancel, takeEvery } from 'redux-saga/effects';
|
import {
|
||||||
|
put, call, takeLatest, take, select, race, fork, cancel, takeEvery
|
||||||
|
} from 'redux-saga/effects';
|
||||||
import { delay } from 'redux-saga';
|
import { delay } from 'redux-saga';
|
||||||
import { BACKGROUND } from 'redux-enhancer-react-native-appstate';
|
import { BACKGROUND } from 'redux-enhancer-react-native-appstate';
|
||||||
|
|
||||||
|
@ -161,7 +163,7 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
|
||||||
yield goRoomsListAndDelete(rid);
|
yield goRoomsListAndDelete(rid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.error === 'error-you-are-last-owner') {
|
if (e.error === 'error-you-are-last-owner') {
|
||||||
Alert.alert(e.error);
|
Alert.alert(I18n.t(e.error));
|
||||||
} else {
|
} else {
|
||||||
Alert.alert(I18n.t('There_was_an_error_while_action', { action: I18n.t('leaving_room') }));
|
Alert.alert(I18n.t('There_was_an_error_while_action', { action: I18n.t('leaving_room') }));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, Switch, SafeAreaView, ScrollView, TextInput, StyleSheet, FlatList, Platform } from 'react-native';
|
import {
|
||||||
|
View, Text, Switch, SafeAreaView, ScrollView, TextInput, StyleSheet, FlatList, Platform
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
import Loading from '../containers/Loading';
|
import Loading from '../containers/Loading';
|
||||||
import LoggedView from './View';
|
import LoggedView from './View';
|
||||||
import { createChannelRequest } from '../actions/createChannel';
|
import { createChannelRequest as createChannelRequestAction } from '../actions/createChannel';
|
||||||
import { removeUser } from '../actions/selectedUsers';
|
import { removeUser as removeUserAction } from '../actions/selectedUsers';
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from '../presentation/KeyboardView';
|
||||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
|
@ -73,8 +75,8 @@ const styles = StyleSheet.create({
|
||||||
createChannel: state.createChannel,
|
createChannel: state.createChannel,
|
||||||
users: state.selectedUsers.users
|
users: state.selectedUsers.users
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
create: data => dispatch(createChannelRequest(data)),
|
create: data => dispatch(createChannelRequestAction(data)),
|
||||||
removeUser: user => dispatch(removeUser(user))
|
removeUser: user => dispatch(removeUserAction(user))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class CreateChannelView extends LoggedView {
|
export default class CreateChannelView extends LoggedView {
|
||||||
|
@ -105,15 +107,19 @@ export default class CreateChannelView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.createChannel.error && prevProps.createChannel.error !== this.props.createChannel.error) {
|
const { createChannel } = this.props;
|
||||||
|
|
||||||
|
if (createChannel.error && prevProps.createChannel.error !== createChannel.error) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const msg = this.props.createChannel.error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
|
const msg = createChannel.error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
|
||||||
showErrorAlert(msg);
|
showErrorAlert(msg);
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeText = (channelName) => {
|
onChangeText = (channelName) => {
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
const rightButtons = [];
|
const rightButtons = [];
|
||||||
if (channelName.trim().length > 0) {
|
if (channelName.trim().length > 0) {
|
||||||
rightButtons.push({
|
rightButtons.push({
|
||||||
|
@ -122,7 +128,7 @@ export default class CreateChannelView extends LoggedView {
|
||||||
testID: 'create-channel-submit'
|
testID: 'create-channel-submit'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.props.navigator.setButtons({ rightButtons });
|
navigator.setButtons({ rightButtons });
|
||||||
this.setState({ channelName });
|
this.setState({ channelName });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,28 +141,30 @@ export default class CreateChannelView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = () => {
|
submit = () => {
|
||||||
if (!this.state.channelName.trim() || this.props.createChannel.isFetching) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {
|
const {
|
||||||
channelName, type, readOnly, broadcast
|
channelName, type, readOnly, broadcast
|
||||||
} = this.state;
|
} = this.state;
|
||||||
let { users } = this.props;
|
const { users: usersProps, createChannel, create } = this.props;
|
||||||
|
|
||||||
|
if (!channelName.trim() || createChannel.isFetching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// transform users object into array of usernames
|
// transform users object into array of usernames
|
||||||
users = users.map(user => user.name);
|
const users = usersProps.map(user => user.name);
|
||||||
|
|
||||||
// create channel
|
// create channel
|
||||||
this.props.create({
|
create({
|
||||||
name: channelName, users, type, readOnly, broadcast
|
name: channelName, users, type, readOnly, broadcast
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeUser = (user) => {
|
removeUser = (user) => {
|
||||||
if (this.props.users.length === 1) {
|
const { users, removeUser } = this.props;
|
||||||
|
if (users.length === 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.props.removeUser(user);
|
removeUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSwitch = ({
|
renderSwitch = ({
|
||||||
|
@ -215,31 +223,42 @@ export default class CreateChannelView extends LoggedView {
|
||||||
|
|
||||||
renderFormSeparator = () => <View style={[sharedStyles.separator, styles.formSeparator]} />
|
renderFormSeparator = () => <View style={[sharedStyles.separator, styles.formSeparator]} />
|
||||||
|
|
||||||
renderItem = ({ item }) => (
|
renderItem = ({ item }) => {
|
||||||
<UserItem
|
const { baseUrl } = this.props;
|
||||||
name={item.fname}
|
|
||||||
username={item.name}
|
|
||||||
onPress={() => this.removeUser(item)}
|
|
||||||
testID={`create-channel-view-item-${ item.name }`}
|
|
||||||
baseUrl={this.props.baseUrl}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
renderInvitedList = () => (
|
return (
|
||||||
<FlatList
|
<UserItem
|
||||||
data={this.props.users}
|
name={item.fname}
|
||||||
extraData={this.props.users}
|
username={item.name}
|
||||||
keyExtractor={item => item._id}
|
onPress={() => this.removeUser(item)}
|
||||||
style={[styles.list, sharedStyles.separatorVertical]}
|
testID={`create-channel-view-item-${ item.name }`}
|
||||||
renderItem={this.renderItem}
|
baseUrl={baseUrl}
|
||||||
ItemSeparatorComponent={this.renderSeparator}
|
/>
|
||||||
enableEmptySections
|
);
|
||||||
keyboardShouldPersistTaps='always'
|
}
|
||||||
/>
|
|
||||||
)
|
renderInvitedList = () => {
|
||||||
|
const { users } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
data={users}
|
||||||
|
extraData={users}
|
||||||
|
keyExtractor={item => item._id}
|
||||||
|
style={[styles.list, sharedStyles.separatorVertical]}
|
||||||
|
renderItem={this.renderItem}
|
||||||
|
ItemSeparatorComponent={this.renderSeparator}
|
||||||
|
enableEmptySections
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const userCount = this.props.users.length;
|
const { channelName } = this.state;
|
||||||
|
const { users, createChannel } = this.props;
|
||||||
|
const userCount = users.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
contentContainerStyle={[sharedStyles.container, styles.container]}
|
contentContainerStyle={[sharedStyles.container, styles.container]}
|
||||||
|
@ -252,7 +271,7 @@ export default class CreateChannelView extends LoggedView {
|
||||||
ref={ref => this.channelNameRef = ref}
|
ref={ref => this.channelNameRef = ref}
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
label={I18n.t('Channel_Name')}
|
label={I18n.t('Channel_Name')}
|
||||||
value={this.state.channelName}
|
value={channelName}
|
||||||
onChangeText={this.onChangeText}
|
onChangeText={this.onChangeText}
|
||||||
placeholder={I18n.t('Channel_Name')}
|
placeholder={I18n.t('Channel_Name')}
|
||||||
returnKeyType='done'
|
returnKeyType='done'
|
||||||
|
@ -273,7 +292,7 @@ export default class CreateChannelView extends LoggedView {
|
||||||
<Text style={styles.invitedCount}>{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}</Text>
|
<Text style={styles.invitedCount}>{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}</Text>
|
||||||
</View>
|
</View>
|
||||||
{this.renderInvitedList()}
|
{this.renderInvitedList()}
|
||||||
<Loading visible={this.props.createChannel.isFetching} />
|
<Loading visible={createChannel.isFetching} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, View, SafeAreaView, ScrollView } from 'react-native';
|
import {
|
||||||
|
Text, View, SafeAreaView, ScrollView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import LoggedView from './View';
|
import LoggedView from './View';
|
||||||
import { forgotPasswordInit, forgotPasswordRequest } from '../actions/login';
|
import { forgotPasswordInit as forgotPasswordInitAction, forgotPasswordRequest as forgotPasswordRequestAction } from '../actions/login';
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from '../presentation/KeyboardView';
|
||||||
import TextInput from '../containers/TextInput';
|
import TextInput from '../containers/TextInput';
|
||||||
import Button from '../containers/Button';
|
import Button from '../containers/Button';
|
||||||
|
@ -17,8 +19,8 @@ import I18n from '../i18n';
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
login: state.login
|
login: state.login
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
forgotPasswordInit: () => dispatch(forgotPasswordInit()),
|
forgotPasswordInit: () => dispatch(forgotPasswordInitAction()),
|
||||||
forgotPasswordRequest: email => dispatch(forgotPasswordRequest(email))
|
forgotPasswordRequest: email => dispatch(forgotPasswordRequestAction(email))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class ForgotPasswordView extends LoggedView {
|
export default class ForgotPasswordView extends LoggedView {
|
||||||
|
@ -39,13 +41,14 @@ export default class ForgotPasswordView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.forgotPasswordInit();
|
const { forgotPasswordInit } = this.props;
|
||||||
|
forgotPasswordInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { login } = this.props;
|
const { login, navigator } = this.props;
|
||||||
if (login.success) {
|
if (login.success) {
|
||||||
this.props.navigator.pop();
|
navigator.pop();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
|
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
|
||||||
});
|
});
|
||||||
|
@ -64,13 +67,17 @@ export default class ForgotPasswordView extends LoggedView {
|
||||||
|
|
||||||
resetPassword = () => {
|
resetPassword = () => {
|
||||||
const { email, invalidEmail } = this.state;
|
const { email, invalidEmail } = this.state;
|
||||||
|
const { forgotPasswordRequest } = this.props;
|
||||||
if (invalidEmail || !email) {
|
if (invalidEmail || !email) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.props.forgotPasswordRequest(email);
|
forgotPasswordRequest(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { invalidEmail } = this.state;
|
||||||
|
const { login } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
contentContainerStyle={styles.container}
|
contentContainerStyle={styles.container}
|
||||||
|
@ -80,7 +87,7 @@ export default class ForgotPasswordView extends LoggedView {
|
||||||
<SafeAreaView style={styles.container} testID='forgot-password-view'>
|
<SafeAreaView style={styles.container} testID='forgot-password-view'>
|
||||||
<View>
|
<View>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputStyle={this.state.invalidEmail ? { borderColor: 'red' } : {}}
|
inputStyle={invalidEmail ? { borderColor: 'red' } : {}}
|
||||||
label={I18n.t('Email')}
|
label={I18n.t('Email')}
|
||||||
placeholder={I18n.t('Email')}
|
placeholder={I18n.t('Email')}
|
||||||
keyboardType='email-address'
|
keyboardType='email-address'
|
||||||
|
@ -99,8 +106,8 @@ export default class ForgotPasswordView extends LoggedView {
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{this.props.login.failure ? <Text style={styles.error}>{this.props.login.error.reason}</Text> : null}
|
{login.failure ? <Text style={styles.error}>{login.error.reason}</Text> : null}
|
||||||
<Loading visible={this.props.login.isFetching} />
|
<Loading visible={login.isFetching} />
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
|
|
||||||
import { open, close } from '../actions/login';
|
import { open as openAction, close as closeAction } from '../actions/login';
|
||||||
import LoggedView from './View';
|
import LoggedView from './View';
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
|
@ -57,8 +59,8 @@ const styles = StyleSheet.create({
|
||||||
Accounts_OAuth_Twitter: state.settings.Accounts_OAuth_Twitter,
|
Accounts_OAuth_Twitter: state.settings.Accounts_OAuth_Twitter,
|
||||||
services: state.login.services
|
services: state.login.services
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
open: () => dispatch(open()),
|
open: () => dispatch(openAction()),
|
||||||
close: () => dispatch(close())
|
close: () => dispatch(closeAction())
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class LoginSignupView extends LoggedView {
|
export default class LoginSignupView extends LoggedView {
|
||||||
|
@ -85,23 +87,27 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.open();
|
const { open } = this.props;
|
||||||
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.services !== nextProps.services) {
|
const { services } = this.props;
|
||||||
|
if (services !== nextProps.services) {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.close();
|
const { close } = this.props;
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressFacebook = () => {
|
onPressFacebook = () => {
|
||||||
const { appId } = this.props.services.facebook;
|
const { services, server } = this.props;
|
||||||
|
const { appId } = services.facebook;
|
||||||
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
|
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
|
||||||
const redirect_uri = `${ this.props.server }/_oauth/facebook?close`;
|
const redirect_uri = `${ server }/_oauth/facebook?close`;
|
||||||
const scope = 'email';
|
const scope = 'email';
|
||||||
const state = this.getOAuthState();
|
const state = this.getOAuthState();
|
||||||
const params = `?client_id=${ appId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&display=touch`;
|
const params = `?client_id=${ appId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&display=touch`;
|
||||||
|
@ -109,9 +115,10 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressGithub = () => {
|
onPressGithub = () => {
|
||||||
const { clientId } = this.props.services.github;
|
const { services, server } = this.props;
|
||||||
|
const { clientId } = services.github;
|
||||||
const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`;
|
const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`;
|
||||||
const redirect_uri = `${ this.props.server }/_oauth/github?close`;
|
const redirect_uri = `${ server }/_oauth/github?close`;
|
||||||
const scope = 'user:email';
|
const scope = 'user:email';
|
||||||
const state = this.getOAuthState();
|
const state = this.getOAuthState();
|
||||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }`;
|
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }`;
|
||||||
|
@ -119,9 +126,10 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressGitlab = () => {
|
onPressGitlab = () => {
|
||||||
const { clientId } = this.props.services.gitlab;
|
const { services, server } = this.props;
|
||||||
|
const { clientId } = services.gitlab;
|
||||||
const endpoint = 'https://gitlab.com/oauth/authorize';
|
const endpoint = 'https://gitlab.com/oauth/authorize';
|
||||||
const redirect_uri = `${ this.props.server }/_oauth/gitlab?close`;
|
const redirect_uri = `${ server }/_oauth/gitlab?close`;
|
||||||
const scope = 'read_user';
|
const scope = 'read_user';
|
||||||
const state = this.getOAuthState();
|
const state = this.getOAuthState();
|
||||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
||||||
|
@ -129,9 +137,10 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressGoogle = () => {
|
onPressGoogle = () => {
|
||||||
const { clientId } = this.props.services.google;
|
const { services, server } = this.props;
|
||||||
|
const { clientId } = services.google;
|
||||||
const endpoint = 'https://accounts.google.com/o/oauth2/auth';
|
const endpoint = 'https://accounts.google.com/o/oauth2/auth';
|
||||||
const redirect_uri = `${ this.props.server }/_oauth/google?close`;
|
const redirect_uri = `${ server }/_oauth/google?close`;
|
||||||
const scope = 'email';
|
const scope = 'email';
|
||||||
const state = this.getOAuthState();
|
const state = this.getOAuthState();
|
||||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
||||||
|
@ -139,9 +148,10 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressLinkedin = () => {
|
onPressLinkedin = () => {
|
||||||
const { clientId } = this.props.services.linkedin;
|
const { services, server } = this.props;
|
||||||
|
const { clientId } = services.linkedin;
|
||||||
const endpoint = 'https://www.linkedin.com/uas/oauth2/authorization';
|
const endpoint = 'https://www.linkedin.com/uas/oauth2/authorization';
|
||||||
const redirect_uri = `${ this.props.server }/_oauth/linkedin?close`;
|
const redirect_uri = `${ server }/_oauth/linkedin?close`;
|
||||||
const scope = 'r_emailaddress';
|
const scope = 'r_emailaddress';
|
||||||
const state = this.getOAuthState();
|
const state = this.getOAuthState();
|
||||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
||||||
|
@ -149,17 +159,19 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressMeteor = () => {
|
onPressMeteor = () => {
|
||||||
const { clientId } = this.props.services['meteor-developer'];
|
const { services, server } = this.props;
|
||||||
|
const { clientId } = services['meteor-developer'];
|
||||||
const endpoint = 'https://www.meteor.com/oauth2/authorize';
|
const endpoint = 'https://www.meteor.com/oauth2/authorize';
|
||||||
const redirect_uri = `${ this.props.server }/_oauth/meteor-developer`;
|
const redirect_uri = `${ server }/_oauth/meteor-developer`;
|
||||||
const state = this.getOAuthState();
|
const state = this.getOAuthState();
|
||||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&state=${ state }&response_type=code`;
|
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&state=${ state }&response_type=code`;
|
||||||
this.openOAuth(`${ endpoint }${ params }`);
|
this.openOAuth(`${ endpoint }${ params }`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressTwitter = () => {
|
onPressTwitter = () => {
|
||||||
|
const { server } = this.props;
|
||||||
const state = this.getOAuthState();
|
const state = this.getOAuthState();
|
||||||
const url = `${ this.props.server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`;
|
const url = `${ server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`;
|
||||||
this.openOAuth(url);
|
this.openOAuth(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +181,8 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
openOAuth = (oAuthUrl) => {
|
openOAuth = (oAuthUrl) => {
|
||||||
this.props.navigator.showModal({
|
const { navigator } = this.props;
|
||||||
|
navigator.showModal({
|
||||||
screen: 'OAuthView',
|
screen: 'OAuthView',
|
||||||
title: 'OAuth',
|
title: 'OAuth',
|
||||||
passProps: {
|
passProps: {
|
||||||
|
@ -179,93 +192,113 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
login = () => {
|
login = () => {
|
||||||
this.props.navigator.push({
|
const { navigator, server } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'LoginView',
|
screen: 'LoginView',
|
||||||
title: this.props.server,
|
title: server,
|
||||||
backButtonTitle: ''
|
backButtonTitle: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
register = () => {
|
register = () => {
|
||||||
this.props.navigator.push({
|
const { navigator, server } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'RegisterView',
|
screen: 'RegisterView',
|
||||||
title: this.props.server,
|
title: server,
|
||||||
backButtonTitle: ''
|
backButtonTitle: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderServices = () => {
|
renderServices = () => {
|
||||||
const { services } = this.props;
|
const {
|
||||||
|
services, Accounts_OAuth_Facebook, Accounts_OAuth_Github, Accounts_OAuth_Gitlab, Accounts_OAuth_Google, Accounts_OAuth_Linkedin, Accounts_OAuth_Meteor, Accounts_OAuth_Twitter
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (!Object.keys(services).length) {
|
if (!Object.keys(services).length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.servicesContainer}>
|
<View style={styles.servicesContainer}>
|
||||||
<Text style={styles.servicesTitle}>
|
<Text style={styles.servicesTitle}>
|
||||||
{I18n.t('Or_continue_using_social_accounts')}
|
{I18n.t('Or_continue_using_social_accounts')}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={sharedStyles.loginOAuthButtons} key='services'>
|
<View style={sharedStyles.loginOAuthButtons} key='services'>
|
||||||
{this.props.Accounts_OAuth_Facebook && this.props.services.facebook ?
|
{Accounts_OAuth_Facebook && services.facebook
|
||||||
<TouchableOpacity
|
? (
|
||||||
style={[sharedStyles.oauthButton, sharedStyles.facebookButton]}
|
<TouchableOpacity
|
||||||
onPress={this.onPressFacebook}
|
style={[sharedStyles.oauthButton, sharedStyles.facebookButton]}
|
||||||
>
|
onPress={this.onPressFacebook}
|
||||||
<Icon name='facebook' size={20} color='#ffffff' />
|
>
|
||||||
</TouchableOpacity>
|
<Icon name='facebook' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{this.props.Accounts_OAuth_Github && this.props.services.github ?
|
{Accounts_OAuth_Github && services.github
|
||||||
<TouchableOpacity
|
? (
|
||||||
style={[sharedStyles.oauthButton, sharedStyles.githubButton]}
|
<TouchableOpacity
|
||||||
onPress={this.onPressGithub}
|
style={[sharedStyles.oauthButton, sharedStyles.githubButton]}
|
||||||
>
|
onPress={this.onPressGithub}
|
||||||
<Icon name='github' size={20} color='#ffffff' />
|
>
|
||||||
</TouchableOpacity>
|
<Icon name='github' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{this.props.Accounts_OAuth_Gitlab && this.props.services.gitlab ?
|
{Accounts_OAuth_Gitlab && services.gitlab
|
||||||
<TouchableOpacity
|
? (
|
||||||
style={[sharedStyles.oauthButton, sharedStyles.gitlabButton]}
|
<TouchableOpacity
|
||||||
onPress={this.onPressGitlab}
|
style={[sharedStyles.oauthButton, sharedStyles.gitlabButton]}
|
||||||
>
|
onPress={this.onPressGitlab}
|
||||||
<Icon name='gitlab' size={20} color='#ffffff' />
|
>
|
||||||
</TouchableOpacity>
|
<Icon name='gitlab' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{this.props.Accounts_OAuth_Google && this.props.services.google ?
|
{Accounts_OAuth_Google && services.google
|
||||||
<TouchableOpacity
|
? (
|
||||||
style={[sharedStyles.oauthButton, sharedStyles.googleButton]}
|
<TouchableOpacity
|
||||||
onPress={this.onPressGoogle}
|
style={[sharedStyles.oauthButton, sharedStyles.googleButton]}
|
||||||
>
|
onPress={this.onPressGoogle}
|
||||||
<Icon name='google' size={20} color='#ffffff' />
|
>
|
||||||
</TouchableOpacity>
|
<Icon name='google' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{this.props.Accounts_OAuth_Linkedin && this.props.services.linkedin ?
|
{Accounts_OAuth_Linkedin && services.linkedin
|
||||||
<TouchableOpacity
|
? (
|
||||||
style={[sharedStyles.oauthButton, sharedStyles.linkedinButton]}
|
<TouchableOpacity
|
||||||
onPress={this.onPressLinkedin}
|
style={[sharedStyles.oauthButton, sharedStyles.linkedinButton]}
|
||||||
>
|
onPress={this.onPressLinkedin}
|
||||||
<Icon name='linkedin' size={20} color='#ffffff' />
|
>
|
||||||
</TouchableOpacity>
|
<Icon name='linkedin' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{this.props.Accounts_OAuth_Meteor && this.props.services['meteor-developer'] ?
|
{Accounts_OAuth_Meteor && services['meteor-developer']
|
||||||
<TouchableOpacity
|
? (
|
||||||
style={[sharedStyles.oauthButton, sharedStyles.meteorButton]}
|
<TouchableOpacity
|
||||||
onPress={this.onPressMeteor}
|
style={[sharedStyles.oauthButton, sharedStyles.meteorButton]}
|
||||||
>
|
onPress={this.onPressMeteor}
|
||||||
<MaterialCommunityIcons name='meteor' size={25} color='#ffffff' />
|
>
|
||||||
</TouchableOpacity>
|
<MaterialCommunityIcons name='meteor' size={25} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{this.props.Accounts_OAuth_Twitter && this.props.services.twitter ?
|
{Accounts_OAuth_Twitter && services.twitter
|
||||||
<TouchableOpacity
|
? (
|
||||||
style={[sharedStyles.oauthButton, sharedStyles.twitterButton]}
|
<TouchableOpacity
|
||||||
onPress={this.onPressTwitter}
|
style={[sharedStyles.oauthButton, sharedStyles.twitterButton]}
|
||||||
>
|
onPress={this.onPressTwitter}
|
||||||
<Icon name='twitter' size={20} color='#ffffff' />
|
>
|
||||||
</TouchableOpacity>
|
<Icon name='twitter' size={20} color='#ffffff' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
|
@ -274,6 +307,8 @@ export default class LoginSignupView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { isFetching } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={[sharedStyles.container, sharedStyles.containerScrollView]}
|
style={[sharedStyles.container, sharedStyles.containerScrollView]}
|
||||||
|
@ -298,7 +333,7 @@ export default class LoginSignupView extends LoggedView {
|
||||||
/>
|
/>
|
||||||
{this.renderServices()}
|
{this.renderServices()}
|
||||||
</View>
|
</View>
|
||||||
<Loading visible={this.props.isFetching} />
|
<Loading visible={isFetching} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Keyboard, Text, ScrollView, View, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
Keyboard, Text, ScrollView, View, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Answers } from 'react-native-fabric';
|
import { Answers } from 'react-native-fabric';
|
||||||
|
|
||||||
|
@ -50,6 +52,8 @@ export default class LoginView extends LoggedView {
|
||||||
|
|
||||||
submit = async() => {
|
submit = async() => {
|
||||||
const { username, password, code } = this.state;
|
const { username, password, code } = this.state;
|
||||||
|
const { loginSubmit } = this.props;
|
||||||
|
|
||||||
if (username.trim() === '' || password.trim() === '') {
|
if (username.trim() === '' || password.trim() === '') {
|
||||||
showToast(I18n.t('Email_or_password_field_is_empty'));
|
showToast(I18n.t('Email_or_password_field_is_empty'));
|
||||||
return;
|
return;
|
||||||
|
@ -57,7 +61,7 @@ export default class LoginView extends LoggedView {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.props.loginSubmit({ username, password, code });
|
await loginSubmit({ username, password, code });
|
||||||
Answers.logLogin('Email', true);
|
Answers.logLogin('Email', true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('LoginView submit', error);
|
console.warn('LoginView submit', error);
|
||||||
|
@ -65,15 +69,17 @@ export default class LoginView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
register = () => {
|
register = () => {
|
||||||
this.props.navigator.push({
|
const { navigator, server } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'RegisterView',
|
screen: 'RegisterView',
|
||||||
title: this.props.server,
|
title: server,
|
||||||
backButtonTitle: ''
|
backButtonTitle: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
forgotPassword = () => {
|
forgotPassword = () => {
|
||||||
this.props.navigator.push({
|
const { navigator } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'ForgotPasswordView',
|
screen: 'ForgotPasswordView',
|
||||||
title: I18n.t('Forgot_Password'),
|
title: I18n.t('Forgot_Password'),
|
||||||
backButtonTitle: ''
|
backButtonTitle: ''
|
||||||
|
@ -81,7 +87,8 @@ export default class LoginView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTOTP = () => {
|
renderTOTP = () => {
|
||||||
if (/totp/ig.test(this.props.error)) {
|
const { error } = this.props;
|
||||||
|
if (/totp/ig.test(error)) {
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={ref => this.codeInput = ref}
|
inputRef={ref => this.codeInput = ref}
|
||||||
|
@ -99,6 +106,10 @@ export default class LoginView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, failure, reason, isFetching
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
contentContainerStyle={styles.container}
|
contentContainerStyle={styles.container}
|
||||||
|
@ -110,7 +121,7 @@ export default class LoginView extends LoggedView {
|
||||||
<Text style={[styles.loginText, styles.loginTitle]}>Login</Text>
|
<Text style={[styles.loginText, styles.loginTitle]}>Login</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={I18n.t('Username')}
|
label={I18n.t('Username')}
|
||||||
placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Username')}
|
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username')}
|
||||||
keyboardType='email-address'
|
keyboardType='email-address'
|
||||||
returnKeyType='next'
|
returnKeyType='next'
|
||||||
iconLeft='at'
|
iconLeft='at'
|
||||||
|
@ -122,7 +133,7 @@ export default class LoginView extends LoggedView {
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={(e) => { this.password = e; }}
|
inputRef={(e) => { this.password = e; }}
|
||||||
label={I18n.t('Password')}
|
label={I18n.t('Password')}
|
||||||
placeholder={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')}
|
placeholder={Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||||
returnKeyType='done'
|
returnKeyType='done'
|
||||||
iconLeft='key-variant'
|
iconLeft='key-variant'
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
|
@ -156,8 +167,8 @@ export default class LoginView extends LoggedView {
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{this.props.failure ? <Text style={styles.error}>{this.props.reason}</Text> : null}
|
{failure ? <Text style={styles.error}>{reason}</Text> : null}
|
||||||
<Loading visible={this.props.isFetching} />
|
<Loading visible={isFetching} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
FlatList, View, Text, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { openMentionedMessages as openMentionedMessagesAction, closeMentionedMessages as closeMentionedMessagesAction } from '../../actions/mentionedMessages';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import { openMentionedMessages, closeMentionedMessages } from '../../actions/mentionedMessages';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
|
@ -19,8 +21,8 @@ import I18n from '../../i18n';
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
}
|
}
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openMentionedMessages: (rid, limit) => dispatch(openMentionedMessages(rid, limit)),
|
openMentionedMessages: (rid, limit) => dispatch(openMentionedMessagesAction(rid, limit)),
|
||||||
closeMentionedMessages: () => dispatch(closeMentionedMessages())
|
closeMentionedMessages: () => dispatch(closeMentionedMessagesAction())
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class MentionedMessagesView extends LoggedView {
|
export default class MentionedMessagesView extends LoggedView {
|
||||||
|
@ -47,17 +49,20 @@ export default class MentionedMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
const { ready } = this.props;
|
||||||
|
if (nextProps.ready && nextProps.ready !== ready) {
|
||||||
this.setState({ loading: false, loadingMore: false });
|
this.setState({ loading: false, loadingMore: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.closeMentionedMessages();
|
const { closeMentionedMessages } = this.props;
|
||||||
|
closeMentionedMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
load = () => {
|
load = () => {
|
||||||
this.props.openMentionedMessages(this.props.rid, this.limit);
|
const { openMentionedMessages, rid } = this.props;
|
||||||
|
openMentionedMessages(rid, this.limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
moreData = () => {
|
moreData = () => {
|
||||||
|
@ -79,15 +84,18 @@ export default class MentionedMessagesView extends LoggedView {
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderItem = ({ item }) => (
|
renderItem = ({ item }) => {
|
||||||
<Message
|
const { user } = this.props;
|
||||||
item={item}
|
return (
|
||||||
style={styles.message}
|
<Message
|
||||||
reactions={item.reactions}
|
item={item}
|
||||||
user={this.props.user}
|
style={styles.message}
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
reactions={item.reactions}
|
||||||
/>
|
user={user}
|
||||||
)
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, loadingMore } = this.state;
|
const { loading, loadingMore } = this.state;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image } from 'react-native';
|
import {
|
||||||
|
View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
|
@ -75,9 +77,10 @@ export default class NewMessageView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onNavigatorEvent(event) {
|
async onNavigatorEvent(event) {
|
||||||
|
const { navigator } = this.props;
|
||||||
if (event.type === 'NavBarButtonPress') {
|
if (event.type === 'NavBarButtonPress') {
|
||||||
if (event.id === 'cancel') {
|
if (event.id === 'cancel') {
|
||||||
this.props.navigator.dismissModal();
|
navigator.dismissModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,12 +90,14 @@ export default class NewMessageView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressItem = (item) => {
|
onPressItem = (item) => {
|
||||||
this.props.navigator.dismissModal();
|
const { navigator, onPressItem } = this.props;
|
||||||
|
navigator.dismissModal();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.props.onPressItem(item);
|
onPressItem(item);
|
||||||
}, 600);
|
}, 600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
updateState = debounce(() => {
|
updateState = debounce(() => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
@ -105,7 +110,8 @@ export default class NewMessageView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
createChannel = () => {
|
createChannel = () => {
|
||||||
this.props.navigator.push({
|
const { navigator } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'SelectedUsersView',
|
screen: 'SelectedUsersView',
|
||||||
title: I18n.t('Select_Users'),
|
title: I18n.t('Select_Users'),
|
||||||
backButtonTitle: '',
|
backButtonTitle: '',
|
||||||
|
@ -130,14 +136,17 @@ export default class NewMessageView extends LoggedView {
|
||||||
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />;
|
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />;
|
||||||
|
|
||||||
renderItem = ({ item, index }) => {
|
renderItem = ({ item, index }) => {
|
||||||
|
const { search } = this.state;
|
||||||
|
const { baseUrl } = this.props;
|
||||||
|
|
||||||
let style = {};
|
let style = {};
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
style = { ...sharedStyles.separatorTop };
|
style = { ...sharedStyles.separatorTop };
|
||||||
}
|
}
|
||||||
if (this.state.search.length > 0 && index === this.state.search.length - 1) {
|
if (search.length > 0 && index === search.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
if (this.state.search.length === 0 && index === this.data.length - 1) {
|
if (search.length === 0 && index === this.data.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -145,24 +154,27 @@ export default class NewMessageView extends LoggedView {
|
||||||
name={item.search ? item.name : item.fname}
|
name={item.search ? item.name : item.fname}
|
||||||
username={item.search ? item.username : item.name}
|
username={item.search ? item.username : item.name}
|
||||||
onPress={() => this.onPressItem(item)}
|
onPress={() => this.onPressItem(item)}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
testID={`new-message-view-item-${ item.name }`}
|
testID={`new-message-view-item-${ item.name }`}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList = () => (
|
renderList = () => {
|
||||||
<FlatList
|
const { search } = this.state;
|
||||||
data={this.state.search.length > 0 ? this.state.search : this.data}
|
return (
|
||||||
extraData={this.state}
|
<FlatList
|
||||||
keyExtractor={item => item._id}
|
data={search.length > 0 ? search : this.data}
|
||||||
ListHeaderComponent={this.renderHeader}
|
extraData={this.state}
|
||||||
renderItem={this.renderItem}
|
keyExtractor={item => item._id}
|
||||||
ItemSeparatorComponent={this.renderSeparator}
|
ListHeaderComponent={this.renderHeader}
|
||||||
keyboardShouldPersistTaps='always'
|
renderItem={this.renderItem}
|
||||||
/>
|
ItemSeparatorComponent={this.renderSeparator}
|
||||||
)
|
keyboardShouldPersistTaps='always'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render = () => (
|
render = () => (
|
||||||
<SafeAreaView style={styles.safeAreaView} testID='new-message-view'>
|
<SafeAreaView style={styles.safeAreaView} testID='new-message-view'>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet, TouchableOpacity } from 'react-native';
|
import {
|
||||||
|
Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet, TouchableOpacity
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Icon from 'react-native-vector-icons/Ionicons';
|
import Icon from 'react-native-vector-icons/Ionicons';
|
||||||
|
|
||||||
|
@ -74,9 +76,9 @@ export default class NewServerView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { server } = this.props;
|
const { server, connectServer } = this.props;
|
||||||
if (server) {
|
if (server) {
|
||||||
this.props.connectServer(server);
|
connectServer(server);
|
||||||
this.setState({ text: server });
|
this.setState({ text: server });
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -86,7 +88,8 @@ export default class NewServerView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.failure && nextProps.failure !== this.props.failure) {
|
const { failure } = this.props;
|
||||||
|
if (nextProps.failure && nextProps.failure !== failure) {
|
||||||
Alert.alert(I18n.t('Oops'), I18n.t('The_URL_is_invalid'));
|
Alert.alert(I18n.t('Oops'), I18n.t('The_URL_is_invalid'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,17 +99,20 @@ export default class NewServerView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = () => {
|
submit = () => {
|
||||||
if (this.state.text) {
|
const { text } = this.state;
|
||||||
|
const { connectServer } = this.props;
|
||||||
|
|
||||||
|
if (text) {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
this.props.connectServer(this.completeUrl(this.state.text));
|
connectServer(this.completeUrl(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
completeUrl = (url) => {
|
completeUrl = (url) => {
|
||||||
url = url && url.trim();
|
url = url && url.trim();
|
||||||
|
|
||||||
if (/^(\w|[0-9-_]){3,}$/.test(url) &&
|
if (/^(\w|[0-9-_]){3,}$/.test(url)
|
||||||
/^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) {
|
&& /^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) {
|
||||||
url = `${ url }.rocket.chat`;
|
url = `${ url }.rocket.chat`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +128,8 @@ export default class NewServerView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBack = () => {
|
renderBack = () => {
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
let top = 15;
|
let top = 15;
|
||||||
if (DeviceInfo.getBrand() === 'Apple') {
|
if (DeviceInfo.getBrand() === 'Apple') {
|
||||||
top = DeviceInfo.isNotch() ? 45 : 30;
|
top = DeviceInfo.isNotch() ? 45 : 30;
|
||||||
|
@ -130,7 +138,7 @@ export default class NewServerView extends LoggedView {
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.backButton, { top }]}
|
style={[styles.backButton, { top }]}
|
||||||
onPress={() => this.props.navigator.pop()}
|
onPress={() => navigator.pop()}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name='ios-arrow-back'
|
name='ios-arrow-back'
|
||||||
|
|
|
@ -52,9 +52,10 @@ export default class OAuthView extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { oAuthUrl, navigator } = this.props;
|
||||||
return (
|
return (
|
||||||
<WebView
|
<WebView
|
||||||
source={{ uri: this.props.oAuthUrl }}
|
source={{ uri: oAuthUrl }}
|
||||||
userAgent={userAgent}
|
userAgent={userAgent}
|
||||||
onNavigationStateChange={(webViewState) => {
|
onNavigationStateChange={(webViewState) => {
|
||||||
const url = decodeURIComponent(webViewState.url);
|
const url = decodeURIComponent(webViewState.url);
|
||||||
|
@ -62,7 +63,7 @@ export default class OAuthView extends React.PureComponent {
|
||||||
const parts = url.split('#');
|
const parts = url.split('#');
|
||||||
const credentials = JSON.parse(parts[1]);
|
const credentials = JSON.parse(parts[1]);
|
||||||
this.login({ oauth: { ...credentials } });
|
this.login({ oauth: { ...credentials } });
|
||||||
this.props.navigator.dismissModal();
|
navigator.dismissModal();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, TouchableWithoutFeedback, Image } from 'react-native';
|
import {
|
||||||
|
View, Text, TouchableWithoutFeedback, Image
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, Image, SafeAreaView, TouchableOpacity } from 'react-native';
|
import {
|
||||||
|
View, Text, Image, SafeAreaView, TouchableOpacity
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -56,11 +58,13 @@ export default class OnboardingView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
this.props.navigator.dismissModal();
|
const { navigator } = this.props;
|
||||||
|
navigator.dismissModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
connectServer = () => {
|
connectServer = () => {
|
||||||
this.props.navigator.push({
|
const { navigator } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'NewServerView',
|
screen: 'NewServerView',
|
||||||
backButtonTitle: '',
|
backButtonTitle: '',
|
||||||
navigatorStyle: {
|
navigatorStyle: {
|
||||||
|
@ -70,7 +74,8 @@ export default class OnboardingView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
joinCommunity = () => {
|
joinCommunity = () => {
|
||||||
this.props.navigator.push({
|
const { navigator } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'NewServerView',
|
screen: 'NewServerView',
|
||||||
backButtonTitle: '',
|
backButtonTitle: '',
|
||||||
passProps: {
|
passProps: {
|
||||||
|
@ -87,7 +92,9 @@ export default class OnboardingView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderClose = () => {
|
renderClose = () => {
|
||||||
if (this.props.previousServer) {
|
const { previousServer } = this.props;
|
||||||
|
|
||||||
|
if (previousServer) {
|
||||||
let top = 15;
|
let top = 15;
|
||||||
if (DeviceInfo.getBrand() === 'Apple') {
|
if (DeviceInfo.getBrand() === 'Apple') {
|
||||||
top = DeviceInfo.isNotch() ? 45 : 30;
|
top = DeviceInfo.isNotch() ? 45 : 30;
|
||||||
|
@ -96,6 +103,7 @@ export default class OnboardingView extends LoggedView {
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.closeModal, { top }]}
|
style={[styles.closeModal, { top }]}
|
||||||
onPress={this.close}
|
onPress={this.close}
|
||||||
|
testID='onboarding-close'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name='close'
|
name='close'
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
FlatList, View, Text, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ActionSheet from 'react-native-actionsheet';
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
|
|
||||||
|
import { openPinnedMessages as openPinnedMessagesAction, closePinnedMessages as closePinnedMessagesAction } from '../../actions/pinnedMessages';
|
||||||
|
import { togglePinRequest as togglePinRequestAction } from '../../actions/messages';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import { openPinnedMessages, closePinnedMessages } from '../../actions/pinnedMessages';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
import { togglePinRequest } from '../../actions/messages';
|
|
||||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
@ -25,9 +27,9 @@ const options = [I18n.t('Unpin'), I18n.t('Cancel')];
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
}
|
}
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openPinnedMessages: (rid, limit) => dispatch(openPinnedMessages(rid, limit)),
|
openPinnedMessages: (rid, limit) => dispatch(openPinnedMessagesAction(rid, limit)),
|
||||||
closePinnedMessages: () => dispatch(closePinnedMessages()),
|
closePinnedMessages: () => dispatch(closePinnedMessagesAction()),
|
||||||
togglePinRequest: message => dispatch(togglePinRequest(message))
|
togglePinRequest: message => dispatch(togglePinRequestAction(message))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class PinnedMessagesView extends LoggedView {
|
export default class PinnedMessagesView extends LoggedView {
|
||||||
|
@ -56,13 +58,15 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
const { ready } = this.props;
|
||||||
|
if (nextProps.ready && nextProps.ready !== ready) {
|
||||||
this.setState({ loading: false, loadingMore: false });
|
this.setState({ loading: false, loadingMore: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.closePinnedMessages();
|
const { closePinnedMessages } = this.props;
|
||||||
|
closePinnedMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
onLongPress = (message) => {
|
onLongPress = (message) => {
|
||||||
|
@ -73,9 +77,12 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleActionPress = (actionIndex) => {
|
handleActionPress = (actionIndex) => {
|
||||||
|
const { message } = this.state;
|
||||||
|
const { togglePinRequest } = this.props;
|
||||||
|
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
case PIN_INDEX:
|
case PIN_INDEX:
|
||||||
this.props.togglePinRequest(this.state.message);
|
togglePinRequest(message);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -83,7 +90,8 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
load = () => {
|
load = () => {
|
||||||
this.props.openPinnedMessages(this.props.rid, this.limit);
|
const { openPinnedMessages, rid } = this.props;
|
||||||
|
openPinnedMessages(rid, this.limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
moreData = () => {
|
moreData = () => {
|
||||||
|
@ -105,16 +113,19 @@ export default class PinnedMessagesView extends LoggedView {
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderItem = ({ item }) => (
|
renderItem = ({ item }) => {
|
||||||
<Message
|
const { user } = this.props;
|
||||||
item={item}
|
return (
|
||||||
style={styles.message}
|
<Message
|
||||||
reactions={item.reactions}
|
item={item}
|
||||||
user={this.props.user}
|
style={styles.message}
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
reactions={item.reactions}
|
||||||
onLongPress={this.onLongPress}
|
user={user}
|
||||||
/>
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
)
|
onLongPress={this.onLongPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, loadingMore } = this.state;
|
const { loading, loadingMore } = this.state;
|
||||||
|
|
|
@ -20,9 +20,11 @@ export default class PrivacyPolicyView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { privacyPolicy } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<WebView originWhitelist={['*']} source={{ html: this.props.privacyPolicy, baseUrl: '' }} />
|
<WebView originWhitelist={['*']} source={{ html: privacyPolicy, baseUrl: '' }} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, ScrollView, SafeAreaView, Keyboard, Dimensions } from 'react-native';
|
import {
|
||||||
|
View, ScrollView, SafeAreaView, Keyboard, Dimensions
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Dialog from 'react-native-dialog';
|
import Dialog from 'react-native-dialog';
|
||||||
import SHA256 from 'js-sha256';
|
import SHA256 from 'js-sha256';
|
||||||
|
@ -61,7 +63,8 @@ export default class ProfileView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props.navigator.setButtons({
|
const { navigator } = this.props;
|
||||||
|
navigator.setButtons({
|
||||||
leftButtons: [{
|
leftButtons: [{
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
icon: { uri: 'settings', scale: Dimensions.get('window').scale }
|
icon: { uri: 'settings', scale: Dimensions.get('window').scale }
|
||||||
|
@ -70,9 +73,11 @@ export default class ProfileView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
this.props.navigator.setDrawerEnabled({
|
navigator.setDrawerEnabled({
|
||||||
side: 'left',
|
side: 'left',
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
|
@ -86,15 +91,18 @@ export default class ProfileView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.user !== nextProps.user) {
|
const { user } = this.props;
|
||||||
|
if (user !== nextProps.user) {
|
||||||
this.init(nextProps.user);
|
this.init(nextProps.user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onNavigatorEvent(event) {
|
onNavigatorEvent(event) {
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
if (event.type === 'NavBarButtonPress') {
|
if (event.type === 'NavBarButtonPress') {
|
||||||
if (event.id === 'settings') {
|
if (event.id === 'settings') {
|
||||||
this.props.navigator.toggleDrawer({
|
navigator.toggleDrawer({
|
||||||
side: 'left'
|
side: 'left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -106,9 +114,11 @@ export default class ProfileView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
init = (user) => {
|
init = (user) => {
|
||||||
|
const { user: userProps } = this.props;
|
||||||
const {
|
const {
|
||||||
name, username, emails, customFields
|
name, username, emails, customFields
|
||||||
} = user || this.props.user;
|
} = user || userProps;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
name,
|
name,
|
||||||
username,
|
username,
|
||||||
|
@ -137,12 +147,12 @@ export default class ProfileView extends LoggedView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return !(user.name === name &&
|
return !(user.name === name
|
||||||
user.username === username &&
|
&& user.username === username
|
||||||
!newPassword &&
|
&& !newPassword
|
||||||
(user.emails && user.emails[0].address === email) &&
|
&& (user.emails && user.emails[0].address === email)
|
||||||
!avatar.data &&
|
&& !avatar.data
|
||||||
!customFieldsChanged
|
&& !customFieldsChanged
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,43 +288,50 @@ export default class ProfileView extends LoggedView {
|
||||||
</Touch>
|
</Touch>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderAvatarButtons = () => (
|
renderAvatarButtons = () => {
|
||||||
<View style={styles.avatarButtons}>
|
const { avatarUrl, avatarSuggestions } = this.state;
|
||||||
{this.renderAvatarButton({
|
const { user, baseUrl } = this.props;
|
||||||
child: <Avatar text={this.props.user.username} size={50} baseUrl={this.props.baseUrl} forceInitials />,
|
|
||||||
onPress: () => this.resetAvatar(),
|
return (
|
||||||
key: 'profile-view-reset-avatar'
|
<View style={styles.avatarButtons}>
|
||||||
})}
|
{this.renderAvatarButton({
|
||||||
{this.renderAvatarButton({
|
child: <Avatar text={user.username} size={50} baseUrl={baseUrl} forceInitials />,
|
||||||
child: <Icon name='file-upload' size={30} />,
|
onPress: () => this.resetAvatar(),
|
||||||
onPress: () => this.pickImage(),
|
key: 'profile-view-reset-avatar'
|
||||||
key: 'profile-view-upload-avatar'
|
})}
|
||||||
})}
|
{this.renderAvatarButton({
|
||||||
{this.renderAvatarButton({
|
child: <Icon name='file-upload' size={30} />,
|
||||||
child: <Icon name='link' size={30} />,
|
onPress: () => this.pickImage(),
|
||||||
onPress: () => this.setAvatar({ url: this.state.avatarUrl, data: this.state.avatarUrl, service: 'url' }),
|
key: 'profile-view-upload-avatar'
|
||||||
disabled: !this.state.avatarUrl,
|
})}
|
||||||
key: 'profile-view-avatar-url-button'
|
{this.renderAvatarButton({
|
||||||
})}
|
child: <Icon name='link' size={30} />,
|
||||||
{Object.keys(this.state.avatarSuggestions).map((service) => {
|
onPress: () => this.setAvatar({ url: avatarUrl, data: avatarUrl, service: 'url' }),
|
||||||
const { url, blob, contentType } = this.state.avatarSuggestions[service];
|
disabled: !avatarUrl,
|
||||||
return this.renderAvatarButton({
|
key: 'profile-view-avatar-url-button'
|
||||||
key: `profile-view-avatar-${ service }`,
|
})}
|
||||||
child: <Avatar avatar={url} size={50} baseUrl={this.props.baseUrl} />,
|
{Object.keys(avatarSuggestions).map((service) => {
|
||||||
onPress: () => this.setAvatar({
|
const { url, blob, contentType } = avatarSuggestions[service];
|
||||||
url, data: blob, service, contentType
|
return this.renderAvatarButton({
|
||||||
})
|
key: `profile-view-avatar-${ service }`,
|
||||||
});
|
child: <Avatar avatar={url} size={50} baseUrl={baseUrl} />,
|
||||||
})}
|
onPress: () => this.setAvatar({
|
||||||
</View>
|
url, data: blob, service, contentType
|
||||||
);
|
})
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderCustomFields = () => {
|
renderCustomFields = () => {
|
||||||
const { customFields } = this.state;
|
const { customFields } = this.state;
|
||||||
if (!this.props.Accounts_CustomFields) {
|
const { Accounts_CustomFields } = this.props;
|
||||||
|
|
||||||
|
if (!Accounts_CustomFields) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const parsedCustomFields = JSON.parse(this.props.Accounts_CustomFields);
|
const parsedCustomFields = JSON.parse(Accounts_CustomFields);
|
||||||
return Object.keys(parsedCustomFields).map((key, index, array) => {
|
return Object.keys(parsedCustomFields).map((key, index, array) => {
|
||||||
if (parsedCustomFields[key].type === 'select') {
|
if (parsedCustomFields[key].type === 'select') {
|
||||||
const options = parsedCustomFields[key].options.map(option => ({ label: option, value: option }));
|
const options = parsedCustomFields[key].options.map(option => ({ label: option, value: option }));
|
||||||
|
@ -325,7 +342,7 @@ export default class ProfileView extends LoggedView {
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const newValue = {};
|
const newValue = {};
|
||||||
newValue[key] = value;
|
newValue[key] = value;
|
||||||
this.setState({ customFields: { ...this.state.customFields, ...newValue } });
|
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||||
}}
|
}}
|
||||||
value={customFields[key]}
|
value={customFields[key]}
|
||||||
>
|
>
|
||||||
|
@ -350,7 +367,7 @@ export default class ProfileView extends LoggedView {
|
||||||
onChangeText={(value) => {
|
onChangeText={(value) => {
|
||||||
const newValue = {};
|
const newValue = {};
|
||||||
newValue[key] = value;
|
newValue[key] = value;
|
||||||
this.setState({ customFields: { ...this.state.customFields, ...newValue } });
|
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||||
}}
|
}}
|
||||||
onSubmitEditing={() => {
|
onSubmitEditing={() => {
|
||||||
if (array.length - 1 > index) {
|
if (array.length - 1 > index) {
|
||||||
|
@ -365,8 +382,10 @@ export default class ProfileView extends LoggedView {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
name, username, email, newPassword, avatarUrl, customFields
|
name, username, email, newPassword, avatarUrl, customFields, avatar, saving, showPasswordAlert
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
const { baseUrl } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
contentContainerStyle={sharedStyles.container}
|
contentContainerStyle={sharedStyles.container}
|
||||||
|
@ -381,9 +400,9 @@ export default class ProfileView extends LoggedView {
|
||||||
<View style={styles.avatarContainer} testID='profile-view-avatar'>
|
<View style={styles.avatarContainer} testID='profile-view-avatar'>
|
||||||
<Avatar
|
<Avatar
|
||||||
text={username}
|
text={username}
|
||||||
avatar={this.state.avatar && this.state.avatar.url}
|
avatar={avatar && avatar.url}
|
||||||
size={100}
|
size={100}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<RCTextInput
|
<RCTextInput
|
||||||
|
@ -448,8 +467,8 @@ export default class ProfileView extends LoggedView {
|
||||||
testID='profile-view-submit'
|
testID='profile-view-submit'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Loading visible={this.state.saving} />
|
<Loading visible={saving} />
|
||||||
<Dialog.Container visible={this.state.showPasswordAlert}>
|
<Dialog.Container visible={showPasswordAlert}>
|
||||||
<Dialog.Title>
|
<Dialog.Title>
|
||||||
{I18n.t('Please_enter_your_password')}
|
{I18n.t('Please_enter_your_password')}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Keyboard, Text, View, ScrollView, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
Keyboard, Text, View, ScrollView, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { registerSubmit, setUsernameSubmit } from '../actions/login';
|
import { registerSubmit as registerSubmitAction, setUsernameSubmit as setUsernameSubmitAction } from '../actions/login';
|
||||||
import TextInput from '../containers/TextInput';
|
import TextInput from '../containers/TextInput';
|
||||||
import Button from '../containers/Button';
|
import Button from '../containers/Button';
|
||||||
import Loading from '../containers/Loading';
|
import Loading from '../containers/Loading';
|
||||||
|
@ -22,8 +24,8 @@ import I18n from '../i18n';
|
||||||
Accounts_RepeatPasswordPlaceholder: state.settings.Accounts_RepeatPasswordPlaceholder,
|
Accounts_RepeatPasswordPlaceholder: state.settings.Accounts_RepeatPasswordPlaceholder,
|
||||||
login: state.login
|
login: state.login
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
registerSubmit: params => dispatch(registerSubmit(params)),
|
registerSubmit: params => dispatch(registerSubmitAction(params)),
|
||||||
setUsernameSubmit: params => dispatch(setUsernameSubmit(params))
|
setUsernameSubmit: params => dispatch(setUsernameSubmitAction(params))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RegisterView extends LoggedView {
|
export default class RegisterView extends LoggedView {
|
||||||
|
@ -51,28 +53,31 @@ export default class RegisterView extends LoggedView {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
valid() {
|
valid = () => {
|
||||||
const {
|
const {
|
||||||
name, email, password, confirmPassword
|
name, email, password, confirmPassword
|
||||||
} = this.state;
|
} = this.state;
|
||||||
return name.trim() && email.trim() &&
|
return name.trim() && email.trim()
|
||||||
password && confirmPassword && password === confirmPassword;
|
&& password && confirmPassword && password === confirmPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidEmail() {
|
invalidEmail = () => {
|
||||||
return this.props.login.failure && /Email/.test(this.props.login.error.reason) ? this.props.login.error : {};
|
const { login } = this.props;
|
||||||
|
return login.failure && /Email/.test(login.error && login.error.reason) ? login.error : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = () => {
|
submit = () => {
|
||||||
const {
|
const {
|
||||||
name, email, password, code
|
name, email, password, code
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
const { registerSubmit } = this.props;
|
||||||
|
|
||||||
if (!this.valid()) {
|
if (!this.valid()) {
|
||||||
showToast(I18n.t('Some_field_is_invalid_or_empty'));
|
showToast(I18n.t('Some_field_is_invalid_or_empty'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.registerSubmit({
|
registerSubmit({
|
||||||
name, email, pass: password, code
|
name, email, pass: password, code
|
||||||
});
|
});
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
|
@ -80,17 +85,20 @@ export default class RegisterView extends LoggedView {
|
||||||
|
|
||||||
usernameSubmit = () => {
|
usernameSubmit = () => {
|
||||||
const { username } = this.state;
|
const { username } = this.state;
|
||||||
|
const { setUsernameSubmit } = this.props;
|
||||||
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
showToast(I18n.t('Username_is_empty'));
|
showToast(I18n.t('Username_is_empty'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.setUsernameSubmit({ username });
|
setUsernameSubmit({ username });
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
termsService = () => {
|
termsService = () => {
|
||||||
this.props.navigator.push({
|
const { navigator } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'TermsServiceView',
|
screen: 'TermsServiceView',
|
||||||
title: I18n.t('Terms_of_Service'),
|
title: I18n.t('Terms_of_Service'),
|
||||||
backButtonTitle: ''
|
backButtonTitle: ''
|
||||||
|
@ -98,7 +106,8 @@ export default class RegisterView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
privacyPolicy = () => {
|
privacyPolicy = () => {
|
||||||
this.props.navigator.push({
|
const { navigator } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'PrivacyPolicyView',
|
screen: 'PrivacyPolicyView',
|
||||||
title: I18n.t('Privacy_Policy'),
|
title: I18n.t('Privacy_Policy'),
|
||||||
backButtonTitle: ''
|
backButtonTitle: ''
|
||||||
|
@ -106,15 +115,20 @@ export default class RegisterView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderRegister() {
|
_renderRegister() {
|
||||||
if (this.props.login.token) {
|
const { password, confirmPassword } = this.state;
|
||||||
|
const {
|
||||||
|
login, Accounts_NamePlaceholder, Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, Accounts_RepeatPasswordPlaceholder
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (login.token) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={(e) => { this.name = e; }}
|
inputRef={(e) => { this.name = e; }}
|
||||||
label={this.props.Accounts_NamePlaceholder || I18n.t('Name')}
|
label={Accounts_NamePlaceholder || I18n.t('Name')}
|
||||||
placeholder={this.props.Accounts_NamePlaceholder || I18n.t('Name')}
|
placeholder={Accounts_NamePlaceholder || I18n.t('Name')}
|
||||||
returnKeyType='next'
|
returnKeyType='next'
|
||||||
iconLeft='account'
|
iconLeft='account'
|
||||||
onChangeText={name => this.setState({ name })}
|
onChangeText={name => this.setState({ name })}
|
||||||
|
@ -123,8 +137,8 @@ export default class RegisterView extends LoggedView {
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={(e) => { this.email = e; }}
|
inputRef={(e) => { this.email = e; }}
|
||||||
label={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')}
|
label={Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')}
|
||||||
placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')}
|
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')}
|
||||||
returnKeyType='next'
|
returnKeyType='next'
|
||||||
keyboardType='email-address'
|
keyboardType='email-address'
|
||||||
iconLeft='email'
|
iconLeft='email'
|
||||||
|
@ -135,28 +149,28 @@ export default class RegisterView extends LoggedView {
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={(e) => { this.password = e; }}
|
inputRef={(e) => { this.password = e; }}
|
||||||
label={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')}
|
label={Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||||
placeholder={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')}
|
placeholder={Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||||
returnKeyType='next'
|
returnKeyType='next'
|
||||||
iconLeft='key-variant'
|
iconLeft='key-variant'
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
onChangeText={password => this.setState({ password })}
|
onChangeText={value => this.setState({ password: value })}
|
||||||
onSubmitEditing={() => { this.confirmPassword.focus(); }}
|
onSubmitEditing={() => { this.confirmPassword.focus(); }}
|
||||||
testID='register-view-password'
|
testID='register-view-password'
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={(e) => { this.confirmPassword = e; }}
|
inputRef={(e) => { this.confirmPassword = e; }}
|
||||||
inputStyle={
|
inputStyle={
|
||||||
this.state.password &&
|
password
|
||||||
this.state.confirmPassword &&
|
&& confirmPassword
|
||||||
this.state.confirmPassword !== this.state.password ? { borderColor: 'red' } : {}
|
&& confirmPassword !== password ? { borderColor: 'red' } : {}
|
||||||
}
|
}
|
||||||
label={this.props.Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')}
|
label={Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')}
|
||||||
placeholder={this.props.Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')}
|
placeholder={Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')}
|
||||||
returnKeyType='done'
|
returnKeyType='done'
|
||||||
iconLeft='key-variant'
|
iconLeft='key-variant'
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
onChangeText={confirmPassword => this.setState({ confirmPassword })}
|
onChangeText={value => this.setState({ confirmPassword: value })}
|
||||||
onSubmitEditing={this.submit}
|
onSubmitEditing={this.submit}
|
||||||
testID='register-view-repeat-password'
|
testID='register-view-repeat-password'
|
||||||
/>
|
/>
|
||||||
|
@ -180,15 +194,17 @@ export default class RegisterView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderUsername() {
|
_renderUsername() {
|
||||||
if (!this.props.login.token) {
|
const { login, Accounts_UsernamePlaceholder } = this.props;
|
||||||
|
|
||||||
|
if (!login.token) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<TextInput
|
<TextInput
|
||||||
inputRef={(e) => { this.username = e; }}
|
inputRef={(e) => { this.username = e; }}
|
||||||
label={this.props.Accounts_UsernamePlaceholder || I18n.t('Username')}
|
label={Accounts_UsernamePlaceholder || I18n.t('Username')}
|
||||||
placeholder={this.props.Accounts_UsernamePlaceholder || I18n.t('Username')}
|
placeholder={Accounts_UsernamePlaceholder || I18n.t('Username')}
|
||||||
returnKeyType='done'
|
returnKeyType='done'
|
||||||
iconLeft='at'
|
iconLeft='at'
|
||||||
onChangeText={username => this.setState({ username })}
|
onChangeText={username => this.setState({ username })}
|
||||||
|
@ -209,6 +225,7 @@ export default class RegisterView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { login } = this.props;
|
||||||
return (
|
return (
|
||||||
<KeyboardView contentContainerStyle={styles.container}>
|
<KeyboardView contentContainerStyle={styles.container}>
|
||||||
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||||
|
@ -216,13 +233,15 @@ export default class RegisterView extends LoggedView {
|
||||||
<Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_Up')}</Text>
|
<Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_Up')}</Text>
|
||||||
{this._renderRegister()}
|
{this._renderRegister()}
|
||||||
{this._renderUsername()}
|
{this._renderUsername()}
|
||||||
{this.props.login.failure ?
|
{login.failure
|
||||||
<Text style={styles.error} testID='register-view-error'>
|
? (
|
||||||
{this.props.login.error.reason}
|
<Text style={styles.error} testID='register-view-error'>
|
||||||
</Text>
|
{login.error.reason}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
<Loading visible={this.props.login.isFetching} />
|
<Loading visible={login.isFetching} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, SectionList, Text, Alert, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
View, SectionList, Text, Alert, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import Icon from 'react-native-vector-icons/Ionicons';
|
import Icon from 'react-native-vector-icons/Ionicons';
|
||||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { leaveRoom as leaveRoomAction } from '../../actions/room';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
|
@ -13,7 +16,6 @@ import Status from '../../containers/status';
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import { leaveRoom } from '../../actions/room';
|
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -26,7 +28,7 @@ const renderSeparator = () => <View style={styles.separator} />;
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
leaveRoom: rid => dispatch(leaveRoom(rid))
|
leaveRoom: rid => dispatch(leaveRoomAction(rid))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomActionsView extends LoggedView {
|
export default class RoomActionsView extends LoggedView {
|
||||||
|
@ -63,8 +65,10 @@ export default class RoomActionsView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressTouchable = (item) => {
|
onPressTouchable = (item) => {
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
if (item.route) {
|
if (item.route) {
|
||||||
this.props.navigator.push({
|
navigator.push({
|
||||||
screen: item.route,
|
screen: item.route,
|
||||||
title: item.name,
|
title: item.name,
|
||||||
passProps: item.params,
|
passProps: item.params,
|
||||||
|
@ -81,8 +85,10 @@ export default class RoomActionsView extends LoggedView {
|
||||||
rid, t
|
rid, t
|
||||||
} = this.room;
|
} = this.room;
|
||||||
const { allMembers } = this.state;
|
const { allMembers } = this.state;
|
||||||
|
const { username } = this.props;
|
||||||
|
|
||||||
// TODO: same test joined
|
// TODO: same test joined
|
||||||
const userInRoom = !!allMembers.find(m => m.username === this.props.username);
|
const userInRoom = !!allMembers.find(m => m.username === username);
|
||||||
const permissions = RocketChat.hasPermission(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'], rid);
|
const permissions = RocketChat.hasPermission(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'], rid);
|
||||||
|
|
||||||
if (userInRoom && permissions['add-user-to-joined-room']) {
|
if (userInRoom && permissions['add-user-to-joined-room']) {
|
||||||
|
@ -96,8 +102,10 @@ export default class RoomActionsView extends LoggedView {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canViewMembers() {
|
get canViewMembers() {
|
||||||
const { rid, t, broadcast } = this.state.room;
|
const { room } = this.state;
|
||||||
|
const { rid, t, broadcast } = room;
|
||||||
if (broadcast) {
|
if (broadcast) {
|
||||||
const viewBroadcastMemberListPermission = 'view-broadcast-member-list';
|
const viewBroadcastMemberListPermission = 'view-broadcast-member-list';
|
||||||
const permissions = RocketChat.hasPermission([viewBroadcastMemberListPermission], rid);
|
const permissions = RocketChat.hasPermission([viewBroadcastMemberListPermission], rid);
|
||||||
|
@ -107,6 +115,7 @@ export default class RoomActionsView extends LoggedView {
|
||||||
}
|
}
|
||||||
return (t === 'c' || t === 'p');
|
return (t === 'c' || t === 'p');
|
||||||
}
|
}
|
||||||
|
|
||||||
get sections() {
|
get sections() {
|
||||||
const {
|
const {
|
||||||
rid, t, blocker, notifications
|
rid, t, blocker, notifications
|
||||||
|
@ -218,9 +227,9 @@ export default class RoomActionsView extends LoggedView {
|
||||||
actions.push({
|
actions.push({
|
||||||
icon: 'ios-people',
|
icon: 'ios-people',
|
||||||
name: I18n.t('Members'),
|
name: I18n.t('Members'),
|
||||||
description: (onlineMembers.length === 1 ?
|
description: (onlineMembers.length === 1
|
||||||
I18n.t('1_online_member') :
|
? I18n.t('1_online_member')
|
||||||
I18n.t('N_online_members', { n: onlineMembers.length })),
|
: I18n.t('N_online_members', { n: onlineMembers.length })),
|
||||||
route: 'RoomMembersView',
|
route: 'RoomMembersView',
|
||||||
params: { rid, members: onlineMembers },
|
params: { rid, members: onlineMembers },
|
||||||
testID: 'room-actions-members'
|
testID: 'room-actions-members'
|
||||||
|
@ -257,7 +266,8 @@ export default class RoomActionsView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRoomMembers = async() => {
|
updateRoomMembers = async() => {
|
||||||
const { t } = this.state.room;
|
const { room } = this.state;
|
||||||
|
const { rid, t } = room;
|
||||||
|
|
||||||
if (!this.canViewMembers) {
|
if (!this.canViewMembers) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -267,8 +277,8 @@ export default class RoomActionsView extends LoggedView {
|
||||||
let onlineMembers = [];
|
let onlineMembers = [];
|
||||||
let allMembers = [];
|
let allMembers = [];
|
||||||
try {
|
try {
|
||||||
const onlineMembersCall = RocketChat.getRoomMembers(this.state.room.rid, false);
|
const onlineMembersCall = RocketChat.getRoomMembers(rid, false);
|
||||||
const allMembersCall = RocketChat.getRoomMembers(this.state.room.rid, true);
|
const allMembersCall = RocketChat.getRoomMembers(rid, true);
|
||||||
const [onlineMembersResult, allMembersResult] = await Promise.all([onlineMembersCall, allMembersCall]);
|
const [onlineMembersResult, allMembersResult] = await Promise.all([onlineMembersCall, allMembersCall]);
|
||||||
onlineMembers = onlineMembersResult.records;
|
onlineMembers = onlineMembersResult.records;
|
||||||
allMembers = allMembersResult.records;
|
allMembers = allMembersResult.records;
|
||||||
|
@ -280,11 +290,15 @@ export default class RoomActionsView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRoomMember = async() => {
|
updateRoomMember = async() => {
|
||||||
if (this.state.room.t !== 'd') {
|
const { room } = this.state;
|
||||||
|
const { rid, t } = room;
|
||||||
|
const { userId } = this.props;
|
||||||
|
|
||||||
|
if (t !== 'd') {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const member = await RocketChat.getRoomMember(this.state.room.rid, this.props.userId);
|
const member = await RocketChat.getRoomMember(rid, userId);
|
||||||
return { member };
|
return { member };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('RoomActions updateRoomMember', e);
|
log('RoomActions updateRoomMember', e);
|
||||||
|
@ -297,7 +311,8 @@ export default class RoomActionsView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleBlockUser = async() => {
|
toggleBlockUser = async() => {
|
||||||
const { rid, blocker } = this.state.room;
|
const { room } = this.state;
|
||||||
|
const { rid, blocker } = room;
|
||||||
const { member } = this.state;
|
const { member } = this.state;
|
||||||
try {
|
try {
|
||||||
RocketChat.toggleBlockUser(rid, member._id, !blocker);
|
RocketChat.toggleBlockUser(rid, member._id, !blocker);
|
||||||
|
@ -308,6 +323,8 @@ export default class RoomActionsView extends LoggedView {
|
||||||
|
|
||||||
leaveChannel = () => {
|
leaveChannel = () => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
|
const { leaveRoom } = this.props;
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
I18n.t('Are_you_sure_question_mark'),
|
I18n.t('Are_you_sure_question_mark'),
|
||||||
I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: room.t === 'd' ? room.fname : room.name }),
|
I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: room.t === 'd' ? room.fname : room.name }),
|
||||||
|
@ -319,7 +336,7 @@ export default class RoomActionsView extends LoggedView {
|
||||||
{
|
{
|
||||||
text: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
text: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => this.props.leaveRoom(room.rid)
|
onPress: () => leaveRoom(room.rid)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -337,6 +354,8 @@ export default class RoomActionsView extends LoggedView {
|
||||||
renderRoomInfo = ({ item }) => {
|
renderRoomInfo = ({ item }) => {
|
||||||
const { room, member } = this.state;
|
const { room, member } = this.state;
|
||||||
const { name, t, topic } = room;
|
const { name, t, topic } = room;
|
||||||
|
const { baseUrl } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.renderTouchableItem([
|
this.renderTouchableItem([
|
||||||
<Avatar
|
<Avatar
|
||||||
|
@ -345,17 +364,19 @@ export default class RoomActionsView extends LoggedView {
|
||||||
size={50}
|
size={50}
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
type={t}
|
type={t}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
>
|
>
|
||||||
{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
||||||
</Avatar>,
|
</Avatar>,
|
||||||
<View key='name' style={styles.roomTitleContainer}>
|
<View key='name' style={styles.roomTitleContainer}>
|
||||||
{room.t === 'd' ?
|
{room.t === 'd'
|
||||||
<Text style={styles.roomTitle}>{room.fname}</Text> :
|
? <Text style={styles.roomTitle}>{room.fname}</Text>
|
||||||
<View style={styles.roomTitleRow}>
|
: (
|
||||||
<RoomTypeIcon type={room.t} />
|
<View style={styles.roomTitleRow}>
|
||||||
<Text style={styles.roomTitle}>{room.name}</Text>
|
<RoomTypeIcon type={room.t} />
|
||||||
</View>
|
<Text style={styles.roomTitle}>{room.name}</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
<Text style={styles.roomDescription} ellipsizeMode='tail' numberOfLines={1}>{t === 'd' ? `@${ name }` : topic}</Text>
|
<Text style={styles.roomDescription} ellipsizeMode='tail' numberOfLines={1}>{t === 'd' ? `@${ name }` : topic}</Text>
|
||||||
</View>,
|
</View>,
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
FlatList, View, Text, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { openRoomFiles as openRoomFilesAction, closeRoomFiles as closeRoomFilesAction } from '../../actions/roomFiles';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import { openRoomFiles, closeRoomFiles } from '../../actions/roomFiles';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
|
@ -19,8 +21,8 @@ import I18n from '../../i18n';
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
}
|
}
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openRoomFiles: (rid, limit) => dispatch(openRoomFiles(rid, limit)),
|
openRoomFiles: (rid, limit) => dispatch(openRoomFilesAction(rid, limit)),
|
||||||
closeRoomFiles: () => dispatch(closeRoomFiles())
|
closeRoomFiles: () => dispatch(closeRoomFilesAction())
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomFilesView extends LoggedView {
|
export default class RoomFilesView extends LoggedView {
|
||||||
|
@ -47,17 +49,20 @@ export default class RoomFilesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
const { ready } = this.props;
|
||||||
|
if (nextProps.ready && nextProps.ready !== ready) {
|
||||||
this.setState({ loading: false, loadingMore: false });
|
this.setState({ loading: false, loadingMore: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.closeRoomFiles();
|
const { closeRoomFiles } = this.props;
|
||||||
|
closeRoomFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
load = () => {
|
load = () => {
|
||||||
this.props.openRoomFiles(this.props.rid, this.limit);
|
const { openRoomFiles, rid } = this.props;
|
||||||
|
openRoomFiles(rid, this.limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
moreData = () => {
|
moreData = () => {
|
||||||
|
@ -79,15 +84,19 @@ export default class RoomFilesView extends LoggedView {
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderItem = ({ item }) => (
|
renderItem = ({ item }) => {
|
||||||
<Message
|
const { user } = this.props;
|
||||||
item={item}
|
|
||||||
style={styles.message}
|
return (
|
||||||
reactions={item.reactions}
|
<Message
|
||||||
user={this.props.user}
|
item={item}
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
style={styles.message}
|
||||||
/>
|
reactions={item.reactions}
|
||||||
)
|
user={user}
|
||||||
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { messages, ready } = this.props;
|
const { messages, ready } = this.props;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { View, Text, Switch } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../Styles';
|
||||||
|
|
||||||
export default class SwitchContainer extends React.PureComponent {
|
export default class SwitchContainer extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, View, ScrollView, TouchableOpacity, SafeAreaView, Keyboard, Alert } from 'react-native';
|
import {
|
||||||
|
Text, View, ScrollView, TouchableOpacity, SafeAreaView, Keyboard, Alert
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { eraseRoom as eraseRoomAction } from '../../actions/room';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import KeyboardView from '../../presentation/KeyboardView';
|
import KeyboardView from '../../presentation/KeyboardView';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
|
@ -11,7 +14,6 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import { showErrorAlert, showToast } from '../../utils/info';
|
import { showErrorAlert, showToast } from '../../utils/info';
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import { eraseRoom } from '../../actions/room';
|
|
||||||
import RCTextInput from '../../containers/TextInput';
|
import RCTextInput from '../../containers/TextInput';
|
||||||
import Loading from '../../containers/Loading';
|
import Loading from '../../containers/Loading';
|
||||||
import SwitchContainer from './SwitchContainer';
|
import SwitchContainer from './SwitchContainer';
|
||||||
|
@ -35,7 +37,7 @@ const PERMISSIONS_ARRAY = [
|
||||||
];
|
];
|
||||||
|
|
||||||
@connect(null, dispatch => ({
|
@connect(null, dispatch => ({
|
||||||
eraseRoom: rid => dispatch(eraseRoom(rid))
|
eraseRoom: rid => dispatch(eraseRoomAction(rid))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomInfoEditView extends LoggedView {
|
export default class RoomInfoEditView extends LoggedView {
|
||||||
|
@ -66,10 +68,11 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
|
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
const { room } = this.state;
|
||||||
await this.updateRoom();
|
await this.updateRoom();
|
||||||
this.init();
|
this.init();
|
||||||
this.rooms.addListener(this.updateRoom);
|
this.rooms.addListener(this.updateRoom);
|
||||||
this.permissions = RocketChat.hasPermission(PERMISSIONS_ARRAY, this.state.room.rid);
|
this.permissions = RocketChat.hasPermission(PERMISSIONS_ARRAY, room.rid);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -82,9 +85,10 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
init = () => {
|
init = () => {
|
||||||
|
const { room } = this.state;
|
||||||
const {
|
const {
|
||||||
name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired
|
name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired
|
||||||
} = this.state.room;
|
} = room;
|
||||||
// fake password just to user knows about it
|
// fake password just to user knows about it
|
||||||
this.randomValue = random(15);
|
this.randomValue = random(15);
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -114,14 +118,14 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
const {
|
const {
|
||||||
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode
|
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode
|
||||||
} = this.state;
|
} = this.state;
|
||||||
return !(room.name === name &&
|
return !(room.name === name
|
||||||
room.description === description &&
|
&& room.description === description
|
||||||
room.topic === topic &&
|
&& room.topic === topic
|
||||||
room.announcement === announcement &&
|
&& room.announcement === announcement
|
||||||
this.randomValue === joinCode &&
|
&& this.randomValue === joinCode
|
||||||
room.t === 'p' === t &&
|
&& room.t === 'p' === t
|
||||||
room.ro === ro &&
|
&& room.ro === ro
|
||||||
room.reactWhenReadOnly === reactWhenReadOnly
|
&& room.reactWhenReadOnly === reactWhenReadOnly
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +203,9 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
delete = () => {
|
delete = () => {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { eraseRoom } = this.props;
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
I18n.t('Are_you_sure_question_mark'),
|
I18n.t('Are_you_sure_question_mark'),
|
||||||
I18n.t('Delete_Room_Warning'),
|
I18n.t('Delete_Room_Warning'),
|
||||||
|
@ -210,7 +217,7 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
{
|
{
|
||||||
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
|
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => this.props.eraseRoom(this.state.room.rid)
|
onPress: () => eraseRoom(room.rid)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
{ cancelable: false }
|
{ cancelable: false }
|
||||||
|
@ -218,7 +225,9 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleArchive = () => {
|
toggleArchive = () => {
|
||||||
const { archived } = this.state.room;
|
const { room } = this.state;
|
||||||
|
const { rid, archived } = room;
|
||||||
|
|
||||||
const action = I18n.t(`${ archived ? 'un' : '' }archive`);
|
const action = I18n.t(`${ archived ? 'un' : '' }archive`);
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
I18n.t('Are_you_sure_question_mark'),
|
I18n.t('Are_you_sure_question_mark'),
|
||||||
|
@ -233,7 +242,7 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
try {
|
try {
|
||||||
RocketChat.toggleArchiveRoom(this.state.room.rid, !archived);
|
RocketChat.toggleArchiveRoom(rid, !archived);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('toggleArchive', e);
|
log('toggleArchive', e);
|
||||||
}
|
}
|
||||||
|
@ -244,9 +253,12 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasDeletePermission = () => (
|
hasDeletePermission = () => {
|
||||||
this.state.room.t === 'p' ? this.permissions[PERMISSION_DELETE_P] : this.permissions[PERMISSION_DELETE_C]
|
const { room } = this.state;
|
||||||
);
|
return (
|
||||||
|
room.t === 'p' ? this.permissions[PERMISSION_DELETE_P] : this.permissions[PERMISSION_DELETE_C]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
hasArchivePermission = () => (
|
hasArchivePermission = () => (
|
||||||
this.permissions[PERMISSION_ARCHIVE] || this.permissions[PERMISSION_UNARCHIVE]
|
this.permissions[PERMISSION_ARCHIVE] || this.permissions[PERMISSION_UNARCHIVE]
|
||||||
|
@ -254,7 +266,7 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode
|
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving
|
||||||
} = this.state;
|
} = this.state;
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
|
@ -328,21 +340,23 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
disabled={!this.permissions[PERMISSION_SET_READONLY] || room.broadcast}
|
disabled={!this.permissions[PERMISSION_SET_READONLY] || room.broadcast}
|
||||||
testID='room-info-edit-view-ro'
|
testID='room-info-edit-view-ro'
|
||||||
/>
|
/>
|
||||||
{ro && !room.broadcast ?
|
{ro && !room.broadcast
|
||||||
<SwitchContainer
|
? (
|
||||||
value={reactWhenReadOnly}
|
<SwitchContainer
|
||||||
leftLabelPrimary={I18n.t('No_Reactions')}
|
value={reactWhenReadOnly}
|
||||||
leftLabelSecondary={I18n.t('Reactions_are_disabled')}
|
leftLabelPrimary={I18n.t('No_Reactions')}
|
||||||
rightLabelPrimary={I18n.t('Allow_Reactions')}
|
leftLabelSecondary={I18n.t('Reactions_are_disabled')}
|
||||||
rightLabelSecondary={I18n.t('Reactions_are_enabled')}
|
rightLabelPrimary={I18n.t('Allow_Reactions')}
|
||||||
onValueChange={value => this.setState({ reactWhenReadOnly: value })}
|
rightLabelSecondary={I18n.t('Reactions_are_enabled')}
|
||||||
disabled={!this.permissions[PERMISSION_SET_REACT_WHEN_READONLY]}
|
onValueChange={value => this.setState({ reactWhenReadOnly: value })}
|
||||||
testID='room-info-edit-view-react-when-ro'
|
disabled={!this.permissions[PERMISSION_SET_REACT_WHEN_READONLY]}
|
||||||
/>
|
testID='room-info-edit-view-react-when-ro'
|
||||||
|
/>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{room.broadcast ?
|
{room.broadcast
|
||||||
[
|
? [
|
||||||
<Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>,
|
<Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>,
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
]
|
]
|
||||||
|
@ -394,7 +408,7 @@ export default class RoomInfoEditView extends LoggedView {
|
||||||
>
|
>
|
||||||
<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>{I18n.t('DELETE')}</Text>
|
<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>{I18n.t('DELETE')}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Loading visible={this.state.saving} />
|
<Loading visible={saving} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, ScrollView, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
View, Text, ScrollView, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@ -20,14 +22,14 @@ import { iconsMap } from '../../Icons';
|
||||||
const PERMISSION_EDIT_ROOM = 'edit-room';
|
const PERMISSION_EDIT_ROOM = 'edit-room';
|
||||||
|
|
||||||
const camelize = str => str.replace(/^(.)/, (match, chr) => chr.toUpperCase());
|
const camelize = str => str.replace(/^(.)/, (match, chr) => chr.toUpperCase());
|
||||||
const getRoomTitle = room => (room.t === 'd' ?
|
const getRoomTitle = room => (room.t === 'd'
|
||||||
<Text testID='room-info-view-name' style={styles.roomTitle}>{room.fname}</Text> :
|
? <Text testID='room-info-view-name' style={styles.roomTitle}>{room.fname}</Text>
|
||||||
[
|
: (
|
||||||
<View style={styles.roomTitleRow}>
|
<View style={styles.roomTitleRow}>
|
||||||
<RoomTypeIcon type={room.t} key='room-info-type' />
|
<RoomTypeIcon type={room.t} key='room-info-type' />
|
||||||
<Text testID='room-info-view-name' style={styles.roomTitle} key='room-info-name'>{room.name}</Text>
|
<Text testID='room-info-view-name' style={styles.roomTitle} key='room-info-name'>{room.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
]
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
|
@ -35,7 +37,7 @@ const getRoomTitle = room => (room.t === 'd' ?
|
||||||
userId: state.login.user && state.login.user.id,
|
userId: state.login.user && state.login.user.id,
|
||||||
activeUsers: state.activeUsers,
|
activeUsers: state.activeUsers,
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||||
roles: state.roles
|
allRoles: state.roles
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomInfoView extends LoggedView {
|
export default class RoomInfoView extends LoggedView {
|
||||||
|
@ -46,7 +48,7 @@ export default class RoomInfoView extends LoggedView {
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
activeUsers: PropTypes.object,
|
activeUsers: PropTypes.object,
|
||||||
Message_TimeFormat: PropTypes.string,
|
Message_TimeFormat: PropTypes.string,
|
||||||
roles: PropTypes.object
|
allRoles: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -64,19 +66,61 @@ export default class RoomInfoView extends LoggedView {
|
||||||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
|
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
await this.updateRoom();
|
this.updateRoom();
|
||||||
this.rooms.addListener(this.updateRoom);
|
this.rooms.addListener(this.updateRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.rooms.removeAllListeners();
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
onNavigatorEvent(event) {
|
||||||
|
const { rid, navigator } = this.props;
|
||||||
|
if (event.type === 'NavBarButtonPress') {
|
||||||
|
if (event.id === 'edit') {
|
||||||
|
navigator.push({
|
||||||
|
screen: 'RoomInfoEditView',
|
||||||
|
title: I18n.t('Room_Info_Edit'),
|
||||||
|
backButtonTitle: '',
|
||||||
|
passProps: {
|
||||||
|
rid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullUserData = async(username) => {
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.subscribe('fullUserData', username);
|
||||||
|
this.sub = result;
|
||||||
|
} catch (e) {
|
||||||
|
log('getFullUserData', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirect = () => {
|
||||||
|
const { room: { t } } = this.state;
|
||||||
|
return t === 'd';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRoom = async() => {
|
||||||
|
const { userId, activeUsers, navigator } = this.props;
|
||||||
|
|
||||||
|
const [room] = this.rooms;
|
||||||
|
this.setState({ room });
|
||||||
|
|
||||||
// get user of room
|
// get user of room
|
||||||
if (this.state.room) {
|
if (room) {
|
||||||
if (this.state.room.t === 'd') {
|
if (room.t === 'd') {
|
||||||
try {
|
try {
|
||||||
const roomUser = await RocketChat.getRoomMember(this.state.room.rid, this.props.userId);
|
const roomUser = await RocketChat.getRoomMember(room.rid, userId);
|
||||||
this.setState({ roomUser });
|
this.setState({ roomUser });
|
||||||
const username = this.state.room.name;
|
const username = room.name;
|
||||||
|
|
||||||
const activeUser = this.props.activeUsers[roomUser._id];
|
const activeUser = activeUsers[roomUser._id];
|
||||||
if (!activeUser || !activeUser.utcOffset) {
|
if (!activeUser || !activeUser.utcOffset) {
|
||||||
// get full user data looking for utcOffset
|
// get full user data looking for utcOffset
|
||||||
// will be catched by .on('users) and saved on activeUsers reducer
|
// will be catched by .on('users) and saved on activeUsers reducer
|
||||||
|
@ -94,9 +138,14 @@ export default class RoomInfoView extends LoggedView {
|
||||||
log('RoomInfoView.componentDidMount', e);
|
log('RoomInfoView.componentDidMount', e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], this.state.room.rid);
|
const isVisible = await navigator.screenIsCurrentlyVisible();
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
||||||
if (permissions[PERMISSION_EDIT_ROOM]) {
|
if (permissions[PERMISSION_EDIT_ROOM]) {
|
||||||
this.props.navigator.setButtons({
|
navigator.setButtons({
|
||||||
rightButtons: [{
|
rightButtons: [{
|
||||||
id: 'edit',
|
id: 'edit',
|
||||||
icon: iconsMap.create,
|
icon: iconsMap.create,
|
||||||
|
@ -108,42 +157,6 @@ export default class RoomInfoView extends LoggedView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.rooms.removeAllListeners();
|
|
||||||
this.sub.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
onNavigatorEvent(event) {
|
|
||||||
if (event.type === 'NavBarButtonPress') {
|
|
||||||
if (event.id === 'edit') {
|
|
||||||
this.props.navigator.push({
|
|
||||||
screen: 'RoomInfoEditView',
|
|
||||||
title: I18n.t('Room_Info_Edit'),
|
|
||||||
backButtonTitle: '',
|
|
||||||
passProps: {
|
|
||||||
rid: this.props.rid
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getFullUserData = async(username) => {
|
|
||||||
try {
|
|
||||||
const result = await RocketChat.subscribe('fullUserData', username);
|
|
||||||
this.sub = result;
|
|
||||||
} catch (e) {
|
|
||||||
log('getFullUserData', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isDirect = () => this.state.room.t === 'd';
|
|
||||||
|
|
||||||
updateRoom = async() => {
|
|
||||||
const [room] = this.rooms;
|
|
||||||
this.setState({ room });
|
|
||||||
}
|
|
||||||
|
|
||||||
renderItem = (key, room) => (
|
renderItem = (key, room) => (
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
<Text style={styles.itemLabel}>{I18n.t(camelize(key))}</Text>
|
<Text style={styles.itemLabel}>{I18n.t(camelize(key))}</Text>
|
||||||
|
@ -155,24 +168,33 @@ export default class RoomInfoView extends LoggedView {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
renderRoles = () => (
|
renderRoles = () => {
|
||||||
this.state.roles.length > 0 ?
|
const { roles } = this.state;
|
||||||
<View style={styles.item}>
|
const { allRoles } = this.props;
|
||||||
<Text style={styles.itemLabel}>{I18n.t('Roles')}</Text>
|
|
||||||
<View style={styles.rolesContainer}>
|
return (
|
||||||
{this.state.roles.map(role => (
|
roles.length > 0
|
||||||
<View style={styles.roleBadge} key={role}>
|
? (
|
||||||
<Text>{ this.props.roles[role] }</Text>
|
<View style={styles.item}>
|
||||||
|
<Text style={styles.itemLabel}>{I18n.t('Roles')}</Text>
|
||||||
|
<View style={styles.rolesContainer}>
|
||||||
|
{roles.map(role => (
|
||||||
|
<View style={styles.roleBadge} key={role}>
|
||||||
|
<Text>{ allRoles[role] }</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
))}
|
</View>
|
||||||
</View>
|
)
|
||||||
</View>
|
: null
|
||||||
: null
|
);
|
||||||
)
|
}
|
||||||
|
|
||||||
renderTimezone = (userId) => {
|
renderTimezone = (userId) => {
|
||||||
if (this.props.activeUsers[userId]) {
|
const { activeUsers, Message_TimeFormat } = this.props;
|
||||||
const { utcOffset } = this.props.activeUsers[userId];
|
|
||||||
|
if (activeUsers[userId]) {
|
||||||
|
const { utcOffset } = activeUsers[userId];
|
||||||
|
|
||||||
if (!utcOffset) {
|
if (!utcOffset) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -180,24 +202,28 @@ export default class RoomInfoView extends LoggedView {
|
||||||
return (
|
return (
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
<Text style={styles.itemLabel}>{I18n.t('Timezone')}</Text>
|
<Text style={styles.itemLabel}>{I18n.t('Timezone')}</Text>
|
||||||
<Text style={styles.itemContent}>{moment().utcOffset(utcOffset).format(this.props.Message_TimeFormat)} (UTC { utcOffset })</Text>
|
<Text style={styles.itemContent}>{moment().utcOffset(utcOffset).format(Message_TimeFormat)} (UTC { utcOffset })</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAvatar = (room, roomUser) => (
|
renderAvatar = (room, roomUser) => {
|
||||||
<Avatar
|
const { baseUrl } = this.props;
|
||||||
text={room.name}
|
|
||||||
size={100}
|
return (
|
||||||
style={styles.avatar}
|
<Avatar
|
||||||
type={room.t}
|
text={room.name}
|
||||||
baseUrl={this.props.baseUrl}
|
size={100}
|
||||||
>
|
style={styles.avatar}
|
||||||
{room.t === 'd' ? <Status style={[sharedStyles.status, styles.status]} id={roomUser._id} /> : null}
|
type={room.t}
|
||||||
</Avatar>
|
baseUrl={baseUrl}
|
||||||
)
|
>
|
||||||
|
{room.t === 'd' ? <Status style={[sharedStyles.status, styles.status]} id={roomUser._id} /> : null}
|
||||||
|
</Avatar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderBroadcast = () => (
|
renderBroadcast = () => (
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FlatList, View, Vibration, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
FlatList, View, Vibration, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import ActionSheet from 'react-native-actionsheet';
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
@ -37,6 +39,8 @@ export default class RoomMembersView extends LoggedView {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super('MentionedMessagesView', props);
|
super('MentionedMessagesView', props);
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
this.CANCEL_INDEX = 0;
|
this.CANCEL_INDEX = 0;
|
||||||
this.MUTE_INDEX = 1;
|
this.MUTE_INDEX = 1;
|
||||||
this.actionSheetOptions = [''];
|
this.actionSheetOptions = [''];
|
||||||
|
@ -52,7 +56,7 @@ export default class RoomMembersView extends LoggedView {
|
||||||
userLongPressed: {},
|
userLongPressed: {},
|
||||||
room: {}
|
room: {}
|
||||||
};
|
};
|
||||||
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
|
navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -64,16 +68,19 @@ export default class RoomMembersView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onNavigatorEvent(event) {
|
async onNavigatorEvent(event) {
|
||||||
|
const { rid, allUsers } = this.state;
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
if (event.type === 'NavBarButtonPress') {
|
if (event.type === 'NavBarButtonPress') {
|
||||||
if (event.id === 'toggleOnline') {
|
if (event.id === 'toggleOnline') {
|
||||||
try {
|
try {
|
||||||
const allUsers = !this.state.allUsers;
|
const allUsersFilter = !allUsers;
|
||||||
const membersResult = await RocketChat.getRoomMembers(this.state.rid, allUsers);
|
const membersResult = await RocketChat.getRoomMembers(rid, allUsersFilter);
|
||||||
const members = membersResult.records;
|
const members = membersResult.records;
|
||||||
this.setState({ allUsers, members });
|
this.setState({ allUsers: allUsersFilter, members });
|
||||||
this.props.navigator.setButtons({
|
navigator.setButtons({
|
||||||
rightButtons: [{
|
rightButtons: [{
|
||||||
title: this.state.allUsers ? I18n.t('Online') : I18n.t('All'),
|
title: allUsers ? I18n.t('Online') : I18n.t('All'),
|
||||||
id: 'toggleOnline',
|
id: 'toggleOnline',
|
||||||
testID: 'room-members-view-toggle-status'
|
testID: 'room-members-view-toggle-status'
|
||||||
}]
|
}]
|
||||||
|
@ -86,9 +93,11 @@ export default class RoomMembersView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchChangeText = (text) => {
|
onSearchChangeText = (text) => {
|
||||||
|
const { members } = this.state;
|
||||||
|
|
||||||
let membersFiltered = [];
|
let membersFiltered = [];
|
||||||
if (text) {
|
if (text) {
|
||||||
membersFiltered = this.state.members.filter(m => m.username.toLowerCase().match(text.toLowerCase()));
|
membersFiltered = members.filter(m => m.username.toLowerCase().match(text.toLowerCase()));
|
||||||
}
|
}
|
||||||
this.setState({ filtering: !!text, membersFiltered });
|
this.setState({ filtering: !!text, membersFiltered });
|
||||||
}
|
}
|
||||||
|
@ -111,8 +120,10 @@ export default class RoomMembersView extends LoggedView {
|
||||||
if (!this.permissions['mute-user']) {
|
if (!this.permissions['mute-user']) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { room } = this.state;
|
||||||
|
const { muted } = room;
|
||||||
|
|
||||||
this.actionSheetOptions = [I18n.t('Cancel')];
|
this.actionSheetOptions = [I18n.t('Cancel')];
|
||||||
const { muted } = this.state.room;
|
|
||||||
const userIsMuted = !!muted.find(m => m.value === user.username);
|
const userIsMuted = !!muted.find(m => m.value === user.username);
|
||||||
user.muted = userIsMuted;
|
user.muted = userIsMuted;
|
||||||
if (userIsMuted) {
|
if (userIsMuted) {
|
||||||
|
@ -133,9 +144,10 @@ export default class RoomMembersView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
goRoom = ({ rid, name }) => {
|
goRoom = ({ rid, name }) => {
|
||||||
this.props.navigator.popToRoot();
|
const { navigator } = this.props;
|
||||||
|
navigator.popToRoot();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.props.navigator.push({
|
navigator.push({
|
||||||
screen: 'RoomView',
|
screen: 'RoomView',
|
||||||
title: name,
|
title: name,
|
||||||
backButtonTitle: '',
|
backButtonTitle: '',
|
||||||
|
@ -170,16 +182,20 @@ export default class RoomMembersView extends LoggedView {
|
||||||
|
|
||||||
renderSeparator = () => <View style={styles.separator} />;
|
renderSeparator = () => <View style={styles.separator} />;
|
||||||
|
|
||||||
renderItem = ({ item }) => (
|
renderItem = ({ item }) => {
|
||||||
<UserItem
|
const { baseUrl } = this.props;
|
||||||
name={item.name}
|
|
||||||
username={item.username}
|
return (
|
||||||
onPress={() => this.onPressUser(item)}
|
<UserItem
|
||||||
onLongPress={() => this.onLongPressUser(item)}
|
name={item.name}
|
||||||
baseUrl={this.props.baseUrl}
|
username={item.username}
|
||||||
testID={`room-members-view-item-${ item.username }`}
|
onPress={() => this.onPressUser(item)}
|
||||||
/>
|
onLongPress={() => this.onLongPressUser(item)}
|
||||||
)
|
baseUrl={baseUrl}
|
||||||
|
testID={`room-members-view-item-${ item.username }`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { filtering, members, membersFiltered } = this.state;
|
const { filtering, members, membersFiltered } = this.state;
|
||||||
|
|
|
@ -35,6 +35,7 @@ export class List extends React.Component {
|
||||||
room: PropTypes.string,
|
room: PropTypes.string,
|
||||||
end: PropTypes.bool
|
end: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.data = database
|
this.data = database
|
||||||
|
@ -43,16 +44,22 @@ export class List extends React.Component {
|
||||||
.sorted('ts', true);
|
.sorted('ts', true);
|
||||||
this.dataSource = ds.cloneWithRows(this.data);
|
this.dataSource = ds.cloneWithRows(this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.data.addListener(this.updateState);
|
this.data.addListener(this.updateState);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return this.props.end !== nextProps.end;
|
const { end } = this.props;
|
||||||
|
return end !== nextProps.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.data.removeAllListeners();
|
this.data.removeAllListeners();
|
||||||
this.updateState.stop();
|
this.updateState.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
updateState = throttle(() => {
|
updateState = throttle(() => {
|
||||||
// this.setState({
|
// this.setState({
|
||||||
this.dataSource = this.dataSource.cloneWithRows(this.data);
|
this.dataSource = this.dataSource.cloneWithRows(this.data);
|
||||||
|
@ -62,6 +69,8 @@ export class List extends React.Component {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { renderFooter, onEndReached, renderRow } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListView
|
<ListView
|
||||||
enableEmptySections
|
enableEmptySections
|
||||||
|
@ -69,11 +78,11 @@ export class List extends React.Component {
|
||||||
data={this.data}
|
data={this.data}
|
||||||
keyExtractor={item => item._id}
|
keyExtractor={item => item._id}
|
||||||
onEndReachedThreshold={100}
|
onEndReachedThreshold={100}
|
||||||
renderFooter={this.props.renderFooter}
|
renderFooter={renderFooter}
|
||||||
renderHeader={() => <Typing />}
|
renderHeader={() => <Typing />}
|
||||||
onEndReached={() => this.props.onEndReached(this.data[this.data.length - 1])}
|
onEndReached={() => onEndReached(this.data[this.data.length - 1])}
|
||||||
dataSource={this.dataSource}
|
dataSource={this.dataSource}
|
||||||
renderRow={(item, previousItem) => this.props.renderRow(item, previousItem)}
|
renderRow={(item, previousItem) => renderRow(item, previousItem)}
|
||||||
initialListSize={1}
|
initialListSize={1}
|
||||||
pageSize={20}
|
pageSize={20}
|
||||||
testID='room-view-messages'
|
testID='room-view-messages'
|
||||||
|
@ -106,7 +115,9 @@ export class ListView extends OldList2 {
|
||||||
setNativeProps(props) {
|
setNativeProps(props) {
|
||||||
this.refs.listView.setNativeProps(props);
|
this.refs.listView.setNativeProps(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DataSource = DataSource;
|
static DataSource = DataSource;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const bodyComponents = [];
|
const bodyComponents = [];
|
||||||
|
|
||||||
|
@ -132,9 +143,9 @@ export class ListView extends OldList2 {
|
||||||
continue; // eslint-disable-line
|
continue; // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
const showUnreadSeparator = this.props.lastOpen &&
|
const showUnreadSeparator = this.props.lastOpen
|
||||||
moment(message.ts).isAfter(this.props.lastOpen) &&
|
&& moment(message.ts).isAfter(this.props.lastOpen)
|
||||||
moment(previousMessage.ts).isBefore(this.props.lastOpen);
|
&& moment(previousMessage.ts).isBefore(this.props.lastOpen);
|
||||||
const showDateSeparator = !moment(message.ts).isSame(previousMessage.ts, 'day');
|
const showDateSeparator = !moment(message.ts).isSame(previousMessage.ts, 'day');
|
||||||
|
|
||||||
if (showUnreadSeparator || showDateSeparator) {
|
if (showUnreadSeparator || showDateSeparator) {
|
||||||
|
|
|
@ -4,8 +4,9 @@ import { View, Platform } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Modal from 'react-native-modal';
|
import Modal from 'react-native-modal';
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
import { responsive } from 'react-native-responsive-ui';
|
||||||
|
|
||||||
import EmojiPicker from '../../containers/EmojiPicker';
|
import EmojiPicker from '../../containers/EmojiPicker';
|
||||||
import { toggleReactionPicker } from '../../actions/messages';
|
import { toggleReactionPicker as toggleReactionPickerAction } from '../../actions/messages';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const margin = Platform.OS === 'android' ? 40 : 20;
|
const margin = Platform.OS === 'android' ? 40 : 20;
|
||||||
|
@ -15,7 +16,7 @@ const tabEmojiStyle = { fontSize: 15 };
|
||||||
showReactionPicker: state.messages.showReactionPicker,
|
showReactionPicker: state.messages.showReactionPicker,
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
|
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message))
|
||||||
}))
|
}))
|
||||||
@responsive
|
@responsive
|
||||||
export default class ReactionPicker extends React.Component {
|
export default class ReactionPicker extends React.Component {
|
||||||
|
@ -28,40 +29,47 @@ export default class ReactionPicker extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
return nextProps.showReactionPicker !== this.props.showReactionPicker || this.props.window.width !== nextProps.window.width;
|
const { showReactionPicker, window } = this.props;
|
||||||
|
return nextProps.showReactionPicker !== showReactionPicker || window.width !== nextProps.window.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmojiSelected(emoji, shortname) {
|
onEmojiSelected(emoji, shortname) {
|
||||||
// standard emojis: `emoji` is unicode and `shortname` is :joy:
|
// standard emojis: `emoji` is unicode and `shortname` is :joy:
|
||||||
// custom emojis: only `emoji` is returned with shortname type (:joy:)
|
// custom emojis: only `emoji` is returned with shortname type (:joy:)
|
||||||
// to set reactions, we need shortname type
|
// to set reactions, we need shortname type
|
||||||
this.props.onEmojiSelected(shortname || emoji);
|
const { onEmojiSelected } = this.props;
|
||||||
|
onEmojiSelected(shortname || emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { window: { width, height }, showReactionPicker, baseUrl } = this.props;
|
const {
|
||||||
|
window: { width, height }, showReactionPicker, baseUrl, toggleReactionPicker
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (showReactionPicker ?
|
return (showReactionPicker
|
||||||
<Modal
|
? (
|
||||||
isVisible={this.props.showReactionPicker}
|
<Modal
|
||||||
style={{ alignItems: 'center' }}
|
isVisible={showReactionPicker}
|
||||||
onBackdropPress={() => this.props.toggleReactionPicker()}
|
style={{ alignItems: 'center' }}
|
||||||
onBackButtonPress={() => this.props.toggleReactionPicker()}
|
onBackdropPress={() => toggleReactionPicker()}
|
||||||
animationIn='fadeIn'
|
onBackButtonPress={() => toggleReactionPicker()}
|
||||||
animationOut='fadeOut'
|
animationIn='fadeIn'
|
||||||
>
|
animationOut='fadeOut'
|
||||||
<View
|
|
||||||
style={[styles.reactionPickerContainer, { width: width - margin, height: Math.min(width, height) - (margin * 2) }]}
|
|
||||||
testID='reaction-picker'
|
|
||||||
>
|
>
|
||||||
<EmojiPicker
|
<View
|
||||||
tabEmojiStyle={tabEmojiStyle}
|
style={[styles.reactionPickerContainer, { width: width - margin, height: Math.min(width, height) - (margin * 2) }]}
|
||||||
width={Math.min(width, height) - margin}
|
testID='reaction-picker'
|
||||||
onEmojiSelected={(emoji, shortname) => this.onEmojiSelected(emoji, shortname)}
|
>
|
||||||
baseUrl={baseUrl}
|
<EmojiPicker
|
||||||
/>
|
tabEmojiStyle={tabEmojiStyle}
|
||||||
</View>
|
width={Math.min(width, height) - margin}
|
||||||
</Modal> : null
|
onEmojiSelected={(emoji, shortname) => this.onEmojiSelected(emoji, shortname)}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
: null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
|
import {
|
||||||
|
View, Text, StyleSheet, TouchableOpacity, ScrollView
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
import { responsive } from 'react-native-responsive-ui';
|
||||||
|
@ -64,7 +66,8 @@ export default class UploadProgress extends Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
uploads: []
|
uploads: []
|
||||||
};
|
};
|
||||||
this.uploads = database.objects('uploads').filtered('rid = $0', this.props.rid);
|
const { rid } = this.props;
|
||||||
|
this.uploads = database.objects('uploads').filtered('rid = $0', rid);
|
||||||
this.uploads.addListener(this.updateUploads);
|
this.uploads.addListener(this.updateUploads);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,11 +101,13 @@ export default class UploadProgress extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
tryAgain = async(item) => {
|
tryAgain = async(item) => {
|
||||||
|
const { rid } = this.props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
item.error = false;
|
item.error = false;
|
||||||
});
|
});
|
||||||
await RocketChat.sendFileMessage(this.props.rid, JSON.parse(JSON.stringify(item)));
|
await RocketChat.sendFileMessage(rid, JSON.parse(JSON.stringify(item)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('UploadProgess.tryAgain', e);
|
log('UploadProgess.tryAgain', e);
|
||||||
}
|
}
|
||||||
|
@ -113,6 +118,8 @@ export default class UploadProgress extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItemContent = (item) => {
|
renderItemContent = (item) => {
|
||||||
|
const { window } = this.props;
|
||||||
|
|
||||||
if (!item.error) {
|
if (!item.error) {
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
|
@ -123,7 +130,7 @@ export default class UploadProgress extends Component {
|
||||||
</Text>
|
</Text>
|
||||||
<Icon name='close' size={20} color='#9EA2A8' onPress={() => this.cancelUpload(item)} />
|
<Icon name='close' size={20} color='#9EA2A8' onPress={() => this.cancelUpload(item)} />
|
||||||
</View>,
|
</View>,
|
||||||
<View key='progress' style={[styles.progress, { width: (this.props.window.width * item.progress) / 100 }]} />
|
<View key='progress' style={[styles.progress, { width: (window.width * item.progress) / 100 }]} />
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, View, LayoutAnimation, ActivityIndicator, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
Text, View, LayoutAnimation, ActivityIndicator, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
|
import { openRoom as openRoomAction, closeRoom as closeRoomAction, setLastOpen as setLastOpenAction } from '../../actions/room';
|
||||||
|
import { toggleReactionPicker as toggleReactionPickerAction, actionsShow as actionsShowAction } from '../../actions/messages';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import { List } from './ListView';
|
import { List } from './ListView';
|
||||||
import { openRoom, closeRoom, setLastOpen } from '../../actions/room';
|
|
||||||
import { toggleReactionPicker, actionsShow } from '../../actions/messages';
|
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
|
@ -33,11 +35,11 @@ import { iconsMap } from '../../Icons';
|
||||||
showActions: state.messages.showActions,
|
showActions: state.messages.showActions,
|
||||||
showErrorActions: state.messages.showErrorActions
|
showErrorActions: state.messages.showErrorActions
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openRoom: room => dispatch(openRoom(room)),
|
openRoom: room => dispatch(openRoomAction(room)),
|
||||||
setLastOpen: date => dispatch(setLastOpen(date)),
|
setLastOpen: date => dispatch(setLastOpenAction(date)),
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message)),
|
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
||||||
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
|
actionsShow: actionMessage => dispatch(actionsShowAction(actionMessage)),
|
||||||
close: () => dispatch(closeRoom())
|
closeRoom: () => dispatch(closeRoomAction())
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomView extends LoggedView {
|
export default class RoomView extends LoggedView {
|
||||||
|
@ -56,12 +58,12 @@ export default class RoomView extends LoggedView {
|
||||||
actionMessage: PropTypes.object,
|
actionMessage: PropTypes.object,
|
||||||
toggleReactionPicker: PropTypes.func.isRequired,
|
toggleReactionPicker: PropTypes.func.isRequired,
|
||||||
actionsShow: PropTypes.func,
|
actionsShow: PropTypes.func,
|
||||||
close: PropTypes.func
|
closeRoom: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super('RoomView', props);
|
super('RoomView', props);
|
||||||
this.rid = this.props.rid;
|
this.rid = props.rid;
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
this.state = {
|
this.state = {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
|
@ -74,7 +76,9 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props.navigator.setButtons({
|
const { navigator } = this.props;
|
||||||
|
|
||||||
|
navigator.setButtons({
|
||||||
rightButtons: [{
|
rightButtons: [{
|
||||||
id: 'more',
|
id: 'more',
|
||||||
testID: 'room-view-header-actions',
|
testID: 'room-view-header-actions',
|
||||||
|
@ -88,9 +92,11 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
this.updateRoom();
|
this.updateRoom();
|
||||||
this.rooms.addListener(this.updateRoom);
|
this.rooms.addListener(this.updateRoom);
|
||||||
this.props.navigator.setDrawerEnabled({
|
navigator.setDrawerEnabled({
|
||||||
side: 'left',
|
side: 'left',
|
||||||
enabled: false
|
enabled: false
|
||||||
});
|
});
|
||||||
|
@ -98,12 +104,16 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
return !(equal(this.props, nextProps) && equal(this.state, nextState) && this.state.room.ro === nextState.room.ro);
|
const { room } = this.state;
|
||||||
|
return !(equal(this.props, nextProps) && equal(this.state, nextState) && room.ro === nextState.room.ro);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (prevState.room.f !== this.state.room.f) {
|
const { room } = this.state;
|
||||||
this.props.navigator.setButtons({
|
const { navigator } = this.props;
|
||||||
|
|
||||||
|
if (prevState.room.f !== room.f) {
|
||||||
|
navigator.setButtons({
|
||||||
rightButtons: [{
|
rightButtons: [{
|
||||||
id: 'more',
|
id: 'more',
|
||||||
testID: 'room-view-header-actions',
|
testID: 'room-view-header-actions',
|
||||||
|
@ -111,32 +121,37 @@ export default class RoomView extends LoggedView {
|
||||||
}, {
|
}, {
|
||||||
id: 'star',
|
id: 'star',
|
||||||
testID: 'room-view-header-star',
|
testID: 'room-view-header-star',
|
||||||
icon: this.state.room.f ? iconsMap.star : iconsMap.starOutline
|
icon: room.f ? iconsMap.star : iconsMap.starOutline
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
const { closeRoom } = this.props;
|
||||||
this.rooms.removeAllListeners();
|
this.rooms.removeAllListeners();
|
||||||
this.onEndReached.stop();
|
this.onEndReached.stop();
|
||||||
this.props.close();
|
closeRoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
onNavigatorEvent(event) {
|
onNavigatorEvent(event) {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { rid, f } = room;
|
||||||
|
const { navigator } = this.props;
|
||||||
|
|
||||||
if (event.type === 'NavBarButtonPress') {
|
if (event.type === 'NavBarButtonPress') {
|
||||||
if (event.id === 'more') {
|
if (event.id === 'more') {
|
||||||
this.props.navigator.push({
|
navigator.push({
|
||||||
screen: 'RoomActionsView',
|
screen: 'RoomActionsView',
|
||||||
title: I18n.t('Actions'),
|
title: I18n.t('Actions'),
|
||||||
backButtonTitle: '',
|
backButtonTitle: '',
|
||||||
passProps: {
|
passProps: {
|
||||||
rid: this.state.room.rid
|
rid
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (event.id === 'star') {
|
} else if (event.id === 'star') {
|
||||||
try {
|
try {
|
||||||
RocketChat.toggleFavorite(this.state.room.rid, this.state.room.f);
|
RocketChat.toggleFavorite(rid, f);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('toggleFavorite', e);
|
log('toggleFavorite', e);
|
||||||
}
|
}
|
||||||
|
@ -151,8 +166,9 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(async() => {
|
requestAnimationFrame(async() => {
|
||||||
|
const { room } = this.state;
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: this.state.room.t, latest: lastRowData.ts });
|
const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: room.t, latest: lastRowData.ts });
|
||||||
this.setState({ end: result < 20 });
|
this.setState({ end: result < 20 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('RoomView.onEndReached', e);
|
log('RoomView.onEndReached', e);
|
||||||
|
@ -161,14 +177,16 @@ export default class RoomView extends LoggedView {
|
||||||
})
|
})
|
||||||
|
|
||||||
onMessageLongPress = (message) => {
|
onMessageLongPress = (message) => {
|
||||||
this.props.actionsShow(message);
|
const { actionsShow } = this.props;
|
||||||
|
actionsShow(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
onReactionPress = (shortname, messageId) => {
|
onReactionPress = (shortname, messageId) => {
|
||||||
|
const { actionMessage, toggleReactionPicker } = this.props;
|
||||||
try {
|
try {
|
||||||
if (!messageId) {
|
if (!messageId) {
|
||||||
RocketChat.setReaction(shortname, this.props.actionMessage._id);
|
RocketChat.setReaction(shortname, actionMessage._id);
|
||||||
return this.props.toggleReactionPicker();
|
return toggleReactionPicker();
|
||||||
}
|
}
|
||||||
RocketChat.setReaction(shortname, messageId);
|
RocketChat.setReaction(shortname, messageId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -177,53 +195,71 @@ export default class RoomView extends LoggedView {
|
||||||
};
|
};
|
||||||
|
|
||||||
updateRoom = async() => {
|
updateRoom = async() => {
|
||||||
|
const { navigator, openRoom, setLastOpen } = this.props;
|
||||||
|
|
||||||
if (this.rooms.length > 0) {
|
if (this.rooms.length > 0) {
|
||||||
const { room: prevRoom } = this.state;
|
const { room: prevRoom } = this.state;
|
||||||
const room = JSON.parse(JSON.stringify(this.rooms[0]));
|
const room = JSON.parse(JSON.stringify(this.rooms[0]));
|
||||||
this.setState({ room });
|
this.setState({ room });
|
||||||
|
|
||||||
if (!prevRoom.rid) {
|
if (!prevRoom.rid) {
|
||||||
this.props.navigator.setTitle({ title: room.name });
|
navigator.setTitle({ title: room.name });
|
||||||
this.props.openRoom({
|
openRoom({
|
||||||
...room
|
...room
|
||||||
});
|
});
|
||||||
if (room.alert || room.unread || room.userMentions) {
|
if (room.alert || room.unread || room.userMentions) {
|
||||||
this.props.setLastOpen(room.ls);
|
setLastOpen(room.ls);
|
||||||
} else {
|
} else {
|
||||||
this.props.setLastOpen(null);
|
setLastOpen(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.props.openRoom({ rid: this.rid });
|
openRoom({ rid: this.rid });
|
||||||
this.setState({ joined: false });
|
this.setState({ joined: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage = (message) => {
|
sendMessage = (message) => {
|
||||||
|
const { setLastOpen } = this.props;
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
RocketChat.sendMessage(this.rid, message).then(() => {
|
RocketChat.sendMessage(this.rid, message).then(() => {
|
||||||
this.props.setLastOpen(null);
|
setLastOpen(null);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
joinRoom = async() => {
|
joinRoom = async() => {
|
||||||
|
const { rid } = this.props;
|
||||||
try {
|
try {
|
||||||
await RocketChat.joinRoom(this.props.rid);
|
await RocketChat.joinRoom(rid);
|
||||||
this.setState({ joined: true });
|
this.setState({
|
||||||
|
joined: true
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('joinRoom', e);
|
log('joinRoom', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
isOwner = () => this.state.room && this.state.room.roles && Array.from(Object.keys(this.state.room.roles), i => this.state.room.roles[i].value).includes('owner');
|
isOwner = () => {
|
||||||
|
const { room } = this.state;
|
||||||
|
return room && room.roles && Array.from(Object.keys(room.roles), i => room.roles[i].value).includes('owner');
|
||||||
|
}
|
||||||
|
|
||||||
isMuted = () => this.state.room && this.state.room.muted && Array.from(Object.keys(this.state.room.muted), i => this.state.room.muted[i].value).includes(this.props.user.username);
|
isMuted = () => {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { user } = this.props;
|
||||||
|
return room && room.muted && Array.from(Object.keys(room.muted), i => room.muted[i].value).includes(user.username);
|
||||||
|
}
|
||||||
|
|
||||||
isReadOnly = () => this.state.room.ro && this.isMuted() && !this.isOwner();
|
isReadOnly = () => {
|
||||||
|
const { room } = this.state;
|
||||||
|
return room.ro && this.isMuted() && !this.isOwner();
|
||||||
|
}
|
||||||
|
|
||||||
isBlocked = () => {
|
isBlocked = () => {
|
||||||
if (this.state.room) {
|
const { room } = this.state;
|
||||||
const { t, blocked, blocker } = this.state.room;
|
|
||||||
|
if (room) {
|
||||||
|
const { t, blocked, blocker } = room;
|
||||||
if (t === 'd' && (blocked || blocker)) {
|
if (t === 'd' && (blocked || blocker)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -231,24 +267,31 @@ export default class RoomView extends LoggedView {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = (item, previousItem) => (
|
renderItem = (item, previousItem) => {
|
||||||
<Message
|
const { room } = this.state;
|
||||||
key={item._id}
|
const { user } = this.props;
|
||||||
item={item}
|
|
||||||
status={item.status}
|
return (
|
||||||
reactions={JSON.parse(JSON.stringify(item.reactions))}
|
<Message
|
||||||
user={this.props.user}
|
key={item._id}
|
||||||
archived={this.state.room.archived}
|
item={item}
|
||||||
broadcast={this.state.room.broadcast}
|
status={item.status}
|
||||||
previousItem={previousItem}
|
reactions={JSON.parse(JSON.stringify(item.reactions))}
|
||||||
_updatedAt={item._updatedAt}
|
user={user}
|
||||||
onReactionPress={this.onReactionPress}
|
archived={room.archived}
|
||||||
onLongPress={this.onMessageLongPress}
|
broadcast={room.broadcast}
|
||||||
/>
|
previousItem={previousItem}
|
||||||
);
|
_updatedAt={item._updatedAt}
|
||||||
|
onReactionPress={this.onReactionPress}
|
||||||
|
onLongPress={this.onMessageLongPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderFooter = () => {
|
renderFooter = () => {
|
||||||
if (!this.state.joined) {
|
const { joined, room } = this.state;
|
||||||
|
|
||||||
|
if (!joined) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.joinRoomContainer} key='room-view-join'>
|
<View style={styles.joinRoomContainer} key='room-view-join'>
|
||||||
<Text style={styles.previewMode}>{I18n.t('You_are_in_preview_mode')}</Text>
|
<Text style={styles.previewMode}>{I18n.t('You_are_in_preview_mode')}</Text>
|
||||||
|
@ -263,7 +306,7 @@ export default class RoomView extends LoggedView {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.state.room.archived || this.isReadOnly()) {
|
if (room.archived || this.isReadOnly()) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.readOnly}>
|
<View style={styles.readOnly}>
|
||||||
<Text>{I18n.t('This_room_is_read_only')}</Text>
|
<Text>{I18n.t('This_room_is_read_only')}</Text>
|
||||||
|
@ -281,21 +324,23 @@ export default class RoomView extends LoggedView {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderHeader = () => {
|
renderHeader = () => {
|
||||||
if (!this.state.end) {
|
const { end } = this.state;
|
||||||
|
if (!end) {
|
||||||
return <ActivityIndicator style={[styles.loading, { transform: [{ scaleY: -1 }] }]} />;
|
return <ActivityIndicator style={[styles.loading, { transform: [{ scaleY: -1 }] }]} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList = () => {
|
renderList = () => {
|
||||||
if (!this.state.loaded) {
|
const { loaded, end } = this.state;
|
||||||
|
if (!loaded) {
|
||||||
return <ActivityIndicator style={styles.loading} />;
|
return <ActivityIndicator style={styles.loading} />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
<List
|
<List
|
||||||
key='room-view-messages'
|
key='room-view-messages'
|
||||||
end={this.state.end}
|
end={end}
|
||||||
room={this.rid}
|
room={this.rid}
|
||||||
renderFooter={this.renderHeader}
|
renderFooter={this.renderHeader}
|
||||||
onEndReached={this.onEndReached}
|
onEndReached={this.onEndReached}
|
||||||
|
@ -307,13 +352,17 @@ export default class RoomView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { user, showActions, showErrorActions } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} testID='room-view'>
|
<SafeAreaView style={styles.container} testID='room-view'>
|
||||||
{this.renderList()}
|
{this.renderList()}
|
||||||
{this.state.room._id && this.props.showActions ?
|
{room._id && showActions
|
||||||
<MessageActions room={this.state.room} user={this.props.user} /> :
|
? <MessageActions room={room} user={user} />
|
||||||
null}
|
: null
|
||||||
{this.props.showErrorActions ? <MessageErrorActions /> : null}
|
}
|
||||||
|
{showErrorActions ? <MessageErrorActions /> : null}
|
||||||
<ReactionPicker onEmojiSelected={this.onReactionPress} />
|
<ReactionPicker onEmojiSelected={this.onReactionPress} />
|
||||||
<UploadProgress rid={this.rid} />
|
<UploadProgress rid={this.rid} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View, TouchableOpacity, Image, StyleSheet } from 'react-native';
|
import {
|
||||||
|
Text, View, TouchableOpacity, Image, StyleSheet
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View, TouchableOpacity, Image, StyleSheet } from 'react-native';
|
import {
|
||||||
|
Text, View, TouchableOpacity, Image, StyleSheet
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { View, TextInput } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { setSearch } from '../../../actions/rooms';
|
import { setSearch as setSearchAction } from '../../../actions/rooms';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
|
|
||||||
@connect(null, dispatch => ({
|
@connect(null, dispatch => ({
|
||||||
setSearch: searchText => dispatch(setSearch(searchText))
|
setSearch: searchText => dispatch(setSearchAction(searchText))
|
||||||
}))
|
}))
|
||||||
export default class RoomsListSearchView extends React.Component {
|
export default class RoomsListSearchView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -20,7 +20,8 @@ export default class RoomsListSearchView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchChangeText(text) {
|
onSearchChangeText(text) {
|
||||||
this.props.setSearch(text.trim());
|
const { setSearch } = this.props;
|
||||||
|
setSearch(text.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, AsyncStorage } from 'react-native';
|
import {
|
||||||
|
View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, AsyncStorage
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
|
||||||
|
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
||||||
|
import { appStart as appStartAction } from '../../actions';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { toggleServerDropdown } from '../../actions/rooms';
|
|
||||||
import { selectServerRequest } from '../../actions/server';
|
|
||||||
import { appStart } from '../../actions';
|
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
@ -19,9 +21,9 @@ const ANIMATION_DURATION = 200;
|
||||||
closeServerDropdown: state.rooms.closeServerDropdown,
|
closeServerDropdown: state.rooms.closeServerDropdown,
|
||||||
server: state.server.server
|
server: state.server.server
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
toggleServerDropdown: () => dispatch(toggleServerDropdown()),
|
toggleServerDropdown: () => dispatch(toggleServerDropdownAction()),
|
||||||
selectServerRequest: server => dispatch(selectServerRequest(server)),
|
selectServerRequest: server => dispatch(selectServerRequestAction(server)),
|
||||||
appStart: () => dispatch(appStart('outside'))
|
appStart: () => dispatch(appStartAction('outside'))
|
||||||
}))
|
}))
|
||||||
export default class ServerDropdown extends Component {
|
export default class ServerDropdown extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -56,7 +58,8 @@ export default class ServerDropdown extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (prevProps.closeServerDropdown !== this.props.closeServerDropdown) {
|
const { closeServerDropdown } = this.props;
|
||||||
|
if (prevProps.closeServerDropdown !== closeServerDropdown) {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +70,7 @@ export default class ServerDropdown extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
|
const { toggleServerDropdown } = this.props;
|
||||||
Animated.timing(
|
Animated.timing(
|
||||||
this.animatedValue,
|
this.animatedValue,
|
||||||
{
|
{
|
||||||
|
@ -75,16 +79,18 @@ export default class ServerDropdown extends Component {
|
||||||
easing: Easing.ease,
|
easing: Easing.ease,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
}
|
}
|
||||||
).start(() => this.props.toggleServerDropdown());
|
).start(() => toggleServerDropdown());
|
||||||
}
|
}
|
||||||
|
|
||||||
addServer = () => {
|
addServer = () => {
|
||||||
|
const { navigator, server } = this.props;
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.props.navigator.showModal({
|
navigator.showModal({
|
||||||
screen: 'OnboardingView',
|
screen: 'OnboardingView',
|
||||||
passProps: {
|
passProps: {
|
||||||
previousServer: this.props.server
|
previousServer: server
|
||||||
},
|
},
|
||||||
navigatorStyle: {
|
navigatorStyle: {
|
||||||
navBarHidden: true,
|
navBarHidden: true,
|
||||||
|
@ -95,14 +101,18 @@ export default class ServerDropdown extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
select = async(server) => {
|
select = async(server) => {
|
||||||
|
const {
|
||||||
|
server: serverProp, selectServerRequest, appStart, navigator
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
if (this.props.server !== server) {
|
if (serverProp !== server) {
|
||||||
this.props.selectServerRequest(server);
|
selectServerRequest(server);
|
||||||
const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
this.props.appStart();
|
appStart();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.props.navigator.push({
|
navigator.push({
|
||||||
screen: 'NewServerView',
|
screen: 'NewServerView',
|
||||||
backButtonTitle: '',
|
backButtonTitle: '',
|
||||||
passProps: {
|
passProps: {
|
||||||
|
@ -119,32 +129,41 @@ export default class ServerDropdown extends Component {
|
||||||
|
|
||||||
renderSeparator = () => <View style={styles.serverSeparator} />;
|
renderSeparator = () => <View style={styles.serverSeparator} />;
|
||||||
|
|
||||||
renderServer = ({ item }) => (
|
renderServer = ({ item }) => {
|
||||||
<Touch onPress={() => this.select(item.id)} style={styles.serverItem} testID={`rooms-list-header-server-${ item.id }`}>
|
const { server } = this.props;
|
||||||
<View style={styles.serverItemContainer}>
|
|
||||||
{item.iconURL ?
|
return (
|
||||||
<Image
|
<Touch onPress={() => this.select(item.id)} style={styles.serverItem} testID={`rooms-list-header-server-${ item.id }`}>
|
||||||
source={{ uri: item.iconURL }}
|
<View style={styles.serverItemContainer}>
|
||||||
defaultSource={{ uri: 'logo' }}
|
{item.iconURL
|
||||||
style={styles.serverIcon}
|
? (
|
||||||
/> :
|
<Image
|
||||||
<Image
|
source={{ uri: item.iconURL }}
|
||||||
source={{ uri: 'logo' }}
|
defaultSource={{ uri: 'logo' }}
|
||||||
style={styles.serverIcon}
|
style={styles.serverIcon}
|
||||||
/>
|
/>
|
||||||
}
|
)
|
||||||
<View style={styles.serverTextContainer}>
|
: (
|
||||||
<Text style={styles.serverName}>{item.name || item.id}</Text>
|
<Image
|
||||||
<Text style={styles.serverUrl}>{item.id}</Text>
|
source={{ uri: 'logo' }}
|
||||||
|
style={styles.serverIcon}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<View style={styles.serverTextContainer}>
|
||||||
|
<Text style={styles.serverName}>{item.name || item.id}</Text>
|
||||||
|
<Text style={styles.serverUrl}>{item.id}</Text>
|
||||||
|
</View>
|
||||||
|
{item.id === server ? <Image style={styles.checkIcon} source={{ uri: 'check' }} /> : null}
|
||||||
</View>
|
</View>
|
||||||
{item.id === this.props.server ? <Image style={styles.checkIcon} source={{ uri: 'check' }} /> : null}
|
</Touch>
|
||||||
</View>
|
);
|
||||||
</Touch>
|
}
|
||||||
)
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { servers } = this.state;
|
||||||
const maxRows = 4;
|
const maxRows = 4;
|
||||||
const initialTop = 41 + (Math.min(this.state.servers.length, maxRows) * ROW_HEIGHT);
|
const initialTop = 41 + (Math.min(servers.length, maxRows) * ROW_HEIGHT);
|
||||||
const translateY = this.animatedValue.interpolate({
|
const translateY = this.animatedValue.interpolate({
|
||||||
inputRange: [0, 1],
|
inputRange: [0, 1],
|
||||||
outputRange: [-initialTop, 0]
|
outputRange: [-initialTop, 0]
|
||||||
|
@ -171,7 +190,7 @@ export default class ServerDropdown extends Component {
|
||||||
</View>
|
</View>
|
||||||
<FlatList
|
<FlatList
|
||||||
style={{ maxHeight: maxRows * ROW_HEIGHT }}
|
style={{ maxHeight: maxRows * ROW_HEIGHT }}
|
||||||
data={this.state.servers}
|
data={servers}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
renderItem={this.renderServer}
|
renderItem={this.renderServer}
|
||||||
ItemSeparatorComponent={this.renderSeparator}
|
ItemSeparatorComponent={this.renderSeparator}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { View, Text, Animated, Easing, Image, TouchableWithoutFeedback } from 'react-native';
|
import {
|
||||||
|
View, Text, Animated, Easing, Image, TouchableWithoutFeedback
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
@ -46,14 +48,17 @@ export default class Sort extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (prevProps.closeSortDropdown !== this.props.closeSortDropdown) {
|
const { closeSortDropdown } = this.props;
|
||||||
|
if (prevProps.closeSortDropdown !== closeSortDropdown) {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSortPreference = async(param) => {
|
setSortPreference = async(param) => {
|
||||||
|
const { setSortPreference } = this.props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.props.setSortPreference(param);
|
setSortPreference(param);
|
||||||
RocketChat.saveSortPreference(param);
|
RocketChat.saveSortPreference(param);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('RoomsListView.setSortPreference', e);
|
log('RoomsListView.setSortPreference', e);
|
||||||
|
@ -69,18 +74,22 @@ export default class Sort extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleGroupByType = () => {
|
toggleGroupByType = () => {
|
||||||
this.setSortPreference({ groupByType: !this.props.groupByType });
|
const { groupByType } = this.props;
|
||||||
|
this.setSortPreference({ groupByType: !groupByType });
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleGroupByFavorites = () => {
|
toggleGroupByFavorites = () => {
|
||||||
this.setSortPreference({ showFavorites: !this.props.showFavorites });
|
const { showFavorites } = this.props;
|
||||||
|
this.setSortPreference({ showFavorites: !showFavorites });
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleUnread = () => {
|
toggleUnread = () => {
|
||||||
this.setSortPreference({ showUnread: !this.props.showUnread });
|
const { showUnread } = this.props;
|
||||||
|
this.setSortPreference({ showUnread: !showUnread });
|
||||||
}
|
}
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
|
const { close } = this.props;
|
||||||
Animated.timing(
|
Animated.timing(
|
||||||
this.animatedValue,
|
this.animatedValue,
|
||||||
{
|
{
|
||||||
|
@ -89,7 +98,7 @@ export default class Sort extends Component {
|
||||||
easing: Easing.ease,
|
easing: Easing.ease,
|
||||||
useNativeDriver: true
|
useNativeDriver: true
|
||||||
},
|
},
|
||||||
).start(() => this.props.close());
|
).start(() => close());
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -104,6 +113,7 @@ export default class Sort extends Component {
|
||||||
const {
|
const {
|
||||||
sortBy, groupByType, showFavorites, showUnread
|
sortBy, groupByType, showFavorites, showUnread
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
[
|
[
|
||||||
<TouchableWithoutFeedback key='sort-backdrop' onPress={this.close}>
|
<TouchableWithoutFeedback key='sort-backdrop' onPress={this.close}>
|
||||||
|
@ -156,7 +166,7 @@ export default class Sort extends Component {
|
||||||
style={[styles.dropdownContainerHeader, styles.sortToggleContainerClose]}
|
style={[styles.dropdownContainerHeader, styles.sortToggleContainerClose]}
|
||||||
>
|
>
|
||||||
<View style={styles.sortItemContainer}>
|
<View style={styles.sortItemContainer}>
|
||||||
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(this.props.sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||||
<Image style={styles.sortIcon} source={{ uri: 'group_type' }} />
|
<Image style={styles.sortIcon} source={{ uri: 'group_type' }} />
|
||||||
</View>
|
</View>
|
||||||
</Touch>
|
</Touch>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Platform, View, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard } from 'react-native';
|
import {
|
||||||
|
Platform, View, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
|
@ -15,7 +17,7 @@ import I18n from '../../i18n';
|
||||||
import SortDropdown from './SortDropdown';
|
import SortDropdown from './SortDropdown';
|
||||||
import ServerDropdown from './ServerDropdown';
|
import ServerDropdown from './ServerDropdown';
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import { toggleSortDropdown } from '../../actions/rooms';
|
import { toggleSortDropdown as toggleSortDropdownAction } from '../../actions/rooms';
|
||||||
|
|
||||||
const ROW_HEIGHT = 70;
|
const ROW_HEIGHT = 70;
|
||||||
const SCROLL_OFFSET = 56;
|
const SCROLL_OFFSET = 56;
|
||||||
|
@ -54,7 +56,7 @@ if (Platform.OS === 'android') {
|
||||||
showUnread: state.sortPreferences.showUnread,
|
showUnread: state.sortPreferences.showUnread,
|
||||||
useRealName: state.settings.UI_Use_Real_Name
|
useRealName: state.settings.UI_Use_Real_Name
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
toggleSortDropdown: () => dispatch(toggleSortDropdown())
|
toggleSortDropdown: () => dispatch(toggleSortDropdownAction())
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class RoomsListView extends LoggedView {
|
export default class RoomsListView extends LoggedView {
|
||||||
|
@ -114,13 +116,15 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.server && this.props.loadingServer !== nextProps.loadingServer) {
|
const { loadingServer, searchText } = this.props;
|
||||||
|
|
||||||
|
if (nextProps.server && loadingServer !== nextProps.loadingServer) {
|
||||||
if (nextProps.loadingServer) {
|
if (nextProps.loadingServer) {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
} else {
|
} else {
|
||||||
this.getSubscriptions();
|
this.getSubscriptions();
|
||||||
}
|
}
|
||||||
} else if (this.props.searchText !== nextProps.searchText) {
|
} else if (searchText !== nextProps.searchText) {
|
||||||
this.search(nextProps.searchText);
|
this.search(nextProps.searchText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,11 +134,15 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
const {
|
||||||
|
sortBy, groupByType, showFavorites, showUnread
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (!(
|
if (!(
|
||||||
(prevProps.sortBy === this.props.sortBy) &&
|
(prevProps.sortBy === sortBy)
|
||||||
(prevProps.groupByType === this.props.groupByType) &&
|
&& (prevProps.groupByType === groupByType)
|
||||||
(prevProps.showFavorites === this.props.showFavorites) &&
|
&& (prevProps.showFavorites === showFavorites)
|
||||||
(prevProps.showUnread === this.props.showUnread)
|
&& (prevProps.showUnread === showUnread)
|
||||||
)) {
|
)) {
|
||||||
this.getSubscriptions();
|
this.getSubscriptions();
|
||||||
}
|
}
|
||||||
|
@ -158,7 +166,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
const { navigator } = this.props;
|
const { navigator } = this.props;
|
||||||
if (event.type === 'NavBarButtonPress') {
|
if (event.type === 'NavBarButtonPress') {
|
||||||
if (event.id === 'newMessage') {
|
if (event.id === 'newMessage') {
|
||||||
this.props.navigator.showModal({
|
navigator.showModal({
|
||||||
screen: 'NewMessageView',
|
screen: 'NewMessageView',
|
||||||
title: I18n.t('New_Message'),
|
title: I18n.t('New_Message'),
|
||||||
passProps: {
|
passProps: {
|
||||||
|
@ -183,8 +191,12 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubscriptions = () => {
|
getSubscriptions = () => {
|
||||||
if (this.props.server && this.hasActiveDB()) {
|
const {
|
||||||
if (this.props.sortBy === 'alphabetical') {
|
server, sortBy, showUnread, showFavorites, groupByType
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (server && this.hasActiveDB()) {
|
||||||
|
if (sortBy === 'alphabetical') {
|
||||||
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('name', false);
|
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('name', false);
|
||||||
} else {
|
} else {
|
||||||
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('roomUpdatedAt', true);
|
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('roomUpdatedAt', true);
|
||||||
|
@ -199,7 +211,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
let livechat = [];
|
let livechat = [];
|
||||||
|
|
||||||
// unread
|
// unread
|
||||||
if (this.props.showUnread) {
|
if (showUnread) {
|
||||||
this.unread = this.data.filtered('archived != true && open == true').sorted('name', false).filtered('(unread > 0 || alert == true)');
|
this.unread = this.data.filtered('archived != true && open == true').sorted('name', false).filtered('(unread > 0 || alert == true)');
|
||||||
unread = this.unread.slice();
|
unread = this.unread.slice();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -209,7 +221,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
this.removeListener(unread);
|
this.removeListener(unread);
|
||||||
}
|
}
|
||||||
// favorites
|
// favorites
|
||||||
if (this.props.showFavorites) {
|
if (showFavorites) {
|
||||||
this.favorites = this.data.filtered('f == true');
|
this.favorites = this.data.filtered('f == true');
|
||||||
favorites = this.favorites.slice();
|
favorites = this.favorites.slice();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -219,7 +231,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
this.removeListener(favorites);
|
this.removeListener(favorites);
|
||||||
}
|
}
|
||||||
// type
|
// type
|
||||||
if (this.props.groupByType) {
|
if (groupByType) {
|
||||||
// channels
|
// channels
|
||||||
this.channels = this.data.filtered('t == $0', 'c');
|
this.channels = this.data.filtered('t == $0', 'c');
|
||||||
channels = this.channels.slice();
|
channels = this.channels.slice();
|
||||||
|
@ -241,7 +253,7 @@ export default class RoomsListView extends LoggedView {
|
||||||
this.removeListener(this.chats);
|
this.removeListener(this.chats);
|
||||||
} else {
|
} else {
|
||||||
// chats
|
// chats
|
||||||
if (this.props.showUnread) {
|
if (showUnread) {
|
||||||
this.chats = this.data.filtered('(unread == 0 && alert == false)');
|
this.chats = this.data.filtered('(unread == 0 && alert == false)');
|
||||||
} else {
|
} else {
|
||||||
this.chats = this.data;
|
this.chats = this.data;
|
||||||
|
@ -327,7 +339,8 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
goRoom = (rid, name) => {
|
goRoom = (rid, name) => {
|
||||||
this.props.navigator.push({
|
const { navigator } = this.props;
|
||||||
|
navigator.push({
|
||||||
screen: 'RoomView',
|
screen: 'RoomView',
|
||||||
title: name,
|
title: name,
|
||||||
backButtonTitle: '',
|
backButtonTitle: '',
|
||||||
|
@ -358,34 +371,41 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSort = () => {
|
toggleSort = () => {
|
||||||
|
const { toggleSortDropdown } = this.props;
|
||||||
|
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
this.scroll.scrollTo({ x: 0, y: SCROLL_OFFSET, animated: true });
|
this.scroll.scrollTo({ x: 0, y: SCROLL_OFFSET, animated: true });
|
||||||
} else {
|
} else {
|
||||||
this.scroll.scrollTo({ x: 0, y: 0, animated: true });
|
this.scroll.scrollTo({ x: 0, y: 0, animated: true });
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.props.toggleSortDropdown();
|
toggleSortDropdown();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeader = () => {
|
renderHeader = () => {
|
||||||
if (this.state.search.length > 0) {
|
const { search } = this.state;
|
||||||
|
if (search.length > 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.renderSort();
|
return this.renderSort();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSort = () => (
|
renderSort = () => {
|
||||||
<Touch
|
const { sortBy } = this.props;
|
||||||
onPress={this.toggleSort}
|
|
||||||
style={styles.dropdownContainerHeader}
|
return (
|
||||||
>
|
<Touch
|
||||||
<View style={styles.sortItemContainer}>
|
onPress={this.toggleSort}
|
||||||
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(this.props.sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
style={styles.dropdownContainerHeader}
|
||||||
<Image style={styles.sortIcon} source={{ uri: 'group_type' }} />
|
>
|
||||||
</View>
|
<View style={styles.sortItemContainer}>
|
||||||
</Touch>
|
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||||
)
|
<Image style={styles.sortIcon} source={{ uri: 'group_type' }} />
|
||||||
|
</View>
|
||||||
|
</Touch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderSearchBar = () => {
|
renderSearchBar = () => {
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
|
@ -394,36 +414,41 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }) => {
|
||||||
const id = item.rid.replace(this.props.userId, '').trim();
|
const { useRealName, userId, baseUrl } = this.props;
|
||||||
const { useRealName } = this.props;
|
const id = item.rid.replace(userId, '').trim();
|
||||||
return (<RoomItem
|
|
||||||
alert={item.alert}
|
return (
|
||||||
unread={item.unread}
|
<RoomItem
|
||||||
userMentions={item.userMentions}
|
alert={item.alert}
|
||||||
favorite={item.f}
|
unread={item.unread}
|
||||||
lastMessage={item.lastMessage}
|
userMentions={item.userMentions}
|
||||||
name={(useRealName && item.fname) || item.name}
|
favorite={item.f}
|
||||||
_updatedAt={item.roomUpdatedAt}
|
lastMessage={item.lastMessage}
|
||||||
key={item._id}
|
name={(useRealName && item.fname) || item.name}
|
||||||
id={id}
|
_updatedAt={item.roomUpdatedAt}
|
||||||
type={item.t}
|
key={item._id}
|
||||||
baseUrl={this.props.baseUrl}
|
id={id}
|
||||||
onPress={() => this._onPressItem(item)}
|
type={item.t}
|
||||||
testID={`rooms-list-view-item-${ item.name }`}
|
baseUrl={baseUrl}
|
||||||
height={ROW_HEIGHT}
|
onPress={() => this._onPressItem(item)}
|
||||||
/>);
|
testID={`rooms-list-view-item-${ item.name }`}
|
||||||
|
height={ROW_HEIGHT}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSeparator = () => <View style={styles.separator} />;
|
renderSeparator = () => <View style={styles.separator} />;
|
||||||
|
|
||||||
renderSection = (data, header) => {
|
renderSection = (data, header) => {
|
||||||
if (header === 'Unread' && !this.props.showUnread) {
|
const { showUnread, showFavorites, groupByType } = this.props;
|
||||||
|
|
||||||
|
if (header === 'Unread' && !showUnread) {
|
||||||
return null;
|
return null;
|
||||||
} else if (header === 'Favorites' && !this.props.showFavorites) {
|
} else if (header === 'Favorites' && !showFavorites) {
|
||||||
return null;
|
return null;
|
||||||
} else if (['Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !this.props.groupByType) {
|
} else if (['Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !groupByType) {
|
||||||
return null;
|
return null;
|
||||||
} else if (header === 'Chats' && this.props.groupByType) {
|
} else if (header === 'Chats' && groupByType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
|
@ -486,7 +511,9 @@ export default class RoomsListView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderScroll = () => {
|
renderScroll = () => {
|
||||||
if (this.state.loading) {
|
const { loading } = this.state;
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
return <ActivityIndicator style={styles.loading} />;
|
return <ActivityIndicator style={styles.loading} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,22 +533,25 @@ export default class RoomsListView extends LoggedView {
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
const {
|
const {
|
||||||
sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown
|
sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown, navigator
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} testID='rooms-list-view'>
|
<SafeAreaView style={styles.container} testID='rooms-list-view'>
|
||||||
{this.renderScroll()}
|
{this.renderScroll()}
|
||||||
{showSortDropdown ?
|
{showSortDropdown
|
||||||
<SortDropdown
|
? (
|
||||||
close={this.toggleSort}
|
<SortDropdown
|
||||||
sortBy={sortBy}
|
close={this.toggleSort}
|
||||||
groupByType={groupByType}
|
sortBy={sortBy}
|
||||||
showFavorites={showFavorites}
|
groupByType={groupByType}
|
||||||
showUnread={showUnread}
|
showFavorites={showFavorites}
|
||||||
/> :
|
showUnread={showUnread}
|
||||||
null}
|
/>
|
||||||
{showServerDropdown ? <ServerDropdown navigator={this.props.navigator} /> : null}
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
{showServerDropdown ? <ServerDropdown navigator={navigator} /> : null}
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,22 +52,26 @@ export default class SearchMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeSearch = debounce((search) => {
|
onChangeSearch = debounce((search) => {
|
||||||
|
const { searching } = this.state;
|
||||||
|
|
||||||
this.searchText = search;
|
this.searchText = search;
|
||||||
this.limit = 0;
|
this.limit = 0;
|
||||||
if (!this.state.searching) {
|
if (!searching) {
|
||||||
this.setState({ searching: true });
|
this.setState({ searching: true });
|
||||||
}
|
}
|
||||||
this.search();
|
this.search();
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
search = async() => {
|
search = async() => {
|
||||||
|
const { rid } = this.props;
|
||||||
|
|
||||||
if (this._cancel) {
|
if (this._cancel) {
|
||||||
this._cancel('cancel');
|
this._cancel('cancel');
|
||||||
}
|
}
|
||||||
const cancel = new Promise((r, reject) => this._cancel = reject);
|
const cancel = new Promise((r, reject) => this._cancel = reject);
|
||||||
let messages = [];
|
let messages = [];
|
||||||
try {
|
try {
|
||||||
const result = await Promise.race([RocketChat.messageSearch(this.searchText, this.props.rid, this.limit), cancel]);
|
const result = await Promise.race([RocketChat.messageSearch(this.searchText, rid, this.limit), cancel]);
|
||||||
messages = result.message.docs.map(message => buildMessage(message));
|
messages = result.message.docs.map(message => buildMessage(message));
|
||||||
this.setState({ messages, searching: false, loadingMore: false });
|
this.setState({ messages, searching: false, loadingMore: false });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -91,27 +95,30 @@ export default class SearchMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = ({ item }) => (
|
renderItem = ({ item }) => {
|
||||||
<Message
|
const { user } = this.props;
|
||||||
item={item}
|
return (
|
||||||
style={styles.message}
|
<Message
|
||||||
reactions={item.reactions}
|
item={item}
|
||||||
user={this.props.user}
|
style={styles.message}
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
reactions={item.reactions}
|
||||||
onReactionPress={async(emoji) => {
|
user={user}
|
||||||
try {
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
await RocketChat.setReaction(emoji, item._id);
|
onReactionPress={async(emoji) => {
|
||||||
this.search();
|
try {
|
||||||
this.forceUpdate();
|
await RocketChat.setReaction(emoji, item._id);
|
||||||
} catch (e) {
|
this.search();
|
||||||
log('SearchMessagesView.onReactionPress', e);
|
this.forceUpdate();
|
||||||
}
|
} catch (e) {
|
||||||
}}
|
log('SearchMessagesView.onReactionPress', e);
|
||||||
/>
|
}
|
||||||
);
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { searching, loadingMore } = this.state;
|
const { searching, loadingMore, messages } = this.state;
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} testID='search-messages-view'>
|
<SafeAreaView style={styles.container} testID='search-messages-view'>
|
||||||
<View style={styles.searchContainer}>
|
<View style={styles.searchContainer}>
|
||||||
|
@ -126,7 +133,7 @@ export default class SearchMessagesView extends LoggedView {
|
||||||
<View style={styles.divider} />
|
<View style={styles.divider} />
|
||||||
</View>
|
</View>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={this.state.messages}
|
data={messages}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
keyExtractor={item => item._id}
|
keyExtractor={item => item._id}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, StyleSheet, SafeAreaView, FlatList, LayoutAnimation, Platform } from 'react-native';
|
import {
|
||||||
|
View, StyleSheet, SafeAreaView, FlatList, LayoutAnimation, Platform
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { addUser, removeUser, reset, setLoading } from '../actions/selectedUsers';
|
import {
|
||||||
|
addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction
|
||||||
|
} from '../actions/selectedUsers';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import UserItem from '../presentation/UserItem';
|
import UserItem from '../presentation/UserItem';
|
||||||
|
@ -33,10 +37,10 @@ const styles = StyleSheet.create({
|
||||||
users: state.selectedUsers.users,
|
users: state.selectedUsers.users,
|
||||||
loading: state.selectedUsers.loading
|
loading: state.selectedUsers.loading
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
addUser: user => dispatch(addUser(user)),
|
addUser: user => dispatch(addUserAction(user)),
|
||||||
removeUser: user => dispatch(removeUser(user)),
|
removeUser: user => dispatch(removeUserAction(user)),
|
||||||
reset: () => dispatch(reset()),
|
reset: () => dispatch(resetAction()),
|
||||||
setLoadingInvite: loading => dispatch(setLoading(loading))
|
setLoadingInvite: loading => dispatch(setLoadingAction(loading))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class SelectedUsersView extends LoggedView {
|
export default class SelectedUsersView extends LoggedView {
|
||||||
|
@ -64,19 +68,22 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.navigator.setDrawerEnabled({
|
const { navigator } = this.props;
|
||||||
|
navigator.setDrawerEnabled({
|
||||||
side: 'left',
|
side: 'left',
|
||||||
enabled: false
|
enabled: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidUpdate(prevProps) {
|
async componentDidUpdate(prevProps) {
|
||||||
const isVisible = await this.props.navigator.screenIsCurrentlyVisible();
|
const { navigator, users } = this.props;
|
||||||
|
const isVisible = await navigator.screenIsCurrentlyVisible();
|
||||||
|
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (prevProps.users.length !== this.props.users.length) {
|
if (prevProps.users.length !== users.length) {
|
||||||
const { length } = this.props.users;
|
const { length } = users;
|
||||||
const rightButtons = [];
|
const rightButtons = [];
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
rightButtons.push({
|
rightButtons.push({
|
||||||
|
@ -85,14 +92,15 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
testID: 'selected-users-view-submit'
|
testID: 'selected-users-view-submit'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.props.navigator.setButtons({ rightButtons });
|
navigator.setButtons({ rightButtons });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
const { reset } = this.props;
|
||||||
this.updateState.stop();
|
this.updateState.stop();
|
||||||
this.data.removeAllListeners();
|
this.data.removeAllListeners();
|
||||||
this.props.reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onNavigatorEvent(event) {
|
async onNavigatorEvent(event) {
|
||||||
|
@ -100,15 +108,16 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
if (event.id === 'create') {
|
if (event.id === 'create') {
|
||||||
const { nextAction, setLoadingInvite, navigator } = this.props;
|
const { nextAction, setLoadingInvite, navigator } = this.props;
|
||||||
if (nextAction === 'CREATE_CHANNEL') {
|
if (nextAction === 'CREATE_CHANNEL') {
|
||||||
this.props.navigator.push({
|
navigator.push({
|
||||||
screen: 'CreateChannelView',
|
screen: 'CreateChannelView',
|
||||||
title: I18n.t('Create_Channel'),
|
title: I18n.t('Create_Channel'),
|
||||||
backButtonTitle: ''
|
backButtonTitle: ''
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const { rid } = this.props;
|
||||||
try {
|
try {
|
||||||
setLoadingInvite(true);
|
setLoadingInvite(true);
|
||||||
await RocketChat.addUsersToRoom(this.props.rid);
|
await RocketChat.addUsersToRoom(rid);
|
||||||
navigator.pop();
|
navigator.pop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('RoomActions Add User', e);
|
log('RoomActions Add User', e);
|
||||||
|
@ -124,6 +133,7 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
this.search(text);
|
this.search(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
updateState = debounce(() => {
|
updateState = debounce(() => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
@ -135,14 +145,19 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isChecked = username => this.props.users.findIndex(el => el.name === username) !== -1;
|
isChecked = (username) => {
|
||||||
|
const { users } = this.props;
|
||||||
|
return users.findIndex(el => el.name === username) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
toggleUser = (user) => {
|
toggleUser = (user) => {
|
||||||
|
const { addUser, removeUser } = this.props;
|
||||||
|
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
if (!this.isChecked(user.name)) {
|
if (!this.isChecked(user.name)) {
|
||||||
this.props.addUser(user);
|
addUser(user);
|
||||||
} else {
|
} else {
|
||||||
this.props.removeUser(user);
|
removeUser(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,12 +179,14 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
)
|
)
|
||||||
|
|
||||||
renderSelected = () => {
|
renderSelected = () => {
|
||||||
if (this.props.users.length === 0) {
|
const { users } = this.props;
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={this.props.users}
|
data={users}
|
||||||
keyExtractor={item => item._id}
|
keyExtractor={item => item._id}
|
||||||
style={[styles.list, sharedStyles.separatorTop]}
|
style={[styles.list, sharedStyles.separatorTop]}
|
||||||
contentContainerStyle={{ marginVertical: 5 }}
|
contentContainerStyle={{ marginVertical: 5 }}
|
||||||
|
@ -181,30 +198,36 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSelectedItem = ({ item }) => (
|
renderSelectedItem = ({ item }) => {
|
||||||
<UserItem
|
const { baseUrl } = this.props;
|
||||||
name={item.fname}
|
return (
|
||||||
username={item.name}
|
<UserItem
|
||||||
onPress={() => this._onPressSelectedItem(item)}
|
name={item.fname}
|
||||||
testID={`selected-user-${ item.name }`}
|
username={item.name}
|
||||||
baseUrl={this.props.baseUrl}
|
onPress={() => this._onPressSelectedItem(item)}
|
||||||
style={{ paddingRight: 15 }}
|
testID={`selected-user-${ item.name }`}
|
||||||
/>
|
baseUrl={baseUrl}
|
||||||
)
|
style={{ paddingRight: 15 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />
|
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />
|
||||||
|
|
||||||
renderItem = ({ item, index }) => {
|
renderItem = ({ item, index }) => {
|
||||||
|
const { search } = this.state;
|
||||||
|
const { baseUrl } = this.props;
|
||||||
|
|
||||||
const name = item.search ? item.name : item.fname;
|
const name = item.search ? item.name : item.fname;
|
||||||
const username = item.search ? item.username : item.name;
|
const username = item.search ? item.username : item.name;
|
||||||
let style = {};
|
let style = {};
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
style = { ...sharedStyles.separatorTop };
|
style = { ...sharedStyles.separatorTop };
|
||||||
}
|
}
|
||||||
if (this.state.search.length > 0 && index === this.state.search.length - 1) {
|
if (search.length > 0 && index === search.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
if (this.state.search.length === 0 && index === this.data.length - 1) {
|
if (search.length === 0 && index === this.data.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -214,29 +237,35 @@ export default class SelectedUsersView extends LoggedView {
|
||||||
onPress={() => this._onPressItem(item._id, item)}
|
onPress={() => this._onPressItem(item._id, item)}
|
||||||
testID={`select-users-view-item-${ item.name }`}
|
testID={`select-users-view-item-${ item.name }`}
|
||||||
icon={this.isChecked(username) ? 'check' : null}
|
icon={this.isChecked(username) ? 'check' : null}
|
||||||
baseUrl={this.props.baseUrl}
|
baseUrl={baseUrl}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList = () => (
|
renderList = () => {
|
||||||
<FlatList
|
const { search } = this.state;
|
||||||
data={this.state.search.length > 0 ? this.state.search : this.data}
|
return (
|
||||||
extraData={this.props}
|
<FlatList
|
||||||
keyExtractor={item => item._id}
|
data={search.length > 0 ? search : this.data}
|
||||||
renderItem={this.renderItem}
|
extraData={this.props}
|
||||||
ItemSeparatorComponent={this.renderSeparator}
|
keyExtractor={item => item._id}
|
||||||
ListHeaderComponent={this.renderHeader}
|
renderItem={this.renderItem}
|
||||||
enableEmptySections
|
ItemSeparatorComponent={this.renderSeparator}
|
||||||
keyboardShouldPersistTaps='always'
|
ListHeaderComponent={this.renderHeader}
|
||||||
/>
|
enableEmptySections
|
||||||
)
|
keyboardShouldPersistTaps='always'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render = () => (
|
render = () => {
|
||||||
<SafeAreaView style={styles.safeAreaView} testID='select-users-view'>
|
const { loading } = this.props;
|
||||||
{this.renderList()}
|
return (
|
||||||
<Loading visible={this.props.loading} />
|
<SafeAreaView style={styles.safeAreaView} testID='select-users-view'>
|
||||||
</SafeAreaView>
|
{this.renderList()}
|
||||||
)
|
<Loading visible={loading} />
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, ScrollView, SafeAreaView, Dimensions } from 'react-native';
|
import {
|
||||||
|
View, ScrollView, SafeAreaView, Dimensions
|
||||||
|
} from 'react-native';
|
||||||
import RNPickerSelect from 'react-native-picker-select';
|
import RNPickerSelect from 'react-native-picker-select';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
@ -15,12 +17,12 @@ import Button from '../../containers/Button';
|
||||||
import Loading from '../../containers/Loading';
|
import Loading from '../../containers/Loading';
|
||||||
import { showErrorAlert, showToast } from '../../utils/info';
|
import { showErrorAlert, showToast } from '../../utils/info';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { setUser } from '../../actions/login';
|
import { setUser as setUserAction } from '../../actions/login';
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
userLanguage: state.login.user && state.login.user.language
|
userLanguage: state.login.user && state.login.user.language
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
setUser: params => dispatch(setUser(params))
|
setUser: params => dispatch(setUserAction(params))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class SettingsView extends LoggedView {
|
export default class SettingsView extends LoggedView {
|
||||||
|
@ -51,7 +53,8 @@ export default class SettingsView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props.navigator.setButtons({
|
const { navigator } = this.props;
|
||||||
|
navigator.setButtons({
|
||||||
leftButtons: [{
|
leftButtons: [{
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
icon: { uri: 'settings', scale: Dimensions.get('window').scale }
|
icon: { uri: 'settings', scale: Dimensions.get('window').scale }
|
||||||
|
@ -60,16 +63,18 @@ export default class SettingsView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.navigator.setDrawerEnabled({
|
const { navigator } = this.props;
|
||||||
|
navigator.setDrawerEnabled({
|
||||||
side: 'left',
|
side: 'left',
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onNavigatorEvent(event) {
|
onNavigatorEvent(event) {
|
||||||
|
const { navigator } = this.props;
|
||||||
if (event.type === 'NavBarButtonPress') {
|
if (event.type === 'NavBarButtonPress') {
|
||||||
if (event.id === 'settings') {
|
if (event.id === 'settings') {
|
||||||
this.props.navigator.toggleDrawer({
|
navigator.toggleDrawer({
|
||||||
side: 'left'
|
side: 'left'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -86,17 +91,16 @@ export default class SettingsView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
formIsChanged = () => {
|
formIsChanged = () => {
|
||||||
|
const { userLanguage } = this.props;
|
||||||
const { language } = this.state;
|
const { language } = this.state;
|
||||||
return !(this.props.userLanguage === language);
|
return !(userLanguage === language);
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = async() => {
|
submit = async() => {
|
||||||
this.setState({ saving: true });
|
this.setState({ saving: true });
|
||||||
|
|
||||||
const {
|
const { language } = this.state;
|
||||||
language
|
const { userLanguage, setUser, navigator } = this.props;
|
||||||
} = this.state;
|
|
||||||
const { userLanguage } = this.props;
|
|
||||||
|
|
||||||
if (!this.formIsChanged()) {
|
if (!this.formIsChanged()) {
|
||||||
return;
|
return;
|
||||||
|
@ -111,14 +115,14 @@ export default class SettingsView extends LoggedView {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await RocketChat.saveUserPreferences(params);
|
await RocketChat.saveUserPreferences(params);
|
||||||
this.props.setUser({ language: params.language });
|
setUser({ language: params.language });
|
||||||
|
|
||||||
this.setState({ saving: false });
|
this.setState({ saving: false });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showToast(I18n.t('Preferences_saved'));
|
showToast(I18n.t('Preferences_saved'));
|
||||||
|
|
||||||
if (params.language) {
|
if (params.language) {
|
||||||
this.props.navigator.setTitle({ title: I18n.t('Settings') });
|
navigator.setTitle({ title: I18n.t('Settings') });
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -134,7 +138,9 @@ export default class SettingsView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { language, languages, placeholder } = this.state;
|
const {
|
||||||
|
language, languages, placeholder, saving
|
||||||
|
} = this.state;
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
contentContainerStyle={sharedStyles.container}
|
contentContainerStyle={sharedStyles.container}
|
||||||
|
@ -171,7 +177,7 @@ export default class SettingsView extends LoggedView {
|
||||||
testID='settings-view-button'
|
testID='settings-view-button'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Loading visible={this.state.saving} />
|
<Loading visible={saving} />
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
FlatList, View, Text, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { openSnippetedMessages as openSnippetedMessagesAction, closeSnippetedMessages as closeSnippetedMessagesAction } from '../../actions/snippetedMessages';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import { openSnippetedMessages, closeSnippetedMessages } from '../../actions/snippetedMessages';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
|
@ -19,8 +21,8 @@ import I18n from '../../i18n';
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
}
|
}
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openSnippetedMessages: (rid, limit) => dispatch(openSnippetedMessages(rid, limit)),
|
openSnippetedMessages: (rid, limit) => dispatch(openSnippetedMessagesAction(rid, limit)),
|
||||||
closeSnippetedMessages: () => dispatch(closeSnippetedMessages())
|
closeSnippetedMessages: () => dispatch(closeSnippetedMessagesAction())
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class SnippetedMessagesView extends LoggedView {
|
export default class SnippetedMessagesView extends LoggedView {
|
||||||
|
@ -47,17 +49,20 @@ export default class SnippetedMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
const { ready } = this.props;
|
||||||
|
if (nextProps.ready && nextProps.ready !== ready) {
|
||||||
this.setState({ loading: false, loadingMore: false });
|
this.setState({ loading: false, loadingMore: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.closeSnippetedMessages();
|
const { closeSnippetedMessages } = this.props;
|
||||||
|
closeSnippetedMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load = () => {
|
||||||
this.props.openSnippetedMessages(this.props.rid, this.limit);
|
const { rid, openSnippetedMessages } = this.props;
|
||||||
|
openSnippetedMessages(rid, this.limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
moreData = () => {
|
moreData = () => {
|
||||||
|
@ -79,15 +84,18 @@ export default class SnippetedMessagesView extends LoggedView {
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderItem = ({ item }) => (
|
renderItem = ({ item }) => {
|
||||||
<Message
|
const { user } = this.props;
|
||||||
item={item}
|
return (
|
||||||
style={styles.message}
|
<Message
|
||||||
reactions={item.reactions}
|
item={item}
|
||||||
user={this.props.user}
|
style={styles.message}
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
reactions={item.reactions}
|
||||||
/>
|
user={user}
|
||||||
);
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, loadingMore } = this.state;
|
const { loading, loadingMore } = this.state;
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
import {
|
||||||
|
FlatList, View, Text, SafeAreaView
|
||||||
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ActionSheet from 'react-native-actionsheet';
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
|
|
||||||
|
import { openStarredMessages as openStarredMessagesAction, closeStarredMessages as closeStarredMessagesAction } from '../../actions/starredMessages';
|
||||||
|
import { toggleStarRequest as toggleStarRequestAction } from '../../actions/messages';
|
||||||
import LoggedView from '../View';
|
import LoggedView from '../View';
|
||||||
import { openStarredMessages, closeStarredMessages } from '../../actions/starredMessages';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
import { toggleStarRequest } from '../../actions/messages';
|
|
||||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
@ -25,9 +27,9 @@ const options = [I18n.t('Unstar'), I18n.t('Cancel')];
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
}
|
}
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
openStarredMessages: (rid, limit) => dispatch(openStarredMessages(rid, limit)),
|
openStarredMessages: (rid, limit) => dispatch(openStarredMessagesAction(rid, limit)),
|
||||||
closeStarredMessages: () => dispatch(closeStarredMessages()),
|
closeStarredMessages: () => dispatch(closeStarredMessagesAction()),
|
||||||
toggleStarRequest: message => dispatch(toggleStarRequest(message))
|
toggleStarRequest: message => dispatch(toggleStarRequestAction(message))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
/** @extends React.Component */
|
||||||
export default class StarredMessagesView extends LoggedView {
|
export default class StarredMessagesView extends LoggedView {
|
||||||
|
@ -56,13 +58,15 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
const { ready } = this.props;
|
||||||
|
if (nextProps.ready && nextProps.ready !== ready) {
|
||||||
this.setState({ loading: false, loadingMore: false });
|
this.setState({ loading: false, loadingMore: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.closeStarredMessages();
|
const { closeStarredMessages } = this.props;
|
||||||
|
closeStarredMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
onLongPress = (message) => {
|
onLongPress = (message) => {
|
||||||
|
@ -73,9 +77,12 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleActionPress = (actionIndex) => {
|
handleActionPress = (actionIndex) => {
|
||||||
|
const { message } = this.state;
|
||||||
|
const { toggleStarRequest } = this.props;
|
||||||
|
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
case STAR_INDEX:
|
case STAR_INDEX:
|
||||||
this.props.toggleStarRequest(this.state.message);
|
toggleStarRequest(message);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -83,7 +90,8 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
}
|
}
|
||||||
|
|
||||||
load = () => {
|
load = () => {
|
||||||
this.props.openStarredMessages(this.props.rid, this.limit);
|
const { rid, openStarredMessages } = this.props;
|
||||||
|
openStarredMessages(rid, this.limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
moreData = () => {
|
moreData = () => {
|
||||||
|
@ -105,16 +113,19 @@ export default class StarredMessagesView extends LoggedView {
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderItem = ({ item }) => (
|
renderItem = ({ item }) => {
|
||||||
<Message
|
const { user } = this.props;
|
||||||
item={item}
|
return (
|
||||||
style={styles.message}
|
<Message
|
||||||
reactions={item.reactions}
|
item={item}
|
||||||
user={this.props.user}
|
style={styles.message}
|
||||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
reactions={item.reactions}
|
||||||
onLongPress={this.onLongPress}
|
user={user}
|
||||||
/>
|
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||||
)
|
onLongPress={this.onLongPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, loadingMore } = this.state;
|
const { loading, loadingMore } = this.state;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { StyleSheet, Platform } from 'react-native';
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
|
|
||||||
import { COLOR_DANGER, COLOR_BUTTON_PRIMARY, COLOR_TEXT, COLOR_SEPARATOR } from '../constants/colors';
|
import {
|
||||||
|
COLOR_DANGER, COLOR_BUTTON_PRIMARY, COLOR_TEXT, COLOR_SEPARATOR
|
||||||
|
} from '../constants/colors';
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
|