Beta (#265)
* Fabric iOS * Fabric configured on iOS and Android * - react-native-fabric configured - login tracked * README updated * Run scripts from README updated * README scripts * get rooms and messages by rest * user status * more improves * more improves * send pong on timeout * fix some methods * more tests * rest messages * Room actions (#266) * Toggle notifications * Search messages * Invite users * Mute/Unmute users in room * rocket.cat messages * Room topic layout fixed * Starred messages loading onEndReached * Room actions onEndReached * Unnecessary login request * Login loading * Login services fixed * User presence layout * ïmproves on room actions view * Removed unnecessary data from SelectedUsersView * load few messages on open room, search message improve * fix loading messages forever * Removed state from search * Custom message time format * secureTextEntry layout * Reduce android app size * Roles subscription fix * Public routes navigation * fix reconnect * - New login/register, login, register * proguard * Login flux * App init/restore * Android layout fixes * Multiple meteor connection requests fixed * Nested attachments * Nested attachments * fix check status * New login layout (#269) * Public routes navigation * New login/register, login, register * Multiple meteor connection requests fixed * Nested attachments * Button component * TextInput android layout fixed * Register fixed * Thinner close modal button * Requests /me after login only one time * Static images moved * fix reconnect * fix ddp * fix custom emoji * New message layout (#273) * Grouping messages * Message layout * Users typing animation * Image attachment layout
|
@ -28,7 +28,7 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: |
|
command: |
|
||||||
npm test
|
npm run test
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Codecov
|
name: Codecov
|
||||||
|
@ -151,9 +151,7 @@ jobs:
|
||||||
name: Install NPM modules
|
name: Install NPM modules
|
||||||
command: |
|
command: |
|
||||||
rm -rf node_modules
|
rm -rf node_modules
|
||||||
# npm install --save react-native@0.51
|
|
||||||
npm install
|
npm install
|
||||||
# npm install react-native
|
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Fix known build error
|
name: Fix known build error
|
||||||
|
@ -227,6 +225,7 @@ workflows:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
|
- beta
|
||||||
- develop
|
- develop
|
||||||
- master
|
- master
|
||||||
# - ios-testflight:
|
# - ios-testflight:
|
||||||
|
|
10
README.md
|
@ -22,20 +22,20 @@ Follow the [React Native Getting Started Guide](https://facebook.github.io/react
|
||||||
$ git clone git@github.com:RocketChat/Rocket.Chat.ReactNative.git
|
$ git clone git@github.com:RocketChat/Rocket.Chat.ReactNative.git
|
||||||
$ cd Rocket.Chat.ReactNative
|
$ cd Rocket.Chat.ReactNative
|
||||||
$ npm install -g react-native-cli
|
$ npm install -g react-native-cli
|
||||||
$ yarn
|
$ npm install
|
||||||
```
|
```
|
||||||
- Configuration
|
- Configuration
|
||||||
```bash
|
```bash
|
||||||
$ yarn fabric-ios --key="YOUR_API_KEY" --secret="YOUR_API_SECRET"
|
$ npm run fabric-ios --key="YOUR_API_KEY" --secret="YOUR_API_SECRET"
|
||||||
$ yarn fabric-android --key="YOUR_API_KEY" --secret="YOUR_API_SECRET"
|
$ npm run fabric-android --key="YOUR_API_KEY" --secret="YOUR_API_SECRET"
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run application
|
- Run application
|
||||||
```bash
|
```bash
|
||||||
$ yarn ios
|
$ npm run ios
|
||||||
```
|
```
|
||||||
```bash
|
```bash
|
||||||
$ yarn android
|
$ npm run android
|
||||||
```
|
```
|
||||||
|
|
||||||
# Storybook
|
# Storybook
|
||||||
|
|
|
@ -46,7 +46,7 @@ exports[`render channel 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#00BCD4",
|
"backgroundColor": "#00BCD4",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -147,10 +147,8 @@ exports[`render channel 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -206,7 +204,7 @@ exports[`render no icon 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#3F51B5",
|
"backgroundColor": "#3F51B5",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -307,10 +305,8 @@ exports[`render no icon 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -366,7 +362,7 @@ exports[`render private group 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#FF9800",
|
"backgroundColor": "#FF9800",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -467,10 +463,8 @@ exports[`render private group 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -527,7 +521,7 @@ exports[`render unread +999 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#3F51B5",
|
"backgroundColor": "#3F51B5",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -561,13 +555,16 @@ exports[`render unread +999 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -639,10 +636,8 @@ exports[`render unread +999 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -721,7 +716,7 @@ exports[`render unread 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#3F51B5",
|
"backgroundColor": "#3F51B5",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -755,13 +750,16 @@ exports[`render unread 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -833,10 +831,8 @@ exports[`render unread 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -915,7 +911,7 @@ exports[`renders correctly 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#3F51B5",
|
"backgroundColor": "#3F51B5",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -949,13 +945,16 @@ exports[`renders correctly 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -1027,10 +1026,8 @@ exports[`renders correctly 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -12,7 +12,7 @@ exports[`Storyshots Avatar avatar 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#3F51B5",
|
"backgroundColor": "#3F51B5",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 25,
|
"height": 25,
|
||||||
"width": 25,
|
"width": 25,
|
||||||
},
|
},
|
||||||
|
@ -48,7 +48,7 @@ exports[`Storyshots Avatar avatar 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#9C27B0",
|
"backgroundColor": "#9C27B0",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 40,
|
"height": 40,
|
||||||
"width": 40,
|
"width": 40,
|
||||||
},
|
},
|
||||||
|
@ -84,7 +84,7 @@ exports[`Storyshots Avatar avatar 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#9C27B0",
|
"backgroundColor": "#9C27B0",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 30,
|
"height": 30,
|
||||||
"width": 30,
|
"width": 30,
|
||||||
},
|
},
|
||||||
|
@ -198,7 +198,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#8BC34A",
|
"backgroundColor": "#8BC34A",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -232,13 +232,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -310,10 +313,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -364,7 +365,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#8BC34A",
|
"backgroundColor": "#8BC34A",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -398,13 +399,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -480,10 +484,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -534,7 +536,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#8BC34A",
|
"backgroundColor": "#8BC34A",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -568,13 +570,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -646,10 +651,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -723,7 +726,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#795548",
|
"backgroundColor": "#795548",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -757,13 +760,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -839,10 +845,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -916,7 +920,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#795548",
|
"backgroundColor": "#795548",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -950,13 +954,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -1028,10 +1035,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1105,7 +1110,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#795548",
|
"backgroundColor": "#795548",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -1139,13 +1144,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -1217,10 +1225,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1294,7 +1300,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#795548",
|
"backgroundColor": "#795548",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -1328,13 +1334,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -1406,10 +1415,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1483,7 +1490,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#795548",
|
"backgroundColor": "#795548",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -1517,13 +1524,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -1595,10 +1605,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -1672,7 +1680,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#E91E63",
|
"backgroundColor": "#E91E63",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -1706,13 +1714,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -1784,10 +1795,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -1838,7 +1847,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#9C27B0",
|
"backgroundColor": "#9C27B0",
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -1872,13 +1881,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -1950,10 +1962,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -2004,7 +2014,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": undefined,
|
"backgroundColor": undefined,
|
||||||
"borderRadius": 4,
|
"borderRadius": 2,
|
||||||
"height": 46,
|
"height": 46,
|
||||||
"width": 46,
|
"width": 46,
|
||||||
},
|
},
|
||||||
|
@ -2038,13 +2048,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
"height": 16,
|
"height": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
},
|
},
|
||||||
Object {
|
Array [
|
||||||
"borderColor": "#fff",
|
Object {
|
||||||
"borderWidth": 3,
|
"borderColor": "#fff",
|
||||||
"bottom": -3,
|
"borderWidth": 3,
|
||||||
"position": "absolute",
|
"bottom": -3,
|
||||||
"right": -3,
|
"position": "absolute",
|
||||||
},
|
"right": -3,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
],
|
||||||
Object {
|
Object {
|
||||||
"backgroundColor": "#cbced1",
|
"backgroundColor": "#cbced1",
|
||||||
},
|
},
|
||||||
|
@ -2116,10 +2129,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"alignItems": "flex-end",
|
"alignItems": "flex-end",
|
||||||
"flex": 1,
|
|
||||||
"flexDirection": "row",
|
"flexDirection": "row",
|
||||||
"justifyContent": "flex-end",
|
"justifyContent": "flex-end",
|
||||||
"width": "100%",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -82,12 +82,12 @@ apply from: "../../node_modules/react-native/react.gradle"
|
||||||
* Upload all the APKs to the Play Store and people will download
|
* Upload all the APKs to the Play Store and people will download
|
||||||
* the correct one based on the CPU architecture of their device.
|
* the correct one based on the CPU architecture of their device.
|
||||||
*/
|
*/
|
||||||
def enableSeparateBuildPerCPUArchitecture = false
|
def enableSeparateBuildPerCPUArchitecture = true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run Proguard to shrink the Java bytecode in release builds.
|
* Run Proguard to shrink the Java bytecode in release builds.
|
||||||
*/
|
*/
|
||||||
def enableProguardInReleaseBuilds = false
|
def enableProguardInReleaseBuilds = true
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25
|
compileSdkVersion 25
|
||||||
|
@ -98,11 +98,12 @@ android {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "1.1"
|
versionName "1"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86"
|
abiFilters "armeabi-v7a", "x86"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
if (project.hasProperty('KEYSTORE')) {
|
if (project.hasProperty('KEYSTORE')) {
|
||||||
|
@ -123,10 +124,16 @@ android {
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled enableProguardInReleaseBuilds
|
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
|
shrinkResources enableProguardInReleaseBuilds
|
||||||
|
zipAlignEnabled enableProguardInReleaseBuilds
|
||||||
|
minifyEnabled enableProguardInReleaseBuilds
|
||||||
|
useProguard enableProguardInReleaseBuilds
|
||||||
|
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
|
||||||
}
|
}
|
||||||
|
debug {
|
||||||
|
applicationIdSuffix ".debug"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// applicationVariants are e.g. debug, release
|
// applicationVariants are e.g. debug, release
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
# Disabling obfuscation is useful if you collect stack traces from production crashes
|
# Disabling obfuscation is useful if you collect stack traces from production crashes
|
||||||
# (unless you are using a system that supports de-obfuscate the stack traces).
|
# (unless you are using a system that supports de-obfuscate the stack traces).
|
||||||
-dontobfuscate
|
# -dontobfuscate
|
||||||
|
|
||||||
# React Native
|
# React Native
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@
|
||||||
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
|
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
|
||||||
|
|
||||||
-dontwarn com.facebook.react.**
|
-dontwarn com.facebook.react.**
|
||||||
|
-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
|
||||||
|
|
||||||
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
|
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
|
||||||
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
|
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
|
||||||
|
@ -68,3 +69,25 @@
|
||||||
-dontwarn java.nio.file.*
|
-dontwarn java.nio.file.*
|
||||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||||
-dontwarn okio.**
|
-dontwarn okio.**
|
||||||
|
|
||||||
|
# Fresco
|
||||||
|
# Keep our interfaces so they can be used by other ProGuard rules.
|
||||||
|
# See http://sourceforge.net/p/proguard/bugs/466/
|
||||||
|
-keep,allowobfuscation @interface com.facebook.soloader.DoNotOptimize
|
||||||
|
|
||||||
|
# Do not strip any method/class that is annotated with @DoNotOptimize
|
||||||
|
-keep @com.facebook.soloader.DoNotOptimize class *
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@com.facebook.soloader.DoNotOptimize *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keep native methods
|
||||||
|
-keepclassmembers class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# For Fabric to properly de-obfuscate your crash reports, you need to remove this line from your ProGuard config:
|
||||||
|
-printmapping mapping.txt
|
||||||
|
|
||||||
|
-dontwarn javax.annotation.**
|
||||||
|
-dontwarn com.facebook.infer.**
|
||||||
|
|
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 35 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="primary_dark">#660B0B0B</color> </resources>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">[DEVELOP] RocketChatRN</string>
|
||||||
|
|
||||||
|
<string name="no_browser_found">No Browser Found</string>
|
||||||
|
</resources>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="android:colorEdgeEffect">#aaaaaa</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -26,7 +26,9 @@
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:largeHeap="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
|
@ -10,4 +10,6 @@ if (__DEV__) {
|
||||||
.use(reactotronRedux())
|
.use(reactotronRedux())
|
||||||
.use(sagaPlugin())
|
.use(sagaPlugin())
|
||||||
.connect();
|
.connect();
|
||||||
|
// Running on android device
|
||||||
|
// $ adb reverse tcp:9090 tcp:9090
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,16 +74,8 @@ export const MESSAGES = createRequestTypes('MESSAGES', [
|
||||||
'CLEAR_INPUT',
|
'CLEAR_INPUT',
|
||||||
'TOGGLE_REACTION_PICKER'
|
'TOGGLE_REACTION_PICKER'
|
||||||
]);
|
]);
|
||||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [
|
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||||
...defaultTypes,
|
export const SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER', 'REMOVE_USER', 'RESET', 'SET_LOADING']);
|
||||||
'REQUEST_USERS',
|
|
||||||
'SUCCESS_USERS',
|
|
||||||
'FAILURE_USERS',
|
|
||||||
'SET_USERS',
|
|
||||||
'ADD_USER',
|
|
||||||
'REMOVE_USER',
|
|
||||||
'RESET'
|
|
||||||
]);
|
|
||||||
export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']);
|
export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']);
|
||||||
export const SERVER = createRequestTypes('SERVER', [
|
export const SERVER = createRequestTypes('SERVER', [
|
||||||
...defaultTypes,
|
...defaultTypes,
|
||||||
|
@ -96,11 +88,11 @@ export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DI
|
||||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||||
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET']);
|
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET']);
|
||||||
export const ROLES = createRequestTypes('ROLES', ['SET']);
|
export const ROLES = createRequestTypes('ROLES', ['SET']);
|
||||||
export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNSTARRED']);
|
export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNSTARRED']);
|
||||||
export const PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNPINNED']);
|
export const PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNPINNED']);
|
||||||
export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
|
export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||||
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
|
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||||
export const ROOM_FILES = createRequestTypes('ROOM_FILES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
|
export const ROOM_FILES = createRequestTypes('ROOM_FILES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||||
|
|
||||||
export const INCREMENT = 'INCREMENT';
|
export const INCREMENT = 'INCREMENT';
|
||||||
export const DECREMENT = 'DECREMENT';
|
export const DECREMENT = 'DECREMENT';
|
||||||
|
|
|
@ -20,51 +20,3 @@ export function createChannelFailure(err) {
|
||||||
err
|
err
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createChannelRequestUsers(data) {
|
|
||||||
return {
|
|
||||||
type: types.CREATE_CHANNEL.REQUEST_USERS,
|
|
||||||
data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createChannelSetUsers(data) {
|
|
||||||
return {
|
|
||||||
type: types.CREATE_CHANNEL.SET_USERS,
|
|
||||||
data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createChannelSuccessUsers(data) {
|
|
||||||
return {
|
|
||||||
type: types.CREATE_CHANNEL.SUCCESS_USERS,
|
|
||||||
data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createChannelFailureUsers(err) {
|
|
||||||
return {
|
|
||||||
type: types.CREATE_CHANNEL.FAILURE_USERS,
|
|
||||||
err
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addUser(user) {
|
|
||||||
return {
|
|
||||||
type: types.CREATE_CHANNEL.ADD_USER,
|
|
||||||
user
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeUser(user) {
|
|
||||||
return {
|
|
||||||
type: types.CREATE_CHANNEL.REMOVE_USER,
|
|
||||||
user
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reset() {
|
|
||||||
return {
|
|
||||||
type: types.CREATE_CHANNEL.RESET
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
export function openMentionedMessages(rid) {
|
export function openMentionedMessages(rid, limit) {
|
||||||
return {
|
return {
|
||||||
type: types.MENTIONED_MESSAGES.OPEN,
|
type: types.MENTIONED_MESSAGES.OPEN,
|
||||||
rid
|
rid,
|
||||||
|
limit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readyMentionedMessages() {
|
||||||
|
return {
|
||||||
|
type: types.MENTIONED_MESSAGES.READY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function closeMentionedMessages() {
|
export function closeMentionedMessages() {
|
||||||
return {
|
return {
|
||||||
type: types.MENTIONED_MESSAGES.CLOSE
|
type: types.MENTIONED_MESSAGES.CLOSE
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
export function messagesRequest({ rid }) {
|
export function messagesRequest(room) {
|
||||||
return {
|
return {
|
||||||
type: types.MESSAGES.REQUEST,
|
type: types.MESSAGES.REQUEST,
|
||||||
rid
|
room
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
export function openPinnedMessages(rid) {
|
export function openPinnedMessages(rid, limit) {
|
||||||
return {
|
return {
|
||||||
type: types.PINNED_MESSAGES.OPEN,
|
type: types.PINNED_MESSAGES.OPEN,
|
||||||
rid
|
rid,
|
||||||
|
limit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readyPinnedMessages() {
|
||||||
|
return {
|
||||||
|
type: types.PINNED_MESSAGES.READY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function closePinnedMessages() {
|
export function closePinnedMessages() {
|
||||||
return {
|
return {
|
||||||
type: types.PINNED_MESSAGES.CLOSE
|
type: types.PINNED_MESSAGES.CLOSE
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
export function openRoomFiles(rid) {
|
export function openRoomFiles(rid, limit) {
|
||||||
return {
|
return {
|
||||||
type: types.ROOM_FILES.OPEN,
|
type: types.ROOM_FILES.OPEN,
|
||||||
rid
|
rid,
|
||||||
|
limit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readyRoomFiles() {
|
||||||
|
return {
|
||||||
|
type: types.ROOM_FILES.READY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
|
export function addUser(user) {
|
||||||
|
return {
|
||||||
|
type: types.SELECTED_USERS.ADD_USER,
|
||||||
|
user
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeUser(user) {
|
||||||
|
return {
|
||||||
|
type: types.SELECTED_USERS.REMOVE_USER,
|
||||||
|
user
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reset() {
|
||||||
|
return {
|
||||||
|
type: types.SELECTED_USERS.RESET
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLoading(loading) {
|
||||||
|
return {
|
||||||
|
type: types.SELECTED_USERS.SET_LOADING,
|
||||||
|
loading
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
export function openSnippetedMessages(rid) {
|
export function openSnippetedMessages(rid, limit) {
|
||||||
return {
|
return {
|
||||||
type: types.SNIPPETED_MESSAGES.OPEN,
|
type: types.SNIPPETED_MESSAGES.OPEN,
|
||||||
rid
|
rid,
|
||||||
|
limit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readySnippetedMessages() {
|
||||||
|
return {
|
||||||
|
type: types.SNIPPETED_MESSAGES.READY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
export function openStarredMessages(rid) {
|
export function openStarredMessages(rid, limit) {
|
||||||
return {
|
return {
|
||||||
type: types.STARRED_MESSAGES.OPEN,
|
type: types.STARRED_MESSAGES.OPEN,
|
||||||
rid
|
rid,
|
||||||
|
limit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readyStarredMessages() {
|
||||||
|
return {
|
||||||
|
type: types.STARRED_MESSAGES.READY
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
export const AVATAR_COLORS = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B'];
|
export const AVATAR_COLORS = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B'];
|
||||||
export const ESLINT_FIX = null;
|
export const ESLINT_FIX = null;
|
||||||
export const COLOR_DANGER = '#f5455c';
|
export const COLOR_DANGER = '#f5455c';
|
||||||
|
export const COLOR_BUTTON_PRIMARY = '#2D6AEA';
|
||||||
|
export const COLOR_TEXT = '#292E35';
|
||||||
export const STATUS_COLORS = {
|
export const STATUS_COLORS = {
|
||||||
online: '#2de0a5',
|
online: '#2de0a5',
|
||||||
busy: COLOR_DANGER,
|
busy: COLOR_DANGER,
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ActivityIndicator, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
indicator: {
|
||||||
|
padding: 10
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const RCActivityIndicator = () => <ActivityIndicator style={styles.indicator} />;
|
||||||
|
|
||||||
|
export default RCActivityIndicator;
|
|
@ -36,7 +36,7 @@ export default class Avatar extends React.PureComponent {
|
||||||
};
|
};
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar, type = 'd'
|
text = '', size = 25, baseUrl, borderRadius = 2, style, avatar, type = 'd'
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
bannerContainer: {
|
bannerContainer: {
|
||||||
backgroundColor: '#ddd',
|
backgroundColor: '#ddd'
|
||||||
position: 'absolute',
|
|
||||||
top: '0%',
|
|
||||||
zIndex: 10,
|
|
||||||
width: '100%'
|
|
||||||
},
|
},
|
||||||
bannerText: {
|
bannerText: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
@ -21,7 +17,8 @@ const styles = StyleSheet.create({
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
connecting: state.meteor.connecting,
|
connecting: state.meteor.connecting,
|
||||||
authenticating: state.login.isFetching,
|
authenticating: state.login.isFetching,
|
||||||
offline: !state.meteor.connected
|
offline: !state.meteor.connected,
|
||||||
|
logged: !!state.login.token
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export default class Banner extends React.PureComponent {
|
export default class Banner extends React.PureComponent {
|
||||||
|
@ -31,7 +28,9 @@ export default class Banner extends React.PureComponent {
|
||||||
offline: PropTypes.bool
|
offline: PropTypes.bool
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { connecting, authenticating, offline } = this.props;
|
const {
|
||||||
|
connecting, authenticating, offline, logged
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (offline) {
|
if (offline) {
|
||||||
return (
|
return (
|
||||||
|
@ -40,6 +39,7 @@ export default class Banner extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connecting) {
|
if (connecting) {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.bannerContainer, { backgroundColor: '#0d0' }]}>
|
<View style={[styles.bannerContainer, { backgroundColor: '#0d0' }]}>
|
||||||
|
@ -56,6 +56,14 @@ export default class Banner extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (logged) {
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.bannerContainer, { backgroundColor: 'orange' }]}>
|
||||||
|
<Text style={[styles.bannerText, { color: '#a00' }]}>Not logged...</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { StyleSheet, View, Text, Platform } from 'react-native';
|
||||||
|
|
||||||
|
import { COLOR_BUTTON_PRIMARY, COLOR_TEXT } from '../../constants/colors';
|
||||||
|
import Touch from '../../utils/touch';
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
backgroundPrimary: COLOR_BUTTON_PRIMARY,
|
||||||
|
backgroundSecondary: 'white',
|
||||||
|
|
||||||
|
textColorPrimary: 'white',
|
||||||
|
textColorSecondary: COLOR_TEXT
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingVertical: 10,
|
||||||
|
borderRadius: 2
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontWeight: '700'
|
||||||
|
},
|
||||||
|
background_primary: {
|
||||||
|
backgroundColor: colors.backgroundPrimary
|
||||||
|
},
|
||||||
|
background_secondary: {
|
||||||
|
backgroundColor: colors.backgroundSecondary
|
||||||
|
},
|
||||||
|
text_color_primary: {
|
||||||
|
color: colors.textColorPrimary
|
||||||
|
},
|
||||||
|
text_color_secondary: {
|
||||||
|
color: colors.textColorSecondary
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
marginBottom: 10
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
opacity: 0.5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default class Button extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
type: PropTypes.string,
|
||||||
|
onPress: PropTypes.func,
|
||||||
|
disabled: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
title: 'Press me!',
|
||||||
|
type: 'primary',
|
||||||
|
onPress: () => alert('It works!'),
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
title, type, onPress, disabled
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<Touch
|
||||||
|
onPress={onPress}
|
||||||
|
accessibilityTraits='button'
|
||||||
|
style={Platform.OS === 'ios' && styles.margin}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.container,
|
||||||
|
styles[`background_${ type }`],
|
||||||
|
Platform.OS === 'android' && styles.margin,
|
||||||
|
disabled && styles.disabled
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text style={[styles.text, styles[`text_color_${ type }`]]}>{title}</Text>
|
||||||
|
</View>
|
||||||
|
</Touch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { TouchableOpacity, StyleSheet } from 'react-native';
|
||||||
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
|
import { NavigationActions } from 'react-navigation';
|
||||||
|
import { COLOR_TEXT } from '../constants/colors';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
button: {
|
||||||
|
width: 25,
|
||||||
|
height: 25,
|
||||||
|
marginTop: 5
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
color: COLOR_TEXT,
|
||||||
|
left: -5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default class CloseModalButton extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={() => this.props.navigation.dispatch(NavigationActions.back())} style={styles.button}>
|
||||||
|
<Icon
|
||||||
|
style={styles.icon}
|
||||||
|
name='close'
|
||||||
|
size={25}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ScrollView } from 'react-native';
|
import { ScrollView } from 'react-native';
|
||||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||||
import _ from 'lodash';
|
import map from 'lodash/map';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
import TabBar from './TabBar';
|
import TabBar from './TabBar';
|
||||||
import EmojiCategory from './EmojiCategory';
|
import EmojiCategory from './EmojiCategory';
|
||||||
|
@ -78,7 +78,7 @@ export default class EmojiPicker extends Component {
|
||||||
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) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ 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 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { StyleSheet, View, Modal, Animated } from 'react-native';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.25)'
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
resizeMode: 'contain'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default class Loading extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
visible: PropTypes.bool.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
scale: new Animated.Value(1),
|
||||||
|
opacity: new Animated.Value(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.opacityAnimation = Animated.timing(
|
||||||
|
this.state.opacity,
|
||||||
|
{
|
||||||
|
toValue: 1,
|
||||||
|
duration: 1000,
|
||||||
|
useNativeDriver: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.scaleAnimation = Animated.loop(Animated.sequence([
|
||||||
|
Animated.timing(
|
||||||
|
this.state.scale,
|
||||||
|
{
|
||||||
|
toValue: 0,
|
||||||
|
duration: 1000,
|
||||||
|
useNativeDriver: true
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Animated.timing(
|
||||||
|
this.state.scale,
|
||||||
|
{
|
||||||
|
toValue: 1,
|
||||||
|
duration: 1000,
|
||||||
|
useNativeDriver: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
|
||||||
|
if (this.props.visible) {
|
||||||
|
this.startAnimations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.visible && this.props.visible !== prevProps.visible) {
|
||||||
|
this.startAnimations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.opacityAnimation.stop();
|
||||||
|
this.scaleAnimation.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
startAnimations() {
|
||||||
|
this.opacityAnimation.start();
|
||||||
|
this.scaleAnimation.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const scale = this.state.scale.interpolate({
|
||||||
|
inputRange: [0, 0.5, 1],
|
||||||
|
outputRange: [1, 1.1, 1]
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={this.props.visible}
|
||||||
|
transparent
|
||||||
|
onRequestClose={() => {}}
|
||||||
|
>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Animated.Image
|
||||||
|
source={require('../static/images/logo.png')}
|
||||||
|
style={[styles.image, {
|
||||||
|
opacity: this.state.opacity,
|
||||||
|
transform: [{
|
||||||
|
scale
|
||||||
|
}]
|
||||||
|
}]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,6 +80,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
|
|
||||||
onChangeText(text) {
|
onChangeText(text) {
|
||||||
this.setState({ text });
|
this.setState({ text });
|
||||||
|
this.props.typing(text.length > 0);
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const { start, end } = this.component._lastNativeSelection;
|
const { start, end } = this.component._lastNativeSelection;
|
||||||
|
@ -174,11 +175,11 @@ export default class MessageBox extends React.PureComponent {
|
||||||
};
|
};
|
||||||
ImagePicker.showImagePicker(options, (response) => {
|
ImagePicker.showImagePicker(options, (response) => {
|
||||||
if (response.didCancel) {
|
if (response.didCancel) {
|
||||||
console.log('User cancelled image picker');
|
console.warn('User cancelled image picker');
|
||||||
} else if (response.error) {
|
} else if (response.error) {
|
||||||
console.log('ImagePicker Error: ', response.error);
|
console.warn('ImagePicker Error: ', response.error);
|
||||||
} else if (response.customButton) {
|
} else if (response.customButton) {
|
||||||
console.log('User tapped custom button: ', response.customButton);
|
console.warn('User tapped custom button: ', response.customButton);
|
||||||
} else {
|
} else {
|
||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
name: response.fileName,
|
name: response.fileName,
|
||||||
|
@ -278,7 +279,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('spotlight canceled');
|
console.warn('spotlight canceled');
|
||||||
} 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();
|
||||||
|
@ -321,7 +322,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.roomsCache = [...this.roomsCache, ...results.rooms].filter(onlyUnique);
|
this.roomsCache = [...this.roomsCache, ...results.rooms].filter(onlyUnique);
|
||||||
this.setState({ mentions: [...rooms.slice(), ...results.rooms] });
|
this.setState({ mentions: [...rooms.slice(), ...results.rooms] });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('spotlight canceled');
|
console.warn('spotlight canceled');
|
||||||
} finally {
|
} finally {
|
||||||
delete this.oldPromise;
|
delete this.oldPromise;
|
||||||
}
|
}
|
||||||
|
@ -454,7 +455,6 @@ export default class MessageBox extends React.PureComponent {
|
||||||
style={{ margin: 8 }}
|
style={{ margin: 8 }}
|
||||||
text={item.username || item.name}
|
text={item.username || item.name}
|
||||||
size={30}
|
size={30}
|
||||||
baseUrl={this.props.baseUrl}
|
|
||||||
/>,
|
/>,
|
||||||
<Text key='mention-item-name'>{ item.username || item.name }</Text>
|
<Text key='mention-item-name'>{ item.username || item.name }</Text>
|
||||||
]
|
]
|
||||||
|
|
|
@ -22,8 +22,9 @@ export default StyleSheet.create({
|
||||||
maxHeight: 120,
|
maxHeight: 120,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
width: 1,
|
width: 1,
|
||||||
paddingTop: 15,
|
// paddingVertical: 12, needs to be paddingTop/paddingBottom because of iOS/Android's TextInput differences on rendering
|
||||||
paddingBottom: 15,
|
paddingTop: 12,
|
||||||
|
paddingBottom: 12,
|
||||||
paddingLeft: 0,
|
paddingLeft: 0,
|
||||||
paddingRight: 0
|
paddingRight: 0
|
||||||
},
|
},
|
||||||
|
@ -35,7 +36,7 @@ export default StyleSheet.create({
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
padding: 15,
|
padding: 15,
|
||||||
paddingHorizontal: 21,
|
paddingHorizontal: 12,
|
||||||
flex: 0
|
flex: 0
|
||||||
},
|
},
|
||||||
mentionList: {
|
mentionList: {
|
||||||
|
|
|
@ -1,26 +1,31 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, StyleSheet, Text, TextInput } 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/FontAwesome';
|
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { COLOR_DANGER } from '../constants/colors';
|
import { COLOR_DANGER, COLOR_TEXT } from '../constants/colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
inputContainer: {
|
inputContainer: {
|
||||||
marginBottom: 20
|
marginBottom: 15
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
marginBottom: 4,
|
marginBottom: 10,
|
||||||
fontSize: 16
|
color: COLOR_TEXT,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '700'
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
|
fontSize: 14,
|
||||||
paddingTop: 12,
|
paddingTop: 12,
|
||||||
paddingBottom: 12,
|
paddingBottom: 12,
|
||||||
|
// paddingTop: 5,
|
||||||
|
// paddingBottom: 5,
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderRadius: 2,
|
borderRadius: 4,
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
borderColor: 'rgba(0,0,0,.15)',
|
borderColor: 'rgba(0,0,0,.15)',
|
||||||
color: 'black'
|
color: 'black'
|
||||||
|
@ -33,14 +38,23 @@ const styles = StyleSheet.create({
|
||||||
borderColor: COLOR_DANGER
|
borderColor: COLOR_DANGER
|
||||||
},
|
},
|
||||||
wrap: {
|
wrap: {
|
||||||
flex: 1,
|
|
||||||
position: 'relative'
|
position: 'relative'
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 0,
|
color: 'rgba(0,0,0,.45)',
|
||||||
padding: 10,
|
height: 45,
|
||||||
color: 'rgba(0,0,0,.45)'
|
textAlignVertical: 'center',
|
||||||
|
...Platform.select({
|
||||||
|
ios: {
|
||||||
|
padding: 12
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingTop: 18,
|
||||||
|
paddingBottom: 6
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -49,7 +63,10 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
secureTextEntry: PropTypes.bool
|
secureTextEntry: PropTypes.bool,
|
||||||
|
containerStyle: ViewPropTypes.style,
|
||||||
|
inputStyle: PropTypes.object,
|
||||||
|
inputRef: PropTypes.func
|
||||||
}
|
}
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
error: {}
|
error: {}
|
||||||
|
@ -58,28 +75,40 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
showPassword: false
|
showPassword: false
|
||||||
}
|
}
|
||||||
|
|
||||||
get icon() { return <Icon name={this.state.showPassword ? 'eye-slash' : 'eye'} style={styles.icon} size={20} onPress={this.tooglePassword} />; }
|
icon = ({ name, onPress, style }) => <Icon name={name} style={[styles.icon, style]} size={20} onPress={onPress} />
|
||||||
|
|
||||||
tooglePassword = () => this.setState({ showPassword: !this.state.showPassword })
|
iconLeft = name => this.icon({ name, onPress: null, style: { left: 0 } });
|
||||||
|
|
||||||
|
iconPassword = name => this.icon({ name, onPress: () => this.tooglePassword(), style: { right: 0 } });
|
||||||
|
|
||||||
|
tooglePassword = () => this.setState({ showPassword: !this.state.showPassword });
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
label, error, secureTextEntry, ...inputProps
|
label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, ...inputProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { showPassword } = this.state;
|
const { showPassword } = this.state;
|
||||||
return (
|
return (
|
||||||
<View style={styles.inputContainer}>
|
<View style={[styles.inputContainer, containerStyle]}>
|
||||||
{ label && <Text style={[styles.label, error.error && styles.labelError]}>{label}</Text> }
|
{ label && <Text style={[styles.label, error.error && styles.labelError]}>{label}</Text> }
|
||||||
<View style={styles.wrap}>
|
<View style={styles.wrap}>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[styles.input, error.error && styles.inputError]}
|
style={[
|
||||||
|
styles.input,
|
||||||
|
error.error && styles.inputError,
|
||||||
|
inputStyle,
|
||||||
|
iconLeft && { paddingLeft: 40 },
|
||||||
|
secureTextEntry && { paddingRight: 40 }
|
||||||
|
]}
|
||||||
|
ref={inputRef}
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
underlineColorAndroid='transparent'
|
underlineColorAndroid='transparent'
|
||||||
secureTextEntry={secureTextEntry && !showPassword}
|
secureTextEntry={secureTextEntry && !showPassword}
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
/>
|
/>
|
||||||
{secureTextEntry && this.icon}
|
{iconLeft && this.iconLeft(iconLeft)}
|
||||||
|
{secureTextEntry && this.iconPassword(showPassword ? 'eye-off' : 'eye')}
|
||||||
</View>
|
</View>
|
||||||
{error.error && <Text style={sharedStyles.error}>{error.reason}</Text>}
|
{error.error && <Text style={sharedStyles.error}>{error.reason}</Text>}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StyleSheet, Text, Keyboard } from 'react-native';
|
import { View, StyleSheet, Text, Keyboard, LayoutAnimation } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
typing: {
|
typing: {
|
||||||
|
|
||||||
transform: [{ scaleY: -1 }],
|
transform: [{ scaleY: -1 }],
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 15,
|
||||||
height: 25
|
height: 25
|
||||||
|
},
|
||||||
|
emptySpace: {
|
||||||
|
height: 5
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,11 +19,13 @@ const styles = StyleSheet.create({
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
usersTyping: state.room.usersTyping
|
usersTyping: state.room.usersTyping
|
||||||
}))
|
}))
|
||||||
|
|
||||||
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();
|
return this.props.usersTyping.join() !== nextProps.usersTyping.join();
|
||||||
}
|
}
|
||||||
|
componentWillUpdate() {
|
||||||
|
LayoutAnimation.easeInEaseOut();
|
||||||
|
}
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
}
|
}
|
||||||
|
@ -31,7 +34,13 @@ export default class Typing extends React.Component {
|
||||||
return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : '';
|
return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : '';
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (<Text style={styles.typing} onPress={() => this.onPress()}>{this.usersTyping}</Text>);
|
const { usersTyping } = this;
|
||||||
|
|
||||||
|
if (!usersTyping) {
|
||||||
|
return <View style={styles.emptySpace} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<Text style={styles.typing} onPress={() => this.onPress()}>{usersTyping}</Text>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { View, StyleSheet, TouchableOpacity, Text, Easing } from 'react-native';
|
||||||
import Video from 'react-native-video';
|
import Video from 'react-native-video';
|
||||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import Slider from 'react-native-slider';
|
import Slider from 'react-native-slider';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
audioContainer: {
|
audioContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
@ -61,6 +61,9 @@ const formatTime = (t = 0, duration = 0) => {
|
||||||
return `${ formattedMinutes }:${ formattedSeconds }`;
|
return `${ formattedMinutes }:${ formattedSeconds }`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
|
}))
|
||||||
export default class Audio extends React.PureComponent {
|
export default class Audio extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
file: PropTypes.object.isRequired,
|
file: PropTypes.object.isRequired,
|
||||||
|
@ -115,8 +118,8 @@ export default class Audio extends React.PureComponent {
|
||||||
const { uri, paused } = this.state;
|
const { uri, paused } = this.state;
|
||||||
const { description } = this.props.file;
|
const { description } = this.props.file;
|
||||||
return (
|
return (
|
||||||
<View>
|
[
|
||||||
<View style={styles.audioContainer}>
|
<View key='audio' style={styles.audioContainer}>
|
||||||
<Video
|
<Video
|
||||||
ref={(ref) => {
|
ref={(ref) => {
|
||||||
this.player = ref;
|
this.player = ref;
|
||||||
|
@ -154,9 +157,9 @@ export default class Audio extends React.PureComponent {
|
||||||
onValueChange={value => this.setState({ currentTime: value })}
|
onValueChange={value => this.setState({ currentTime: value })}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>,
|
||||||
<Markdown msg={description} />
|
<Markdown key='description' msg={description} />
|
||||||
</View>
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@ import React from 'react';
|
||||||
import { Text, ViewPropTypes } from 'react-native';
|
import { Text, ViewPropTypes } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
customEmojis: state.customEmojis
|
||||||
|
}))
|
||||||
export default class Emoji extends React.PureComponent {
|
export default class Emoji extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
content: PropTypes.string,
|
content: PropTypes.string,
|
||||||
|
|
|
@ -1,41 +1,30 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CachedImage } from 'react-native-img-cache';
|
import { CachedImage } from 'react-native-img-cache';
|
||||||
import { Text, TouchableOpacity, View, StyleSheet } from 'react-native';
|
import { TouchableOpacity, StyleSheet } from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import PhotoModal from './PhotoModal';
|
import PhotoModal from './PhotoModal';
|
||||||
|
import Markdown from './Markdown';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'column',
|
flexDirection: 'column'
|
||||||
height: 320,
|
|
||||||
borderColor: '#ccc',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 6
|
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
flex: 1,
|
width: 320,
|
||||||
height: undefined,
|
height: 200,
|
||||||
width: undefined,
|
resizeMode: 'cover'
|
||||||
resizeMode: 'contain'
|
|
||||||
},
|
},
|
||||||
labelContainer: {
|
labelContainer: {
|
||||||
height: 62,
|
alignItems: 'flex-start'
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
imageName: {
|
|
||||||
fontSize: 12,
|
|
||||||
alignSelf: 'center',
|
|
||||||
fontStyle: 'italic'
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
alignSelf: 'center',
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class Image extends React.PureComponent {
|
@connect(state => ({
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
|
}))
|
||||||
|
export default class extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
file: PropTypes.object.isRequired,
|
file: PropTypes.object.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
@ -45,8 +34,9 @@ export default class Image extends React.PureComponent {
|
||||||
state = { modalVisible: false };
|
state = { modalVisible: false };
|
||||||
|
|
||||||
getDescription() {
|
getDescription() {
|
||||||
if (this.props.file.description) {
|
const { file, customEmojis } = this.props;
|
||||||
return <Text style={styles.message}>{this.props.file.description}</Text>;
|
if (file.description) {
|
||||||
|
return <Markdown msg={file.description} customEmojis={customEmojis} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,8 +50,9 @@ export default class Image extends React.PureComponent {
|
||||||
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 }`;
|
||||||
return (
|
return (
|
||||||
<View>
|
[
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
key='image'
|
||||||
onPress={() => this._onPressButton()}
|
onPress={() => this._onPressButton()}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
|
@ -69,18 +60,16 @@ export default class Image extends React.PureComponent {
|
||||||
style={styles.image}
|
style={styles.image}
|
||||||
source={{ uri: encodeURI(img) }}
|
source={{ uri: encodeURI(img) }}
|
||||||
/>
|
/>
|
||||||
<View style={styles.labelContainer}>
|
{this.getDescription()}
|
||||||
<Text style={styles.imageName}>{this.props.file.title}</Text>
|
</TouchableOpacity>,
|
||||||
{this.getDescription()}
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<PhotoModal
|
<PhotoModal
|
||||||
|
key='modal'
|
||||||
title={this.props.file.title}
|
title={this.props.file.title}
|
||||||
image={img}
|
image={img}
|
||||||
isVisible={this.state.modalVisible}
|
isVisible={this.state.modalVisible}
|
||||||
onClose={() => this.setState({ modalVisible: false })}
|
onClose={() => this.setState({ modalVisible: false })}
|
||||||
/>
|
/>
|
||||||
</View>
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
||||||
import EasyMarkdown from 'react-native-easy-markdown'; // eslint-disable-line
|
import EasyMarkdown from 'react-native-easy-markdown'; // eslint-disable-line
|
||||||
import SimpleMarkdown from 'simple-markdown';
|
import SimpleMarkdown from 'simple-markdown';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
|
|
||||||
|
@ -17,126 +18,139 @@ const BlockCode = ({ node, state }) => (
|
||||||
);
|
);
|
||||||
const mentionStyle = { color: '#13679a' };
|
const mentionStyle = { color: '#13679a' };
|
||||||
|
|
||||||
const Markdown = ({
|
const defaultRules = {
|
||||||
msg, customEmojis, style, markdownStyle, customRules, renderInline
|
username: {
|
||||||
}) => {
|
order: -1,
|
||||||
if (!msg) {
|
match: SimpleMarkdown.inlineRegex(/^@[0-9a-zA-Z-_.]+/),
|
||||||
return null;
|
parse: capture => ({ content: capture[0] }),
|
||||||
}
|
react: (node, output, state) => ({
|
||||||
msg = emojify(msg, { output: 'unicode' });
|
type: 'custom',
|
||||||
|
key: state.key,
|
||||||
const defaultRules = {
|
props: {
|
||||||
username: {
|
children: (
|
||||||
order: -1,
|
<Text
|
||||||
match: SimpleMarkdown.inlineRegex(/^@[0-9a-zA-Z-_.]+/),
|
key={state.key}
|
||||||
parse: capture => ({ content: capture[0] }),
|
style={mentionStyle}
|
||||||
react: (node, output, state) => ({
|
onPress={() => alert('Username')}
|
||||||
type: 'custom',
|
>
|
||||||
key: state.key,
|
{node.content}
|
||||||
props: {
|
</Text>
|
||||||
children: (
|
)
|
||||||
<Text
|
|
||||||
key={state.key}
|
|
||||||
style={mentionStyle}
|
|
||||||
onPress={() => alert('Username')}
|
|
||||||
>
|
|
||||||
{node.content}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
heading: {
|
|
||||||
order: -2,
|
|
||||||
match: SimpleMarkdown.inlineRegex(/^#[0-9a-zA-Z-_.]+/),
|
|
||||||
parse: capture => ({ content: capture[0] }),
|
|
||||||
react: (node, output, state) => ({
|
|
||||||
type: 'custom',
|
|
||||||
key: state.key,
|
|
||||||
props: {
|
|
||||||
children: (
|
|
||||||
<Text
|
|
||||||
key={state.key}
|
|
||||||
style={mentionStyle}
|
|
||||||
onPress={() => alert('Room')}
|
|
||||||
>
|
|
||||||
{node.content}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fence: {
|
|
||||||
order: -3,
|
|
||||||
match: SimpleMarkdown.blockRegex(/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/),
|
|
||||||
parse: capture => ({
|
|
||||||
lang: capture[2] || undefined,
|
|
||||||
content: capture[3]
|
|
||||||
}),
|
|
||||||
react: (node, output, state) => ({
|
|
||||||
type: 'custom',
|
|
||||||
key: state.key,
|
|
||||||
props: {
|
|
||||||
children: (
|
|
||||||
<BlockCode key={state.key} node={node} state={state} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
blockCode: {
|
|
||||||
order: -4,
|
|
||||||
match: SimpleMarkdown.blockRegex(/^(```)\s*([\s\S]*?[^`])\s*\1(?!```)/),
|
|
||||||
parse: capture => ({ content: capture[2] }),
|
|
||||||
react: (node, output, state) => ({
|
|
||||||
type: 'custom',
|
|
||||||
key: state.key,
|
|
||||||
props: {
|
|
||||||
children: (
|
|
||||||
<BlockCode key={state.key} node={node} state={state} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
customEmoji: {
|
|
||||||
order: -5,
|
|
||||||
match: SimpleMarkdown.inlineRegex(/^:([0-9a-zA-Z-_.]+):/),
|
|
||||||
parse: capture => ({ content: capture }),
|
|
||||||
react: (node, output, state) => {
|
|
||||||
const element = {
|
|
||||||
type: 'custom',
|
|
||||||
key: state.key,
|
|
||||||
props: {
|
|
||||||
children: <Text key={state.key}>{node.content[0]}</Text>
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const content = node.content[1];
|
|
||||||
const emojiExtension = customEmojis[content];
|
|
||||||
if (emojiExtension) {
|
|
||||||
const emoji = { extension: emojiExtension, content };
|
|
||||||
element.props.children = (
|
|
||||||
<CustomEmoji key={state.key} style={styles.customEmoji} emoji={emoji} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return element;
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
};
|
},
|
||||||
|
heading: {
|
||||||
const codeStyle = StyleSheet.flatten(styles.codeStyle);
|
order: -2,
|
||||||
style = StyleSheet.flatten(style);
|
match: SimpleMarkdown.inlineRegex(/^#[0-9a-zA-Z-_.]+/),
|
||||||
return (
|
parse: capture => ({ content: capture[0] }),
|
||||||
<EasyMarkdown
|
react: (node, output, state) => ({
|
||||||
style={{ marginBottom: 0, ...style }}
|
type: 'custom',
|
||||||
markdownStyles={{ code: codeStyle, ...markdownStyle }}
|
key: state.key,
|
||||||
rules={{ ...defaultRules, ...customRules }}
|
props: {
|
||||||
renderInline={renderInline}
|
children: (
|
||||||
>{msg}
|
<Text
|
||||||
</EasyMarkdown>
|
key={state.key}
|
||||||
);
|
style={mentionStyle}
|
||||||
|
onPress={() => alert('Room')}
|
||||||
|
>
|
||||||
|
{node.content}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fence: {
|
||||||
|
order: -3,
|
||||||
|
match: SimpleMarkdown.blockRegex(/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/),
|
||||||
|
parse: capture => ({
|
||||||
|
lang: capture[2] || undefined,
|
||||||
|
content: capture[3]
|
||||||
|
}),
|
||||||
|
react: (node, output, state) => ({
|
||||||
|
type: 'custom',
|
||||||
|
key: state.key,
|
||||||
|
props: {
|
||||||
|
children: (
|
||||||
|
<BlockCode key={state.key} node={node} state={state} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
blockCode: {
|
||||||
|
order: -4,
|
||||||
|
match: SimpleMarkdown.blockRegex(/^(```)\s*([\s\S]*?[^`])\s*\1(?!```)/),
|
||||||
|
parse: capture => ({ content: capture[2] }),
|
||||||
|
react: (node, output, state) => ({
|
||||||
|
type: 'custom',
|
||||||
|
key: state.key,
|
||||||
|
props: {
|
||||||
|
children: (
|
||||||
|
<BlockCode key={state.key} node={node} state={state} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const codeStyle = StyleSheet.flatten(styles.codeStyle);
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
customEmojis: state.customEmojis
|
||||||
|
}))
|
||||||
|
export default class Markdown extends React.Component {
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
return nextProps.msg !== this.props.msg;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
msg, customEmojis = {}, style, markdownStyle, customRules, renderInline
|
||||||
|
} = this.props;
|
||||||
|
if (!msg) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const m = emojify(msg, { output: 'unicode' });
|
||||||
|
|
||||||
|
const s = StyleSheet.flatten(style);
|
||||||
|
return (
|
||||||
|
<EasyMarkdown
|
||||||
|
style={{ marginBottom: 0, ...s }}
|
||||||
|
markdownStyles={{ code: codeStyle, ...markdownStyle }}
|
||||||
|
rules={{
|
||||||
|
customEmoji: {
|
||||||
|
order: -5,
|
||||||
|
match: SimpleMarkdown.inlineRegex(/^:([0-9a-zA-Z-_.]+):/),
|
||||||
|
parse: capture => ({ content: capture }),
|
||||||
|
react: (node, output, state) => {
|
||||||
|
const element = {
|
||||||
|
type: 'custom',
|
||||||
|
key: state.key,
|
||||||
|
props: {
|
||||||
|
children: <Text key={state.key}>{node.content[0]}</Text>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const content = node.content[1];
|
||||||
|
const emojiExtension = customEmojis[content];
|
||||||
|
if (emojiExtension) {
|
||||||
|
const emoji = { extension: emojiExtension, content };
|
||||||
|
element.props.children = (
|
||||||
|
<CustomEmoji key={state.key} style={styles.customEmoji} emoji={emoji} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...defaultRules,
|
||||||
|
...customRules
|
||||||
|
}}
|
||||||
|
renderInline={renderInline}
|
||||||
|
>{m}
|
||||||
|
</EasyMarkdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Markdown.propTypes = {
|
Markdown.propTypes = {
|
||||||
msg: PropTypes.string.isRequired,
|
msg: PropTypes.string,
|
||||||
customEmojis: PropTypes.object,
|
customEmojis: PropTypes.object,
|
||||||
// eslint-disable-next-line react/no-typos
|
// eslint-disable-next-line react/no-typos
|
||||||
style: ViewPropTypes.style,
|
style: ViewPropTypes.style,
|
||||||
|
@ -149,5 +163,3 @@ BlockCode.propTypes = {
|
||||||
node: PropTypes.object,
|
node: PropTypes.object,
|
||||||
state: PropTypes.object
|
state: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Markdown;
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { View, Text, TouchableWithoutFeedback, FlatList, StyleSheet } from 'reac
|
||||||
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';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import Emoji from './Emoji';
|
import Emoji from './Emoji';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -52,6 +53,10 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
const standardEmojiStyle = { fontSize: 20 };
|
const standardEmojiStyle = { fontSize: 20 };
|
||||||
const customEmojiStyle = { width: 20, height: 20 };
|
const customEmojiStyle = { width: 20, height: 20 };
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
customEmojis: state.customEmojis
|
||||||
|
}))
|
||||||
export default class ReactionsModal extends React.PureComponent {
|
export default class ReactionsModal extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isVisible: PropTypes.bool.isRequired,
|
isVisible: PropTypes.bool.isRequired,
|
||||||
|
|
|
@ -7,7 +7,9 @@ import Avatar from '../Avatar';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
username: {
|
username: {
|
||||||
fontWeight: 'bold'
|
color: '#000',
|
||||||
|
fontWeight: '400',
|
||||||
|
fontSize: 14
|
||||||
},
|
},
|
||||||
usernameView: {
|
usernameView: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -22,7 +24,8 @@ const styles = StyleSheet.create({
|
||||||
time: {
|
time: {
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: '#888',
|
color: '#888',
|
||||||
paddingLeft: 5
|
paddingLeft: 5,
|
||||||
|
fontWeight: '400'
|
||||||
},
|
},
|
||||||
edited: {
|
edited: {
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
|
@ -35,11 +38,10 @@ export default class User extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
Message_TimeFormat: PropTypes.string.isRequired,
|
Message_TimeFormat: PropTypes.string.isRequired,
|
||||||
onPress: PropTypes.func,
|
onPress: PropTypes.func
|
||||||
baseUrl: PropTypes.string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEdited(item) {
|
renderEdited = (item) => {
|
||||||
if (!item.editedBy) {
|
if (!item.editedBy) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +52,6 @@ export default class User extends React.PureComponent {
|
||||||
style={{ marginLeft: 5 }}
|
style={{ marginLeft: 5 }}
|
||||||
text={item.editedBy.username}
|
text={item.editedBy.username}
|
||||||
size={20}
|
size={20}
|
||||||
baseUrl={this.props.baseUrl}
|
|
||||||
avatar={item.avatar}
|
avatar={item.avatar}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, StyleSheet, TouchableOpacity, Image, Platform } from 'react-native';
|
import { StyleSheet, TouchableOpacity, Image, Platform } 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 { connect } from 'react-redux';
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
|
|
||||||
|
@ -27,6 +28,9 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
|
}))
|
||||||
export default class Video extends React.PureComponent {
|
export default class Video extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
file: PropTypes.object.isRequired,
|
file: PropTypes.object.isRequired,
|
||||||
|
@ -55,18 +59,20 @@ export default class Video extends React.PureComponent {
|
||||||
const { baseUrl, user } = this.props;
|
const { baseUrl, user } = this.props;
|
||||||
const uri = `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
const uri = `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||||
return (
|
return (
|
||||||
<View>
|
[
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
key='button'
|
||||||
style={styles.container}
|
style={styles.container}
|
||||||
onPress={() => this.open()}
|
onPress={() => this.open()}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
source={require('../../../static/images/logo.png')}
|
source={require('../../static/images/logo.png')}
|
||||||
style={styles.image}
|
style={styles.image}
|
||||||
/>
|
/>
|
||||||
<Markdown msg={description} />
|
<Markdown msg={description} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>,
|
||||||
<Modal
|
<Modal
|
||||||
|
key='modal'
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
style={styles.modal}
|
style={styles.modal}
|
||||||
supportedOrientations={['portrait', 'landscape']}
|
supportedOrientations={['portrait', 'landscape']}
|
||||||
|
@ -78,7 +84,7 @@ export default class Video extends React.PureComponent {
|
||||||
disableVolume
|
disableVolume
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
</View>
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, TouchableHighlight, Text, TouchableOpacity, Vibration, ViewPropTypes } from 'react-native';
|
import { View, Text, TouchableOpacity, Vibration, ViewPropTypes } 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 moment from 'moment';
|
import moment from 'moment';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import { KeyboardUtils } from 'react-native-keyboard-input';
|
import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||||
|
|
||||||
import { actionsShow, errorActionsShow, toggleReactionPicker } from '../../actions/messages';
|
|
||||||
import Image from './Image';
|
import Image from './Image';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
|
@ -18,13 +17,54 @@ import Url from './Url';
|
||||||
import Reply from './Reply';
|
import Reply from './Reply';
|
||||||
import ReactionsModal from './ReactionsModal';
|
import ReactionsModal from './ReactionsModal';
|
||||||
import Emoji from './Emoji';
|
import Emoji from './Emoji';
|
||||||
import messageStatus from '../../constants/messagesStatus';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { actionsShow, errorActionsShow, toggleReactionPicker } from '../../actions/messages';
|
||||||
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
|
import Touch from '../../utils/touch';
|
||||||
|
|
||||||
|
const getInfoMessage = ({
|
||||||
|
t, role, msg, u
|
||||||
|
}) => {
|
||||||
|
if (t === 'rm') {
|
||||||
|
return 'Message removed';
|
||||||
|
} else if (t === 'uj') {
|
||||||
|
return 'Has joined the channel.';
|
||||||
|
} else if (t === 'r') {
|
||||||
|
return `Room name changed to: ${ msg } by ${ u.username }`;
|
||||||
|
} else if (t === 'message_pinned') {
|
||||||
|
return 'Message pinned';
|
||||||
|
} else if (t === 'ul') {
|
||||||
|
return 'Has left the channel.';
|
||||||
|
} else if (t === 'ru') {
|
||||||
|
return `User ${ msg } removed by ${ u.username }`;
|
||||||
|
} else if (t === 'au') {
|
||||||
|
return `User ${ msg } added by ${ u.username }`;
|
||||||
|
} else if (t === 'user-muted') {
|
||||||
|
return `User ${ msg } muted by ${ u.username }`;
|
||||||
|
} else if (t === 'user-unmuted') {
|
||||||
|
return `User ${ msg } unmuted by ${ u.username }`;
|
||||||
|
} else if (t === 'subscription-role-added') {
|
||||||
|
return `${ msg } was set ${ role } by ${ u.username }`;
|
||||||
|
} else if (t === 'subscription-role-removed') {
|
||||||
|
return `${ msg } is no longer ${ role } by ${ u.username }`;
|
||||||
|
} else if (t === 'room_changed_description') {
|
||||||
|
return `Room description changed to: ${ msg } by ${ u.username }`;
|
||||||
|
} else if (t === 'room_changed_announcement') {
|
||||||
|
return `Room announcement changed to: ${ msg } by ${ u.username }`;
|
||||||
|
} else if (t === 'room_changed_topic') {
|
||||||
|
return `Room topic changed to: ${ msg } by ${ u.username }`;
|
||||||
|
} else if (t === 'room_changed_privacy') {
|
||||||
|
return `Room type changed to: ${ msg } by ${ u.username }`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
message: state.messages.message,
|
message: state.messages.message,
|
||||||
editing: state.messages.editing,
|
editing: state.messages.editing,
|
||||||
customEmojis: state.customEmojis
|
customEmojis: state.customEmojis,
|
||||||
|
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||||
|
Message_GroupingPeriod: state.settings.Message_GroupingPeriod
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
|
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
|
||||||
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
|
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
|
||||||
|
@ -35,13 +75,13 @@ export default class Message extends React.Component {
|
||||||
status: PropTypes.any,
|
status: PropTypes.any,
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
reactions: PropTypes.any.isRequired,
|
reactions: PropTypes.any.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
|
||||||
Message_TimeFormat: PropTypes.string.isRequired,
|
Message_TimeFormat: PropTypes.string.isRequired,
|
||||||
|
Message_GroupingPeriod: PropTypes.number.isRequired,
|
||||||
|
customTimeFormat: PropTypes.string,
|
||||||
message: PropTypes.object.isRequired,
|
message: PropTypes.object.isRequired,
|
||||||
user: PropTypes.object.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
errorActionsShow: PropTypes.func,
|
errorActionsShow: PropTypes.func,
|
||||||
customEmojis: PropTypes.object,
|
|
||||||
toggleReactionPicker: PropTypes.func,
|
toggleReactionPicker: PropTypes.func,
|
||||||
onReactionPress: PropTypes.func,
|
onReactionPress: PropTypes.func,
|
||||||
style: ViewPropTypes.style,
|
style: ViewPropTypes.style,
|
||||||
|
@ -63,28 +103,35 @@ export default class Message extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
if (!equal(this.props.reactions, nextProps.reactions)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.state.reactionsModal !== nextState.reactionsModal) {
|
if (this.state.reactionsModal !== nextState.reactionsModal) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return this.props._updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString() || this.props.status !== nextProps.status;
|
if (this.props.status !== nextProps.status) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (!!this.props._updatedAt ^ !!nextProps._updatedAt) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!equal(this.props.reactions, nextProps.reactions)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.props._updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
KeyboardUtils.dismiss();
|
KeyboardUtils.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
onLongPress() {
|
onLongPress = () => {
|
||||||
this.props.onLongPress(this.parseMessage());
|
this.props.onLongPress(this.parseMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
onErrorPress() {
|
onErrorPress = () => {
|
||||||
this.props.errorActionsShow(this.parseMessage());
|
this.props.errorActionsShow(this.parseMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
onReactionPress(emoji) {
|
onReactionPress = (emoji) => {
|
||||||
this.props.onReactionPress(emoji, this.props.item._id);
|
this.props.onReactionPress(emoji, this.props.item._id);
|
||||||
}
|
}
|
||||||
onClose() {
|
onClose() {
|
||||||
|
@ -95,45 +142,9 @@ export default class Message extends React.Component {
|
||||||
Vibration.vibrate(50);
|
Vibration.vibrate(50);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInfoMessage() {
|
get timeFormat() {
|
||||||
let message = '';
|
const { customTimeFormat, Message_TimeFormat } = this.props;
|
||||||
const {
|
return customTimeFormat || Message_TimeFormat;
|
||||||
t, role, msg, u
|
|
||||||
} = this.props.item;
|
|
||||||
|
|
||||||
if (t === 'rm') {
|
|
||||||
message = 'Message removed';
|
|
||||||
} else if (t === 'uj') {
|
|
||||||
message = 'Has joined the channel.';
|
|
||||||
} else if (t === 'r') {
|
|
||||||
message = `Room name changed to: ${ msg } by ${ u.username }`;
|
|
||||||
} else if (t === 'message_pinned') {
|
|
||||||
message = 'Message pinned';
|
|
||||||
} else if (t === 'ul') {
|
|
||||||
message = 'Has left the channel.';
|
|
||||||
} else if (t === 'ru') {
|
|
||||||
message = `User ${ msg } removed by ${ u.username }`;
|
|
||||||
} else if (t === 'au') {
|
|
||||||
message = `User ${ msg } added by ${ u.username }`;
|
|
||||||
} else if (t === 'user-muted') {
|
|
||||||
message = `User ${ msg } muted by ${ u.username }`;
|
|
||||||
} else if (t === 'user-unmuted') {
|
|
||||||
message = `User ${ msg } unmuted by ${ u.username }`;
|
|
||||||
} else if (t === 'subscription-role-added') {
|
|
||||||
message = `${ msg } was set ${ role } by ${ u.username }`;
|
|
||||||
} else if (t === 'subscription-role-removed') {
|
|
||||||
message = `${ msg } is no longer ${ role } by ${ u.username }`;
|
|
||||||
} else if (t === 'room_changed_description') {
|
|
||||||
message = `Room description changed to: ${ msg } by ${ u.username }`;
|
|
||||||
} else if (t === 'room_changed_announcement') {
|
|
||||||
message = `Room announcement changed to: ${ msg } by ${ u.username }`;
|
|
||||||
} else if (t === 'room_changed_topic') {
|
|
||||||
message = `Room topic changed to: ${ msg } by ${ u.username }`;
|
|
||||||
} else if (t === 'room_changed_privacy') {
|
|
||||||
message = `Room type changed to: ${ msg } by ${ u.username }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
|
parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
|
||||||
|
@ -163,64 +174,97 @@ export default class Message extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
isTemp() {
|
isTemp() {
|
||||||
return this.props.item.status === messageStatus.TEMP || this.props.item.status === messageStatus.ERROR;
|
return this.props.item.status === messagesStatus.TEMP || this.props.item.status === messagesStatus.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasError() {
|
hasError() {
|
||||||
return this.props.item.status === messageStatus.ERROR;
|
return this.props.item.status === messagesStatus.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
attachments() {
|
renderHeader = (username) => {
|
||||||
|
const { item, previousItem } = this.props;
|
||||||
|
|
||||||
|
if (previousItem && (
|
||||||
|
(previousItem.ts.toDateString() === item.ts.toDateString()) &&
|
||||||
|
(previousItem.u.username === item.u.username) &&
|
||||||
|
!(previousItem.groupable === false || item.groupable === false) &&
|
||||||
|
(previousItem.status === item.status) &&
|
||||||
|
(item.ts - previousItem.ts < this.props.Message_GroupingPeriod * 1000)
|
||||||
|
)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.flex}>
|
||||||
|
<Avatar
|
||||||
|
style={styles.avatar}
|
||||||
|
text={item.avatar ? '' : username}
|
||||||
|
size={20}
|
||||||
|
avatar={item.avatar}
|
||||||
|
/>
|
||||||
|
<User
|
||||||
|
onPress={this._onPress}
|
||||||
|
item={item}
|
||||||
|
Message_TimeFormat={this.timeFormat}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent() {
|
||||||
|
if (this.isInfoMessage()) {
|
||||||
|
return <Text style={styles.textInfo}>{getInfoMessage(this.props.item)}</Text>;
|
||||||
|
}
|
||||||
|
const { item } = this.props;
|
||||||
|
return <Markdown msg={item.msg} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAttachment() {
|
||||||
if (this.props.item.attachments.length === 0) {
|
if (this.props.item.attachments.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = this.props.item.attachments[0];
|
const file = this.props.item.attachments[0];
|
||||||
const { baseUrl, user } = this.props;
|
const { user } = this.props;
|
||||||
if (file.image_type) {
|
if (file.image_type) {
|
||||||
return <Image file={file} baseUrl={baseUrl} user={user} />;
|
return <Image file={file} user={user} />;
|
||||||
} else if (file.audio_type) {
|
}
|
||||||
return <Audio file={file} baseUrl={baseUrl} user={user} />;
|
if (file.audio_type) {
|
||||||
} else if (file.video_type) {
|
return <Audio file={file} user={user} />;
|
||||||
return <Video file={file} baseUrl={baseUrl} user={user} />;
|
}
|
||||||
|
if (file.video_type) {
|
||||||
|
return <Video file={file} user={user} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Reply attachment={file} timeFormat={this.props.Message_TimeFormat} />;
|
return <Reply attachment={file} timeFormat={this.timeFormat} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMessageContent() {
|
renderUrl = () => {
|
||||||
if (this.isInfoMessage()) {
|
const { urls } = this.props.item;
|
||||||
return <Text style={styles.textInfo}>{this.getInfoMessage()}</Text>;
|
if (urls.length === 0) {
|
||||||
}
|
|
||||||
const { item, customEmojis, baseUrl } = this.props;
|
|
||||||
return <Markdown msg={item.msg} customEmojis={customEmojis} baseUrl={baseUrl} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderUrl() {
|
|
||||||
if (this.props.item.urls.length === 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.props.item.urls.map(url => (
|
return urls.map(url => (
|
||||||
<Url url={url} key={url.url} />
|
<Url url={url} key={url.url} />
|
||||||
));
|
));
|
||||||
}
|
};
|
||||||
|
|
||||||
renderError = () => {
|
renderError = () => {
|
||||||
if (!this.hasError()) {
|
if (!this.hasError()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={() => this.onErrorPress()}>
|
<TouchableOpacity onPress={this.onErrorPress}>
|
||||||
<Icon name='error-outline' color='red' size={20} style={{ padding: 10, paddingRight: 12, paddingLeft: 0 }} />
|
<Icon name='error-outline' color='red' size={20} style={styles.errorIcon} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderReaction(reaction) {
|
renderReaction = (reaction) => {
|
||||||
const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
|
const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
|
||||||
const reactedContainerStyle = reacted ? { borderColor: '#bde1fe', backgroundColor: '#f3f9ff' } : {};
|
const reactedContainerStyle = reacted && styles.reactedContainer;
|
||||||
const reactedCount = reacted ? { color: '#4fb0fc' } : {};
|
const reactedCount = reacted && styles.reactedCountText;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => this.onReactionPress(reaction.emoji)}
|
onPress={() => this.onReactionPress(reaction.emoji)}
|
||||||
|
@ -232,7 +276,6 @@ export default class Message extends React.Component {
|
||||||
content={reaction.emoji}
|
content={reaction.emoji}
|
||||||
standardEmojiStyle={styles.reactionEmoji}
|
standardEmojiStyle={styles.reactionEmoji}
|
||||||
customEmojiStyle={styles.reactionCustomEmoji}
|
customEmojiStyle={styles.reactionCustomEmoji}
|
||||||
customEmojis={this.props.customEmojis}
|
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.reactionCount, reactedCount]}>{ reaction.usernames.length }</Text>
|
<Text style={[styles.reactionCount, reactedCount]}>{ reaction.usernames.length }</Text>
|
||||||
</View>
|
</View>
|
||||||
|
@ -246,7 +289,7 @@ export default class Message extends React.Component {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={styles.reactionsContainer}>
|
<View style={styles.reactionsContainer}>
|
||||||
{this.props.item.reactions.map(reaction => this.renderReaction(reaction))}
|
{this.props.item.reactions.map(this.renderReaction)}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => this.props.toggleReactionPicker(this.parseMessage())}
|
onPress={() => this.props.toggleReactionPicker(this.parseMessage())}
|
||||||
key='add-reaction'
|
key='add-reaction'
|
||||||
|
@ -260,57 +303,42 @@ export default class Message extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
item, message, editing, baseUrl, customEmojis, style, archived
|
item, message, editing, style, archived
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const username = item.alias || item.u.username;
|
const username = item.alias || item.u.username;
|
||||||
const isEditing = message._id === item._id && editing;
|
const isEditing = message._id === item._id && editing;
|
||||||
const accessibilityLabel = `Message from ${ username } at ${ moment(item.ts).format(this.props.Message_TimeFormat) }, ${ this.props.item.msg }`;
|
const accessibilityLabel = `Message from ${ username } at ${ moment(item.ts).format(this.timeFormat) }, ${ this.props.item.msg }`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableHighlight
|
<Touch
|
||||||
onPress={() => this.onPress()}
|
onPress={this.onPress}
|
||||||
onLongPress={() => this.onLongPress()}
|
onLongPress={this.onLongPress}
|
||||||
disabled={this.isDeleted() || this.hasError() || archived}
|
disabled={this.isInfoMessage() || this.hasError() || archived}
|
||||||
underlayColor='#FFFFFF'
|
underlayColor='#FFFFFF'
|
||||||
activeOpacity={0.3}
|
activeOpacity={0.3}
|
||||||
style={[styles.message, isEditing ? styles.editing : null, style]}
|
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
>
|
>
|
||||||
<View style={styles.flex}>
|
<View style={[styles.message, isEditing && styles.editing, style]}>
|
||||||
{this.renderError()}
|
{this.renderHeader(username)}
|
||||||
<View style={[this.isTemp() && { opacity: 0.3 }, styles.flex]}>
|
<View style={styles.flex}>
|
||||||
<Avatar
|
{this.renderError()}
|
||||||
style={styles.avatar}
|
<View style={[styles.messageContent, this.isTemp() && styles.temp]}>
|
||||||
text={item.avatar ? '' : username}
|
{this.renderContent()}
|
||||||
size={40}
|
{this.renderAttachment()}
|
||||||
baseUrl={baseUrl}
|
|
||||||
avatar={item.avatar}
|
|
||||||
/>
|
|
||||||
<View style={[styles.content]}>
|
|
||||||
<User
|
|
||||||
onPress={this._onPress}
|
|
||||||
item={item}
|
|
||||||
Message_TimeFormat={this.props.Message_TimeFormat}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
/>
|
|
||||||
{this.renderMessageContent()}
|
|
||||||
{this.attachments()}
|
|
||||||
{this.renderUrl()}
|
{this.renderUrl()}
|
||||||
{this.renderReactions()}
|
{this.renderReactions()}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{this.state.reactionsModal ?
|
{this.state.reactionsModal &&
|
||||||
<ReactionsModal
|
<ReactionsModal
|
||||||
isVisible={this.state.reactionsModal}
|
isVisible={this.state.reactionsModal}
|
||||||
onClose={this.onClose}
|
onClose={this.onClose}
|
||||||
reactions={item.reactions}
|
reactions={item.reactions}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
customEmojis={customEmojis}
|
|
||||||
/>
|
/>
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
</TouchableHighlight>
|
</Touch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { StyleSheet, Platform } from 'react-native';
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
content: {
|
messageContent: {
|
||||||
flexGrow: 1,
|
flex: 1,
|
||||||
flexShrink: 1
|
marginLeft: 30
|
||||||
},
|
},
|
||||||
flex: {
|
flex: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
padding: 12,
|
paddingHorizontal: 12,
|
||||||
paddingTop: 6,
|
paddingVertical: 3,
|
||||||
paddingBottom: 6,
|
flexDirection: 'column',
|
||||||
flexDirection: 'row',
|
transform: [{ scaleY: -1 }],
|
||||||
transform: [{ scaleY: -1 }]
|
flex: 1
|
||||||
},
|
},
|
||||||
textInfo: {
|
textInfo: {
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
|
@ -27,6 +27,7 @@ export default StyleSheet.create({
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16
|
height: 16
|
||||||
},
|
},
|
||||||
|
temp: { opacity: 0.3 },
|
||||||
codeStyle: {
|
codeStyle: {
|
||||||
...Platform.select({
|
...Platform.select({
|
||||||
ios: { fontFamily: 'Courier New' },
|
ios: { fontFamily: 'Courier New' },
|
||||||
|
@ -40,7 +41,8 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
reactionsContainer: {
|
reactionsContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap'
|
flexWrap: 'wrap',
|
||||||
|
marginTop: 6
|
||||||
},
|
},
|
||||||
reactionContainer: {
|
reactionContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -70,5 +72,17 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
marginRight: 10
|
marginRight: 10
|
||||||
|
},
|
||||||
|
reactedContainer: {
|
||||||
|
borderColor: '#bde1fe',
|
||||||
|
backgroundColor: '#f3f9ff'
|
||||||
|
},
|
||||||
|
reactedCountText: {
|
||||||
|
color: '#4fb0fc'
|
||||||
|
},
|
||||||
|
errorIcon: {
|
||||||
|
padding: 10,
|
||||||
|
paddingRight: 12,
|
||||||
|
paddingLeft: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,12 +6,13 @@ import RoomsListView from '../../views/RoomsListView';
|
||||||
import RoomView from '../../views/RoomView';
|
import RoomView from '../../views/RoomView';
|
||||||
import RoomActionsView from '../../views/RoomActionsView';
|
import RoomActionsView from '../../views/RoomActionsView';
|
||||||
import CreateChannelView from '../../views/CreateChannelView';
|
import CreateChannelView from '../../views/CreateChannelView';
|
||||||
import SelectUsersView from '../../views/SelectUsersView';
|
import SelectedUsersView from '../../views/SelectedUsersView';
|
||||||
import NewServerView from '../../views/NewServerView';
|
import NewServerView from '../../views/NewServerView';
|
||||||
import StarredMessagesView from '../../views/StarredMessagesView';
|
import StarredMessagesView from '../../views/StarredMessagesView';
|
||||||
import PinnedMessagesView from '../../views/PinnedMessagesView';
|
import PinnedMessagesView from '../../views/PinnedMessagesView';
|
||||||
import MentionedMessagesView from '../../views/MentionedMessagesView';
|
import MentionedMessagesView from '../../views/MentionedMessagesView';
|
||||||
import SnippetedMessagesView from '../../views/SnippetedMessagesView';
|
import SnippetedMessagesView from '../../views/SnippetedMessagesView';
|
||||||
|
import SearchMessagesView from '../../views/SearchMessagesView';
|
||||||
import RoomFilesView from '../../views/RoomFilesView';
|
import RoomFilesView from '../../views/RoomFilesView';
|
||||||
import RoomMembersView from '../../views/RoomMembersView';
|
import RoomMembersView from '../../views/RoomMembersView';
|
||||||
import RoomInfoView from '../../views/RoomInfoView';
|
import RoomInfoView from '../../views/RoomInfoView';
|
||||||
|
@ -28,19 +29,22 @@ const AuthRoutes = StackNavigator(
|
||||||
CreateChannel: {
|
CreateChannel: {
|
||||||
screen: CreateChannelView,
|
screen: CreateChannelView,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
title: 'Create Channel'
|
title: 'Create Channel',
|
||||||
|
headerTintColor: '#292E35'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SelectUsers: {
|
SelectedUsers: {
|
||||||
screen: SelectUsersView,
|
screen: SelectedUsersView,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
title: 'Select Users'
|
title: 'Select Users',
|
||||||
|
headerTintColor: '#292E35'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AddServer: {
|
AddServer: {
|
||||||
screen: NewServerView,
|
screen: NewServerView,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
title: 'New server'
|
title: 'New server',
|
||||||
|
headerTintColor: '#292E35'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RoomActions: {
|
RoomActions: {
|
||||||
|
@ -78,6 +82,13 @@ const AuthRoutes = StackNavigator(
|
||||||
headerTintColor: '#292E35'
|
headerTintColor: '#292E35'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
SearchMessages: {
|
||||||
|
screen: SearchMessagesView,
|
||||||
|
navigationOptions: {
|
||||||
|
title: 'Search Messages',
|
||||||
|
headerTintColor: '#292E35'
|
||||||
|
}
|
||||||
|
},
|
||||||
RoomFiles: {
|
RoomFiles: {
|
||||||
screen: RoomFilesView,
|
screen: RoomFilesView,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
|
|
|
@ -48,6 +48,11 @@ export function goRoom({ rid, name }, counter = 0) {
|
||||||
NavigationActions.navigate({ key: `Room-${ rid }`, routeName: 'Room', params: { room: { rid, name }, rid, name } })
|
NavigationActions.navigate({ key: `Room-${ rid }`, routeName: 'Room', params: { room: { rid, name }, rid, name } })
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
config.navigator.dispatch(action);
|
config.navigator.dispatch(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dispatch(action) {
|
||||||
|
if (config.navigator) {
|
||||||
|
config.navigator.dispatch(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,74 +5,114 @@ import Icon from 'react-native-vector-icons/FontAwesome';
|
||||||
|
|
||||||
import ListServerView from '../../views/ListServerView';
|
import ListServerView from '../../views/ListServerView';
|
||||||
import NewServerView from '../../views/NewServerView';
|
import NewServerView from '../../views/NewServerView';
|
||||||
|
import LoginSignupView from '../../views/LoginSignupView';
|
||||||
import LoginView from '../../views/LoginView';
|
import LoginView from '../../views/LoginView';
|
||||||
import RegisterView from '../../views/RegisterView';
|
import RegisterView from '../../views/RegisterView';
|
||||||
|
|
||||||
import TermsServiceView from '../../views/TermsServiceView';
|
import TermsServiceView from '../../views/TermsServiceView';
|
||||||
import PrivacyPolicyView from '../../views/PrivacyPolicyView';
|
import PrivacyPolicyView from '../../views/PrivacyPolicyView';
|
||||||
import ForgotPasswordView from '../../views/ForgotPasswordView';
|
import ForgotPasswordView from '../../views/ForgotPasswordView';
|
||||||
|
import database from '../../lib/realm';
|
||||||
|
|
||||||
|
const hasServers = () => {
|
||||||
|
const db = database.databases.serversDB.objects('servers');
|
||||||
|
return db.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServerStack = StackNavigator({
|
||||||
|
ListServer: {
|
||||||
|
screen: ListServerView,
|
||||||
|
navigationOptions({ navigation }) {
|
||||||
|
return {
|
||||||
|
title: 'Servers',
|
||||||
|
headerRight: (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => navigation.navigate({ key: 'AddServer', routeName: 'AddServer' })}
|
||||||
|
style={{ width: 50, alignItems: 'center' }}
|
||||||
|
accessibilityLabel='Add server'
|
||||||
|
accessibilityTraits='button'
|
||||||
|
>
|
||||||
|
<Icon name='plus' size={16} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AddServer: {
|
||||||
|
screen: NewServerView,
|
||||||
|
navigationOptions: {
|
||||||
|
header: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LoginSignup: {
|
||||||
|
screen: LoginSignupView,
|
||||||
|
navigationOptions: {
|
||||||
|
header: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
headerMode: 'screen',
|
||||||
|
initialRouteName: hasServers() ? 'ListServer' : 'AddServer'
|
||||||
|
});
|
||||||
|
|
||||||
|
const LoginStack = StackNavigator({
|
||||||
|
Login: {
|
||||||
|
screen: LoginView,
|
||||||
|
navigationOptions: {
|
||||||
|
header: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ForgotPassword: {
|
||||||
|
screen: ForgotPasswordView,
|
||||||
|
navigationOptions: {
|
||||||
|
title: 'Forgot my password',
|
||||||
|
headerTintColor: '#292E35'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
headerMode: 'screen'
|
||||||
|
});
|
||||||
|
|
||||||
|
const RegisterStack = StackNavigator({
|
||||||
|
Register: {
|
||||||
|
screen: RegisterView,
|
||||||
|
navigationOptions: {
|
||||||
|
header: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TermsService: {
|
||||||
|
screen: TermsServiceView,
|
||||||
|
navigationOptions: {
|
||||||
|
title: 'Terms of service',
|
||||||
|
headerTintColor: '#292E35'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PrivacyPolicy: {
|
||||||
|
screen: PrivacyPolicyView,
|
||||||
|
navigationOptions: {
|
||||||
|
title: 'Privacy policy',
|
||||||
|
headerTintColor: '#292E35'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
headerMode: 'screen'
|
||||||
|
});
|
||||||
|
|
||||||
const PublicRoutes = StackNavigator(
|
const PublicRoutes = StackNavigator(
|
||||||
{
|
{
|
||||||
ListServer: {
|
Server: {
|
||||||
screen: ListServerView,
|
screen: ServerStack
|
||||||
navigationOptions({ navigation }) {
|
|
||||||
return {
|
|
||||||
title: 'Servers',
|
|
||||||
headerRight: (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => navigation.navigate({ key: 'AddServer', routeName: 'AddServer' })}
|
|
||||||
style={{ width: 50, alignItems: 'center' }}
|
|
||||||
accessibilityLabel='Add server'
|
|
||||||
accessibilityTraits='button'
|
|
||||||
>
|
|
||||||
<Icon name='plus' size={16} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AddServer: {
|
|
||||||
screen: NewServerView,
|
|
||||||
navigationOptions: {
|
|
||||||
title: 'New server'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Login: {
|
Login: {
|
||||||
screen: LoginView,
|
screen: LoginStack
|
||||||
navigationOptions: {
|
|
||||||
title: 'Login'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Register: {
|
Register: {
|
||||||
screen: RegisterView,
|
screen: RegisterStack
|
||||||
navigationOptions: {
|
|
||||||
title: 'Register'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
TermsService: {
|
|
||||||
screen: TermsServiceView,
|
|
||||||
navigationOptions: {
|
|
||||||
title: 'Terms of service'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
PrivacyPolicy: {
|
|
||||||
screen: PrivacyPolicyView,
|
|
||||||
navigationOptions: {
|
|
||||||
title: 'Privacy policy'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ForgotPassword: {
|
|
||||||
screen: ForgotPasswordView,
|
|
||||||
navigationOptions: {
|
|
||||||
title: 'Forgot my password'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
navigationOptions: {
|
mode: 'modal',
|
||||||
headerTitleAllowFontScaling: false
|
headerMode: 'none'
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { createStore as reduxCreateStore, applyMiddleware, compose } from 'redux';
|
import { createStore as reduxCreateStore, applyMiddleware, compose } from 'redux';
|
||||||
|
import Reactotron from 'reactotron-react-native' ; // eslint-disable-line
|
||||||
import createSagaMiddleware from 'redux-saga';
|
import createSagaMiddleware from 'redux-saga';
|
||||||
import logger from 'redux-logger';
|
|
||||||
import applyAppStateListener from 'redux-enhancer-react-native-appstate';
|
import applyAppStateListener from 'redux-enhancer-react-native-appstate';
|
||||||
import Reactotron from 'reactotron-react-native'; // eslint-disable-line
|
|
||||||
import reducers from '../reducers';
|
import reducers from '../reducers';
|
||||||
import sagas from '../sagas';
|
import sagas from '../sagas';
|
||||||
|
|
||||||
|
@ -20,8 +20,7 @@ if (__DEV__) {
|
||||||
enhancers = compose(
|
enhancers = compose(
|
||||||
applyAppStateListener(),
|
applyAppStateListener(),
|
||||||
applyMiddleware(reduxImmutableStateInvariant),
|
applyMiddleware(reduxImmutableStateInvariant),
|
||||||
applyMiddleware(sagaMiddleware),
|
applyMiddleware(sagaMiddleware)
|
||||||
applyMiddleware(logger)
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
sagaMiddleware = createSagaMiddleware();
|
sagaMiddleware = createSagaMiddleware();
|
||||||
|
|
236
app/lib/ddp.js
|
@ -1,4 +1,23 @@
|
||||||
import EJSON from 'ejson';
|
import EJSON from 'ejson';
|
||||||
|
import { Answers } from 'react-native-fabric';
|
||||||
|
import { AppState } from 'react-native';
|
||||||
|
import debounce from '../utils/debounce';
|
||||||
|
// import { AppState, NativeModules } from 'react-native';
|
||||||
|
// const { WebSocketModule, BlobManager } = NativeModules;
|
||||||
|
|
||||||
|
// class WS extends WebSocket {
|
||||||
|
// _close(code?: number, reason?: string): void {
|
||||||
|
// if (Platform.OS === 'android') {
|
||||||
|
// WebSocketModule.close(code, reason, this._socketId);
|
||||||
|
// } else {
|
||||||
|
// WebSocketModule.close(this._socketId);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (BlobManager.isAvailable && this._binaryType === 'blob') {
|
||||||
|
// BlobManager.removeWebSocketHandler(this._socketId);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
class EventEmitter {
|
class EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -9,6 +28,7 @@ class EventEmitter {
|
||||||
this.events[event] = [];
|
this.events[event] = [];
|
||||||
}
|
}
|
||||||
this.events[event].push(listener);
|
this.events[event].push(listener);
|
||||||
|
return listener;
|
||||||
}
|
}
|
||||||
removeListener(event, listener) {
|
removeListener(event, listener) {
|
||||||
if (typeof this.events[event] === 'object') {
|
if (typeof this.events[event] === 'object') {
|
||||||
|
@ -24,7 +44,8 @@ class EventEmitter {
|
||||||
try {
|
try {
|
||||||
listener.apply(this, args);
|
listener.apply(this, args);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
Answers.logCustom(e);
|
||||||
|
console.warn(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -34,72 +55,195 @@ class EventEmitter {
|
||||||
this.removeListener(event, g);
|
this.removeListener(event, g);
|
||||||
listener.apply(this, args);
|
listener.apply(this, args);
|
||||||
});
|
});
|
||||||
|
return listener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default class Socket extends EventEmitter {
|
export default class Socket extends EventEmitter {
|
||||||
constructor(url) {
|
constructor(url, login) {
|
||||||
super();
|
super();
|
||||||
this.url = url.replace(/^http/, 'ws');
|
this.state = 'active';
|
||||||
|
this.lastping = new Date();
|
||||||
|
this._login = login;
|
||||||
|
this.url = url;// .replace(/^http/, 'ws');
|
||||||
this.id = 0;
|
this.id = 0;
|
||||||
this.subscriptions = {};
|
this.subscriptions = {};
|
||||||
this._connect();
|
|
||||||
this.ddp = new EventEmitter();
|
this.ddp = new EventEmitter();
|
||||||
this.on('ping', () => this.send({ msg: 'pong' }));
|
this._logged = false;
|
||||||
|
const waitTimeout = () => setTimeout(async() => {
|
||||||
|
// this.connection.ping();
|
||||||
|
this.send({ msg: 'ping' });
|
||||||
|
this.timeout = setTimeout(() => this.reconnect(), 1000);
|
||||||
|
}, 40000);
|
||||||
|
const handlePing = () => {
|
||||||
|
this.lastping = new Date();
|
||||||
|
this.send({ msg: 'pong' }, true);
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
this.timeout = waitTimeout();
|
||||||
|
};
|
||||||
|
const handlePong = () => {
|
||||||
|
this.lastping = new Date();
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
this.timeout = waitTimeout();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
AppState.addEventListener('change', (nextAppState) => {
|
||||||
|
if (this.state && this.state.match(/inactive/) && nextAppState === 'active') {
|
||||||
|
try {
|
||||||
|
this.send({ msg: 'ping' }, true);
|
||||||
|
// this.connection.ping();
|
||||||
|
} catch (e) {
|
||||||
|
this.reconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.state && this.state.match(/background/) && nextAppState === 'active') {
|
||||||
|
this.emit('background');
|
||||||
|
}
|
||||||
|
this.state = nextAppState;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('pong', handlePong);
|
||||||
|
this.on('ping', handlePing);
|
||||||
|
|
||||||
this.on('result', data => this.ddp.emit(data.id, { id: data.id, result: data.result, error: data.error }));
|
this.on('result', data => this.ddp.emit(data.id, { id: data.id, result: data.result, error: data.error }));
|
||||||
this.on('ready', data => this.ddp.emit(data.subs[0], data));
|
this.on('ready', data => this.ddp.emit(data.subs[0], data));
|
||||||
|
// this.on('error', () => this.reconnect());
|
||||||
|
this.on('disconnected', debounce(() => this.reconnect(), 300));
|
||||||
|
this.on('logged', () => this._logged = true);
|
||||||
|
|
||||||
|
this.on('logged', () => {
|
||||||
|
Object.keys(this.subscriptions || {}).forEach((key) => {
|
||||||
|
const { name, params } = this.subscriptions[key];
|
||||||
|
this.subscriptions[key].unsubscribe();
|
||||||
|
this.subscribe(name, ...params);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.on('open', async() => {
|
||||||
|
this._logged = false;
|
||||||
|
this.send({ msg: 'connect', version: '1', support: ['1', 'pre2', 'pre1'] });
|
||||||
|
});
|
||||||
|
|
||||||
|
this._connect();
|
||||||
}
|
}
|
||||||
send(obj) {
|
check() {
|
||||||
|
if (!this.lastping) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((Math.abs(this.lastping.getTime() - new Date().getTime()) / 1000) > 50) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
async login(params) {
|
||||||
|
try {
|
||||||
|
this.emit('login', params);
|
||||||
|
const result = await this.call('login', params);
|
||||||
|
this._login = { resume: result.token, ...result };
|
||||||
|
this._logged = true;
|
||||||
|
this.emit('logged', result);
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
const error = { ...err };
|
||||||
|
if (/user not found/i.test(error.reason)) {
|
||||||
|
error.error = 1;
|
||||||
|
error.reason = 'User or Password incorrect';
|
||||||
|
error.message = 'User or Password incorrect';
|
||||||
|
}
|
||||||
|
this.emit('logginError', error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async send(obj, ignore) {
|
||||||
|
console.log('send');
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.id += 1;
|
this.id += 1;
|
||||||
const id = obj.id || `${ this.id }`;
|
const id = obj.id || `ddp-react-native-${ this.id }`;
|
||||||
|
// console.log('send', { ...obj, id });
|
||||||
this.connection.send(EJSON.stringify({ ...obj, id }));
|
this.connection.send(EJSON.stringify({ ...obj, id }));
|
||||||
this.ddp.once(id, data => (data.error ? reject(data.error) : resolve({ id, ...data })));
|
if (ignore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cancel = this.ddp.once('disconnected', reject);
|
||||||
|
this.ddp.once(id, (data) => {
|
||||||
|
// console.log(data);
|
||||||
|
this.ddp.removeListener(id, cancel);
|
||||||
|
return (data.error ? reject(data.error) : resolve({ id, ...data }));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
get status() {
|
||||||
|
return this.connection && this.connection.readyState === 1 && this.check() && !!this._logged;
|
||||||
|
}
|
||||||
|
_close() {
|
||||||
|
try {
|
||||||
|
// this.connection && this.connection.readyState > 1 && this.connection.close && this.connection.close(300, 'disconnect');
|
||||||
|
if (this.connection && this.connection.close) {
|
||||||
|
this.connection.close(300, 'disconnect');
|
||||||
|
delete this.connection;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
_connect() {
|
_connect() {
|
||||||
const connection = new WebSocket(`${ this.url }/websocket`);
|
return new Promise((resolve) => {
|
||||||
connection.onopen = () => {
|
this.lastping = new Date();
|
||||||
this.emit('open');
|
this._close();
|
||||||
this.send({ msg: 'connect', version: '1', support: ['1', 'pre2', 'pre1'] });
|
clearInterval(this.reconnect_timeout);
|
||||||
};
|
this.reconnect_timeout = setInterval(() => (!this.connection || this.connection.readyState > 1 || !this.check()) && this.reconnect(), 5000);
|
||||||
connection.onclose = e => this.emit('disconnected', e);
|
this.connection = new WebSocket(`${ this.url }/websocket`, null);
|
||||||
// connection.onerror = () => {
|
|
||||||
// // alert(error.type);
|
|
||||||
// // console.log(error);
|
|
||||||
// // console.log(`WebSocket Error ${ JSON.stringify({...error}) }`);
|
|
||||||
// };
|
|
||||||
|
|
||||||
connection.onmessage = (e) => {
|
this.connection.onopen = () => {
|
||||||
const data = EJSON.parse(e.data);
|
this.emit('open');
|
||||||
this.emit(data.msg, data);
|
resolve();
|
||||||
return data.collection && this.emit(data.collection, data);
|
this.ddp.emit('open');
|
||||||
};
|
return this._login && this.login(this._login);
|
||||||
// this.on('disconnected', e => alert(JSON.stringify(e)));
|
};
|
||||||
this.connection = connection;
|
this.connection.onclose = debounce((e) => { console.log('aer'); this.emit('disconnected', e); }, 300);
|
||||||
|
this.connection.onmessage = (e) => {
|
||||||
|
try {
|
||||||
|
// console.log('received', e.data, e.target.readyState);
|
||||||
|
const data = EJSON.parse(e.data);
|
||||||
|
this.emit(data.msg, data);
|
||||||
|
return data.collection && this.emit(data.collection, data);
|
||||||
|
} catch (err) {
|
||||||
|
Answers.logCustom('EJSON parse', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
logout() {
|
logout() {
|
||||||
|
this._login = null;
|
||||||
return this.call('logout').then(() => this.subscriptions = {});
|
return this.call('logout').then(() => this.subscriptions = {});
|
||||||
}
|
}
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.emit('disconnected_by_user');
|
this._close();
|
||||||
this.connection.close();
|
|
||||||
}
|
}
|
||||||
reconnect() {
|
async reconnect() {
|
||||||
this.disconnect();
|
if (this._timer) {
|
||||||
this.once('connected', () => {
|
return;
|
||||||
Object.keys(this.subscriptions).forEach((key) => {
|
}
|
||||||
const { name, params } = this.subscriptions[key];
|
delete this.connection;
|
||||||
this.subscriptions[key].unsubscribe();
|
this._logged = false;
|
||||||
this.subscribe(name, params);
|
|
||||||
});
|
this._timer = setTimeout(() => {
|
||||||
});
|
delete this._timer;
|
||||||
this._connect();
|
this._connect();
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
call(method, ...params) {
|
call(method, ...params) {
|
||||||
return this.send({
|
return this.send({
|
||||||
msg: 'method', method, params
|
msg: 'method', method, params
|
||||||
}).then(data => data.result || data.subs);
|
}).then(data => data.result || data.subs).catch((err) => {
|
||||||
|
Answers.logCustom('DDP call Error', err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
unsubscribe(id) {
|
unsubscribe(id) {
|
||||||
if (!this.subscriptions[id]) {
|
if (!this.subscriptions[id]) {
|
||||||
|
@ -109,19 +253,31 @@ export default class Socket extends EventEmitter {
|
||||||
return this.send({
|
return this.send({
|
||||||
msg: 'unsub',
|
msg: 'unsub',
|
||||||
id
|
id
|
||||||
}).then(data => data.result || data.subs);
|
}).then(data => data.result || data.subs).catch((err) => {
|
||||||
|
console.warn('unsubscribe', err);
|
||||||
|
Answers.logCustom('DDP unsubscribe Error', err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
subscribe(name, ...params) {
|
subscribe(name, ...params) {
|
||||||
|
console.log(name, params);
|
||||||
return this.send({
|
return this.send({
|
||||||
msg: 'sub', name, params
|
msg: 'sub', name, params
|
||||||
}).then(({ id }) => {
|
}).then(({ id }) => {
|
||||||
const args = {
|
const args = {
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
params,
|
params,
|
||||||
unsubscribe: () => this.unsubscribe(id)
|
unsubscribe: () => this.unsubscribe(id)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.subscriptions[id] = args;
|
this.subscriptions[id] = args;
|
||||||
|
// console.log(args);
|
||||||
return args;
|
return args;
|
||||||
|
}).catch((err) => {
|
||||||
|
console.warn('subscribe', err);
|
||||||
|
Answers.logCustom('DDP subscribe Error', err);
|
||||||
|
return Promise.reject(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
import reduxStore from '../createStore';
|
||||||
|
// import { get } from './helpers/rest';
|
||||||
|
|
||||||
|
import database from '../realm';
|
||||||
|
import * as actions from '../../actions';
|
||||||
|
|
||||||
|
const getLastMessage = () => {
|
||||||
|
const setting = database.objects('customEmojis').sorted('_updatedAt', true)[0];
|
||||||
|
return setting && setting._updatedAt;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default async function() {
|
||||||
|
const lastMessage = getLastMessage();
|
||||||
|
let emojis = await this.ddp.call('listEmojiCustom');
|
||||||
|
emojis = emojis.filter(emoji => !lastMessage || emoji._updatedAt > lastMessage);
|
||||||
|
emojis = this._prepareEmojis(emojis);
|
||||||
|
InteractionManager.runAfterInteractions(() => database.write(() => {
|
||||||
|
emojis.forEach(emoji => database.create('customEmojis', emoji, true));
|
||||||
|
}));
|
||||||
|
reduxStore.dispatch(actions.setCustomEmojis(this.parseEmojis(emojis)));
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
import reduxStore from '../createStore';
|
||||||
|
// import { get } from './helpers/rest';
|
||||||
|
|
||||||
|
import database from '../realm';
|
||||||
|
import * as actions from '../../actions';
|
||||||
|
|
||||||
|
const getLastMessage = () => {
|
||||||
|
const setting = database.objects('permissions').sorted('_updatedAt', true)[0];
|
||||||
|
return setting && setting._updatedAt;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default async function() {
|
||||||
|
const lastMessage = getLastMessage();
|
||||||
|
const result = await (!lastMessage ? this.ddp.call('permissions/get') : this.ddp.call('permissions/get', new Date(lastMessage)));
|
||||||
|
const permissions = this._preparePermissions(result.update || result);
|
||||||
|
console.log('getPermissions', permissions);
|
||||||
|
InteractionManager.runAfterInteractions(() => database.write(() =>
|
||||||
|
permissions.forEach(permission => database.create('permissions', permission, true))));
|
||||||
|
reduxStore.dispatch(actions.setAllPermissions(this.parsePermissions(permissions)));
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
// import { showToast } from '../../utils/info';
|
||||||
|
import { get } from './helpers/rest';
|
||||||
|
import mergeSubscriptionsRooms, { merge } from './helpers/mergeSubscriptionsRooms';
|
||||||
|
import database from '../realm';
|
||||||
|
|
||||||
|
const lastMessage = () => {
|
||||||
|
const message = database
|
||||||
|
.objects('subscriptions')
|
||||||
|
.sorted('roomUpdatedAt', true)[0];
|
||||||
|
return message && new Date(message.roomUpdatedAt);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoomRest = async function() {
|
||||||
|
const { ddp } = this;
|
||||||
|
const updatedSince = lastMessage();
|
||||||
|
const { token, id } = ddp._login;
|
||||||
|
const server = this.ddp.url.replace('ws', 'http');
|
||||||
|
const [subscriptions, rooms] = await Promise.all([get({ token, id, server }, 'subscriptions.get', { updatedSince }), get({ token, id, server }, 'rooms.get', { updatedSince })]);
|
||||||
|
return mergeSubscriptionsRooms(subscriptions, rooms);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoomDpp = async function() {
|
||||||
|
try {
|
||||||
|
const { ddp } = this;
|
||||||
|
const updatedSince = lastMessage();
|
||||||
|
const [subscriptions, rooms] = await Promise.all([ddp.call('subscriptions/get', updatedSince), ddp.call('rooms/get', updatedSince)]);
|
||||||
|
return mergeSubscriptionsRooms(subscriptions, rooms);
|
||||||
|
} catch (e) {
|
||||||
|
return getRoomRest.apply(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function() {
|
||||||
|
const { database: db } = database;
|
||||||
|
|
||||||
|
return new Promise(async(resolve) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const { subscriptions, rooms } = await (false && this.ddp.status ? getRoomDpp.apply(this) : getRoomRest.apply(this));
|
||||||
|
|
||||||
|
const data = rooms.map(room => ({ room, sub: database.objects('subscriptions').filtered('rid == $0', room._id) }));
|
||||||
|
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
db.write(() => {
|
||||||
|
subscriptions.forEach(subscription => db.create('subscriptions', subscription, true));
|
||||||
|
data.forEach(({ sub, room }) => sub[0] && merge(sub[0], room));
|
||||||
|
});
|
||||||
|
resolve(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
import reduxStore from '../createStore';
|
||||||
|
// import { get } from './helpers/rest';
|
||||||
|
|
||||||
|
import database from '../realm';
|
||||||
|
import * as actions from '../../actions';
|
||||||
|
|
||||||
|
const getLastMessage = () => {
|
||||||
|
const [setting] = database.objects('settings').sorted('_updatedAt', true);
|
||||||
|
return setting && setting._updatedAt;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function() {
|
||||||
|
const lastMessage = getLastMessage();
|
||||||
|
const result = await (!lastMessage ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastMessage)));
|
||||||
|
console.log('getSettings', lastMessage, result);
|
||||||
|
|
||||||
|
const filteredSettings = this._prepareSettings(this._filterSettings(result.update || result));
|
||||||
|
|
||||||
|
InteractionManager.runAfterInteractions(() =>
|
||||||
|
database.write(() =>
|
||||||
|
filteredSettings.forEach(setting => database.create('settings', setting, true))));
|
||||||
|
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import normalizeMessage from './normalizeMessage';
|
||||||
|
import messagesStatus from '../../../constants/messagesStatus';
|
||||||
|
|
||||||
|
export default (message) => {
|
||||||
|
message.status = messagesStatus.SENT;
|
||||||
|
return normalizeMessage(message);
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
import normalizeMessage from './normalizeMessage';
|
||||||
|
// TODO: delete and update
|
||||||
|
|
||||||
|
export const merge = (subscription, room) => {
|
||||||
|
subscription.muted = [];
|
||||||
|
if (room) {
|
||||||
|
subscription.roomUpdatedAt = room._updatedAt;
|
||||||
|
subscription.lastMessage = normalizeMessage(room.lastMessage);
|
||||||
|
subscription.ro = room.ro;
|
||||||
|
subscription.description = room.description;
|
||||||
|
subscription.topic = room.topic;
|
||||||
|
subscription.announcement = room.announcement;
|
||||||
|
subscription.reactWhenReadOnly = room.reactWhenReadOnly;
|
||||||
|
subscription.archived = room.archived;
|
||||||
|
subscription.joinCodeRequired = room.joinCodeRequired;
|
||||||
|
|
||||||
|
if (room.muted && room.muted.length) {
|
||||||
|
subscription.muted = room.muted.filter(role => role).map(role => ({ value: role }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (subscription.roles && subscription.roles.length) {
|
||||||
|
subscription.roles = subscription.roles.map(role => (role.value ? role : { value: role }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscription.mobilePushNotifications === 'nothing') {
|
||||||
|
subscription.notifications = true;
|
||||||
|
} else {
|
||||||
|
subscription.notifications = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.blocked = !!subscription.blocker;
|
||||||
|
return subscription;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (subscriptions = [], rooms = []) => {
|
||||||
|
if (subscriptions.update) {
|
||||||
|
subscriptions = subscriptions.update;
|
||||||
|
rooms = rooms.update;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
subscriptions: subscriptions.map((s) => {
|
||||||
|
const index = rooms.findIndex(({ _id }) => _id === s.rid);
|
||||||
|
if (index < 0) {
|
||||||
|
return merge(s);
|
||||||
|
}
|
||||||
|
const [room] = rooms.splice(index, 1);
|
||||||
|
return merge(s, room);
|
||||||
|
}),
|
||||||
|
rooms
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
import parseUrls from './parseUrls';
|
||||||
|
|
||||||
|
function normalizeAttachments(msg) {
|
||||||
|
if (typeof msg.attachments !== typeof [] || !msg.attachments || !msg.attachments.length) {
|
||||||
|
msg.attachments = [];
|
||||||
|
}
|
||||||
|
msg.attachments = msg.attachments.map((att) => {
|
||||||
|
att.fields = att.fields || [];
|
||||||
|
att = normalizeAttachments(att);
|
||||||
|
return att;
|
||||||
|
});
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (msg) => {
|
||||||
|
if (!msg) { return; }
|
||||||
|
msg = normalizeAttachments(msg);
|
||||||
|
msg.reactions = msg.reactions || [];
|
||||||
|
// TODO: api problems
|
||||||
|
if (Array.isArray(msg.reactions)) {
|
||||||
|
msg.reactions = msg.reactions.map((value, key) => ({ teste: 1, emoji: key, usernames: value.usernames.map(username => ({ value: username })) }));
|
||||||
|
} else {
|
||||||
|
msg.reactions = Object.keys(msg.reactions).map(key => ({ teste: 1, emoji: key, usernames: msg.reactions[key].usernames.map(username => ({ value: username })) }));
|
||||||
|
}
|
||||||
|
msg.urls = msg.urls ? parseUrls(msg.urls) : [];
|
||||||
|
msg._updatedAt = new Date();
|
||||||
|
// loadHistory returns msg.starred as object
|
||||||
|
// stream-room-msgs returns msg.starred as an array
|
||||||
|
msg.starred = msg.starred && (Array.isArray(msg.starred) ? msg.starred.length > 0 : !!msg.starred);
|
||||||
|
return msg;
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
export default urls => urls.filter(url => url.meta && !url.ignoreParse).map((url, index) => {
|
||||||
|
const tmp = {};
|
||||||
|
const { meta } = url;
|
||||||
|
tmp._id = index;
|
||||||
|
tmp.title = meta.ogTitle || meta.twitterTitle || meta.title || meta.pageTitle || meta.oembedTitle;
|
||||||
|
tmp.description = meta.ogDescription || meta.twitterDescription || meta.description || meta.oembedAuthorName;
|
||||||
|
let decodedOgImage;
|
||||||
|
if (meta.ogImage) {
|
||||||
|
decodedOgImage = meta.ogImage.replace(/&/g, '&');
|
||||||
|
}
|
||||||
|
tmp.image = decodedOgImage || meta.twitterImage || meta.oembedThumbnailUrl;
|
||||||
|
tmp.url = url.url;
|
||||||
|
return tmp;
|
||||||
|
});
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Answers } from 'react-native-fabric';
|
||||||
|
|
||||||
|
export default fn => (params) => {
|
||||||
|
try {
|
||||||
|
fn(params);
|
||||||
|
} catch (e) {
|
||||||
|
Answers.logCustom('erro', e);
|
||||||
|
if (__DEV__) {
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,40 @@
|
||||||
|
import toQuery from './toQuery';
|
||||||
|
|
||||||
|
|
||||||
|
const handleSuccess = (msg) => {
|
||||||
|
if (msg.success !== undefined && !msg.success) {
|
||||||
|
return Promise.reject(msg);
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const get = function({
|
||||||
|
token, id, server
|
||||||
|
}, method, params = {}) {
|
||||||
|
return fetch(`${ server }/api/v1/${ method }/?${ toQuery(params) }`, {
|
||||||
|
method: 'get',
|
||||||
|
headers: {
|
||||||
|
// 'Accept-Encoding': 'gzip',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Token': token,
|
||||||
|
'X-User-Id': id
|
||||||
|
}
|
||||||
|
}).then(response => response.json()).then(handleSuccess);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const post = function({
|
||||||
|
token, id, server
|
||||||
|
}, method, params = {}) {
|
||||||
|
return fetch(`${ server }/api/v1/${ method }`, {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
headers: {
|
||||||
|
// 'Accept-Encoding': 'gzip',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
'X-Auth-Token': token,
|
||||||
|
'X-User-Id': id
|
||||||
|
}
|
||||||
|
}).then(response => response.json()).then(handleSuccess);
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default function(obj) {
|
||||||
|
return Object.keys(obj).filter(p => obj[p] !== undefined && obj[p] !== null).map(p => `${ encodeURIComponent(p) }=${ encodeURIComponent(obj[p]) }`).join('&');
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
|
||||||
|
import { get } from './helpers/rest';
|
||||||
|
import buildMessage from './helpers/buildMessage';
|
||||||
|
import database from '../realm';
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: api fix
|
||||||
|
const types = {
|
||||||
|
c: 'channels', d: 'im', p: 'groups'
|
||||||
|
};
|
||||||
|
|
||||||
|
async function loadMessagesForRoomRest({ rid: roomId, latest, t }) {
|
||||||
|
const { token, id } = this.ddp._login;
|
||||||
|
const server = this.ddp.url.replace('ws', 'http');
|
||||||
|
const data = await get({ token, id, server }, `${ types[t] }.history`, { roomId, latest });
|
||||||
|
return data.messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMessagesForRoomDDP(...args) {
|
||||||
|
const [{ rid: roomId, latest }] = args;
|
||||||
|
try {
|
||||||
|
const data = await this.ddp.call('loadHistory', roomId, latest, 50);
|
||||||
|
if (!data || !data.messages.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return data.messages;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('loadMessagesForRoomDDP', e);
|
||||||
|
return loadMessagesForRoomRest.call(this, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// }
|
||||||
|
// if (cb) {
|
||||||
|
// cb({ end: data && data.messages.length < 20 });
|
||||||
|
// }
|
||||||
|
// return data.message;
|
||||||
|
// }, (err) => {
|
||||||
|
// if (err) {
|
||||||
|
// if (cb) {
|
||||||
|
// cb({ end: true });
|
||||||
|
// }
|
||||||
|
// return Promise.reject(err);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function loadMessagesForRoom(...args) {
|
||||||
|
console.log('aqui');
|
||||||
|
const { database: db } = database;
|
||||||
|
console.log('database', db);
|
||||||
|
|
||||||
|
return new Promise(async(resolve) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const data = (await (false && this.ddp.status ? loadMessagesForRoomDDP.call(this, ...args) : loadMessagesForRoomRest.call(this, ...args))).map(buildMessage);
|
||||||
|
if (data) {
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
try {
|
||||||
|
db.write(() => data.forEach(message => db.create('messages', message, true)));
|
||||||
|
resolve(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('loadMessagesForRoom', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return resolve([]);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
|
||||||
|
import { get } from './helpers/rest';
|
||||||
|
import buildMessage from './helpers/buildMessage';
|
||||||
|
import database from '../realm';
|
||||||
|
|
||||||
|
|
||||||
|
async function loadMissedMessagesRest({ rid: roomId, lastOpen: lastUpdate }) {
|
||||||
|
const { token, id } = this.ddp._login;
|
||||||
|
const server = this.ddp.url.replace('ws', 'http');
|
||||||
|
const { result } = await get({ token, id, server }, 'chat.syncMessages', { roomId, lastUpdate });
|
||||||
|
// TODO: api fix
|
||||||
|
return result.updated || result.messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMissedMessagesDDP(...args) {
|
||||||
|
const [{ rid, lastOpen: lastUpdate }] = args;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.ddp.call('messages/get', rid, { lastUpdate: new Date(lastUpdate) });
|
||||||
|
return data.updated || data.messages;
|
||||||
|
} catch (e) {
|
||||||
|
return loadMissedMessagesRest.call(this, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// }
|
||||||
|
// if (cb) {
|
||||||
|
// cb({ end: data && data.messages.length < 20 });
|
||||||
|
// }
|
||||||
|
// return data.message;
|
||||||
|
// }, (err) => {
|
||||||
|
// if (err) {
|
||||||
|
// if (cb) {
|
||||||
|
// cb({ end: true });
|
||||||
|
// }
|
||||||
|
// return Promise.reject(err);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function(...args) {
|
||||||
|
const { database: db } = database;
|
||||||
|
return new Promise(async(resolve) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const data = (await (false && this.ddp.status ? loadMissedMessagesDDP.call(this, ...args) : loadMissedMessagesRest.call(this, ...args)));
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
data.forEach(buildMessage);
|
||||||
|
return InteractionManager.runAfterInteractions(() => {
|
||||||
|
try {
|
||||||
|
db.write(() => data.forEach(message => db.create('messages', message, true)));
|
||||||
|
resolve(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('loadMessagesForRoom', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resolve([]);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { post } from './helpers/rest';
|
||||||
|
import database from '../realm';
|
||||||
|
|
||||||
|
const readMessagesREST = function readMessagesREST(rid) {
|
||||||
|
const { token, id } = this.ddp._login;
|
||||||
|
const server = this.ddp.url.replace('ws', 'http');
|
||||||
|
return post({ token, id, server }, 'subscriptions.read', { rid });
|
||||||
|
};
|
||||||
|
|
||||||
|
const readMessagesDDP = function readMessagesDDP(rid) {
|
||||||
|
try {
|
||||||
|
return this.ddp.call('readMessages', rid);
|
||||||
|
} catch (e) {
|
||||||
|
return readMessagesREST.call(this, rid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function readMessages(rid) {
|
||||||
|
const { database: db } = database;
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const data = await (false && this.ddp.status ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid));
|
||||||
|
const [subscription] = db.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
|
db.write(() => {
|
||||||
|
subscription.open = true;
|
||||||
|
subscription.alert = false;
|
||||||
|
subscription.unread = 0;
|
||||||
|
subscription.userMentions = 0;
|
||||||
|
subscription.groupMentions = 0;
|
||||||
|
subscription.ls = new Date();
|
||||||
|
subscription.lastOpen = new Date();
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import Random from 'react-native-meteor/lib/Random';
|
||||||
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
|
|
||||||
|
import buildMessage from '../methods/helpers/buildMessage';
|
||||||
|
import { post } from './helpers/rest';
|
||||||
|
import database from '../realm';
|
||||||
|
import reduxStore from '../createStore';
|
||||||
|
|
||||||
|
export const getMessage = (rid, msg = {}) => {
|
||||||
|
const _id = Random.id();
|
||||||
|
const message = {
|
||||||
|
_id,
|
||||||
|
rid,
|
||||||
|
msg,
|
||||||
|
ts: new Date(),
|
||||||
|
_updatedAt: new Date(),
|
||||||
|
status: messagesStatus.TEMP,
|
||||||
|
u: {
|
||||||
|
_id: reduxStore.getState().login.user.id || '1',
|
||||||
|
username: reduxStore.getState().login.user.username
|
||||||
|
}
|
||||||
|
};
|
||||||
|
database.write(() => {
|
||||||
|
database.create('messages', message, true);
|
||||||
|
});
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sendMessageByRest(message) {
|
||||||
|
const { token, id } = this.ddp._login;
|
||||||
|
const server = this.ddp.url.replace('ws', 'http');
|
||||||
|
const { _id, rid, msg } = message;
|
||||||
|
return post({ token, id, server }, 'chat.sendMessage', { message: { _id, rid, msg } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessageByDDP(message) {
|
||||||
|
const { _id, rid, msg } = message;
|
||||||
|
return this.ddp.call('sendMessage', { _id, rid, msg });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function _sendMessageCall(message) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const data = await (false && this.ddp.status ? sendMessageByDDP.call(this, message) : sendMessageByRest.call(this, message));
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
database.write(() => {
|
||||||
|
message.status = messagesStatus.ERROR;
|
||||||
|
database.create('messages', message, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function(rid, msg) {
|
||||||
|
const { database: db } = database;
|
||||||
|
try {
|
||||||
|
const message = getMessage(rid, msg);
|
||||||
|
const room = db.objects('subscriptions').filtered('rid == $0', rid);
|
||||||
|
|
||||||
|
db.write(() => {
|
||||||
|
room.lastMessage = message;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ret = await _sendMessageCall.call(this, message);
|
||||||
|
// TODO: maybe I have created a bug in the future here <3
|
||||||
|
db.write(() => {
|
||||||
|
db.create('messages', buildMessage({ ...message, ...ret }), true);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('sendMessage', e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
// import database from '../../realm';
|
||||||
|
// import reduxStore from '../../createStore';
|
||||||
|
// import normalizeMessage from '../helpers/normalizeMessage';
|
||||||
|
// import _buildMessage from '../helpers/buildMessage';
|
||||||
|
// import protectedFunction from '../helpers/protectedFunction';
|
||||||
|
|
||||||
|
const subscribe = (ddp, rid) => Promise.all([
|
||||||
|
ddp.subscribe('stream-room-messages', rid, false),
|
||||||
|
ddp.subscribe('stream-notify-room', `${ rid }/typing`, false)
|
||||||
|
]);
|
||||||
|
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(e => console.warn(e)));
|
||||||
|
|
||||||
|
let timer = null;
|
||||||
|
let promises;
|
||||||
|
let logged;
|
||||||
|
let disconnected;
|
||||||
|
|
||||||
|
const stop = (ddp) => {
|
||||||
|
if (promises) {
|
||||||
|
promises.then(unsubscribe);
|
||||||
|
promises = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ddp.removeListener('logged', logged);
|
||||||
|
ddp.removeListener('disconnected', disconnected);
|
||||||
|
|
||||||
|
logged = false;
|
||||||
|
disconnected = false;
|
||||||
|
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function subscribeRoom({ rid, t }) {
|
||||||
|
if (promises) {
|
||||||
|
promises.then(unsubscribe);
|
||||||
|
promises = false;
|
||||||
|
}
|
||||||
|
const loop = (time = new Date()) => {
|
||||||
|
if (timer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timer = setTimeout(async() => {
|
||||||
|
try {
|
||||||
|
await this.loadMissedMessages({ rid, t, lastOpen: timer });
|
||||||
|
timer = false;
|
||||||
|
loop();
|
||||||
|
} catch (e) {
|
||||||
|
loop(time);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
logged = this.ddp.on('logged', () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = false;
|
||||||
|
promises = subscribe(this.ddp, rid);
|
||||||
|
});
|
||||||
|
|
||||||
|
disconnected = this.ddp.on('disconnected', () => { loop(); });
|
||||||
|
|
||||||
|
if (!this.ddp.status) {
|
||||||
|
loop();
|
||||||
|
} else {
|
||||||
|
promises = subscribe(this.ddp, rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
stop: () => stop(this.ddp)
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import database from '../../realm';
|
||||||
|
import { merge } from '../helpers/mergeSubscriptionsRooms';
|
||||||
|
|
||||||
|
export default async function subscribeRooms(id) {
|
||||||
|
const subscriptions = Promise.all([
|
||||||
|
this.ddp.subscribe('stream-notify-user', `${ id }/subscriptions-changed`, false),
|
||||||
|
this.ddp.subscribe('stream-notify-user', `${ id }/rooms-changed`, false),
|
||||||
|
this.ddp.subscribe('stream-notify-user', `${ id }/message`, false)
|
||||||
|
]);
|
||||||
|
|
||||||
|
let timer = null;
|
||||||
|
const loop = (time = new Date()) => {
|
||||||
|
if (timer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timer = setTimeout(async() => {
|
||||||
|
try {
|
||||||
|
await this.getRooms(time);
|
||||||
|
timer = false;
|
||||||
|
loop();
|
||||||
|
} catch (e) {
|
||||||
|
loop(time);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ddp.on('logged', () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ddp.on('logout', () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ddp.on('disconnected', () => { loop(); });
|
||||||
|
|
||||||
|
this.ddp.on('stream-notify-user', (ddpMessage) => {
|
||||||
|
const [type, data] = ddpMessage.fields.args;
|
||||||
|
const [, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
|
if (/subscriptions/.test(ev)) {
|
||||||
|
const tpm = merge(data);
|
||||||
|
return database.write(() => {
|
||||||
|
database.create('subscriptions', tpm, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (/rooms/.test(ev) && type === 'updated') {
|
||||||
|
const [sub] = database.objects('subscriptions').filtered('rid == $0', data._id);
|
||||||
|
database.write(() => {
|
||||||
|
merge(sub, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await subscriptions;
|
||||||
|
console.log(this.ddp.subscriptions);
|
||||||
|
}
|
|
@ -51,6 +51,7 @@ const roomsSchema = {
|
||||||
_id: 'string',
|
_id: 'string',
|
||||||
t: 'string',
|
t: 'string',
|
||||||
lastMessage: 'messages',
|
lastMessage: 'messages',
|
||||||
|
description: { type: 'string', optional: true },
|
||||||
_updatedAt: { type: 'date', optional: true }
|
_updatedAt: { type: 'date', optional: true }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -63,6 +64,14 @@ const subscriptionRolesSchema = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const userMutedInRoomSchema = {
|
||||||
|
name: 'usersMuted',
|
||||||
|
primaryKey: 'value',
|
||||||
|
properties: {
|
||||||
|
value: 'string'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const subscriptionSchema = {
|
const subscriptionSchema = {
|
||||||
name: 'subscriptions',
|
name: 'subscriptions',
|
||||||
primaryKey: '_id',
|
primaryKey: '_id',
|
||||||
|
@ -90,7 +99,9 @@ const subscriptionSchema = {
|
||||||
blocked: { type: 'bool', optional: true },
|
blocked: { type: 'bool', optional: true },
|
||||||
reactWhenReadOnly: { type: 'bool', optional: true },
|
reactWhenReadOnly: { type: 'bool', optional: true },
|
||||||
archived: { type: 'bool', optional: true },
|
archived: { type: 'bool', optional: true },
|
||||||
joinCodeRequired: { type: 'bool', optional: true }
|
joinCodeRequired: { type: 'bool', optional: true },
|
||||||
|
notifications: { type: 'bool', optional: true },
|
||||||
|
muted: { type: 'list', objectType: 'usersMuted' }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,7 +148,9 @@ const attachment = {
|
||||||
color: { type: 'string', optional: true },
|
color: { type: 'string', optional: true },
|
||||||
ts: { type: 'date', optional: true },
|
ts: { type: 'date', optional: true },
|
||||||
attachments: { type: 'list', objectType: 'attachment' },
|
attachments: { type: 'list', objectType: 'attachment' },
|
||||||
fields: { type: 'list', objectType: 'attachmentFields' }
|
fields: {
|
||||||
|
type: 'list', objectType: 'attachmentFields', default: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -265,8 +278,48 @@ const schema = [
|
||||||
customEmojisSchema,
|
customEmojisSchema,
|
||||||
messagesReactionsSchema,
|
messagesReactionsSchema,
|
||||||
messagesReactionsUsernamesSchema,
|
messagesReactionsUsernamesSchema,
|
||||||
rolesSchema
|
rolesSchema,
|
||||||
|
userMutedInRoomSchema
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// class DebouncedDb {
|
||||||
|
// constructor(db) {
|
||||||
|
// this.database = db;
|
||||||
|
// }
|
||||||
|
// deleteAll(...args) {
|
||||||
|
// return this.database.write(() => this.database.deleteAll(...args));
|
||||||
|
// }
|
||||||
|
// delete(...args) {
|
||||||
|
// return this.database.delete(...args);
|
||||||
|
// }
|
||||||
|
// write(fn) {
|
||||||
|
// return fn();
|
||||||
|
// }
|
||||||
|
// create(...args) {
|
||||||
|
// this.queue = this.queue || [];
|
||||||
|
// if (this.timer) {
|
||||||
|
// clearTimeout(this.timer);
|
||||||
|
// this.timer = null;
|
||||||
|
// }
|
||||||
|
// this.timer = setTimeout(() => {
|
||||||
|
// alert(this.queue.length);
|
||||||
|
// this.database.write(() => {
|
||||||
|
// this.queue.forEach(({ db, args }) => this.database.create(...args));
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// this.timer = null;
|
||||||
|
// return this.roles = [];
|
||||||
|
// }, 1000);
|
||||||
|
//
|
||||||
|
// this.queue.push({
|
||||||
|
// db: this.database,
|
||||||
|
// args
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// objects(...args) {
|
||||||
|
// return this.database.objects(...args);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
class DB {
|
class DB {
|
||||||
databases = {
|
databases = {
|
||||||
serversDB: new Realm({
|
serversDB: new Realm({
|
||||||
|
@ -296,7 +349,7 @@ class DB {
|
||||||
return this.databases.activeDB;
|
return this.databases.activeDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveDB(database) {
|
setActiveDB(database = '') {
|
||||||
const path = database.replace(/(^\w+:|^)\/\//, '');
|
const path = database.replace(/(^\w+:|^)\/\//, '');
|
||||||
return this.databases.activeDB = new Realm({
|
return this.databases.activeDB = new Realm({
|
||||||
path: `${ path }.realm`,
|
path: `${ path }.realm`,
|
||||||
|
|
|
@ -1,47 +1,56 @@
|
||||||
import Random from 'react-native-meteor/lib/Random';
|
|
||||||
import { AsyncStorage, Platform } from 'react-native';
|
import { AsyncStorage, Platform } from 'react-native';
|
||||||
import { hashPassword } from 'react-native-meteor/lib/utils';
|
import { hashPassword } from 'react-native-meteor/lib/utils';
|
||||||
import _ from 'lodash';
|
import foreach from 'lodash/forEach';
|
||||||
|
import Random from 'react-native-meteor/lib/Random';
|
||||||
|
import { Answers } from 'react-native-fabric';
|
||||||
|
|
||||||
import RNFetchBlob from 'react-native-fetch-blob';
|
import RNFetchBlob from 'react-native-fetch-blob';
|
||||||
import reduxStore from './createStore';
|
import reduxStore from './createStore';
|
||||||
import settingsType from '../constants/settings';
|
import settingsType from '../constants/settings';
|
||||||
import messagesStatus from '../constants/messagesStatus';
|
import messagesStatus from '../constants/messagesStatus';
|
||||||
import database from './realm';
|
import database from './realm';
|
||||||
import * as actions from '../actions';
|
// import * as actions from '../actions';
|
||||||
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
|
||||||
import { setUser, setLoginServices, removeLoginServices } from '../actions/login';
|
import { setUser, setLoginServices, removeLoginServices, loginRequest, loginSuccess, loginFailure } from '../actions/login';
|
||||||
import { disconnect, disconnect_by_user, 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';
|
||||||
import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
|
import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
|
||||||
import { mentionedMessagesReceived } from '../actions/mentionedMessages';
|
import { mentionedMessagesReceived } from '../actions/mentionedMessages';
|
||||||
import { snippetedMessagesReceived } from '../actions/snippetedMessages';
|
import { snippetedMessagesReceived } from '../actions/snippetedMessages';
|
||||||
import { roomFilesReceived } from '../actions/roomFiles';
|
import { roomFilesReceived } from '../actions/roomFiles';
|
||||||
|
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
||||||
import { setRoles } from '../actions/roles';
|
import { setRoles } from '../actions/roles';
|
||||||
import Ddp from './ddp';
|
import Ddp from './ddp';
|
||||||
|
|
||||||
export { Accounts } from 'react-native-meteor';
|
import normalizeMessage from './methods/helpers/normalizeMessage';
|
||||||
|
|
||||||
|
import subscribeRooms from './methods/subscriptions/rooms';
|
||||||
|
import subscribeRoom from './methods/subscriptions/room';
|
||||||
|
|
||||||
|
import protectedFunction from './methods/helpers/protectedFunction';
|
||||||
|
import readMessages from './methods/readMessages';
|
||||||
|
import getSettings from './methods/getSettings';
|
||||||
|
|
||||||
|
import getRooms from './methods/getRooms';
|
||||||
|
import getPermissions from './methods/getPermissions';
|
||||||
|
import getCustomEmoji from './methods/getCustomEmojis';
|
||||||
|
|
||||||
|
|
||||||
|
import _buildMessage from './methods/helpers/buildMessage';
|
||||||
|
import loadMessagesForRoom from './methods/loadMessagesForRoom';
|
||||||
|
import loadMissedMessages from './methods/loadMissedMessages';
|
||||||
|
|
||||||
|
import sendMessage, { getMessage, _sendMessageCall } from './methods/sendMessage';
|
||||||
|
|
||||||
const call = (method, ...params) => RocketChat.ddp.call(method, ...params); // eslint-disable-line
|
|
||||||
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||||
const SERVER_TIMEOUT = 30000;
|
const call = (method, ...params) => RocketChat.ddp.call(method, ...params); // eslint-disable-line
|
||||||
|
|
||||||
const returnAnArray = obj => obj || [];
|
const returnAnArray = obj => obj || [];
|
||||||
|
|
||||||
const normalizeMessage = (lastMessage) => {
|
|
||||||
if (lastMessage) {
|
|
||||||
lastMessage.attachments = lastMessage.attachments || [];
|
|
||||||
lastMessage.reactions = _.map(lastMessage.reactions, (value, key) =>
|
|
||||||
({ emoji: key, usernames: value.usernames.map(username => ({ value: username })) }));
|
|
||||||
}
|
|
||||||
return lastMessage;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const RocketChat = {
|
const RocketChat = {
|
||||||
TOKEN_KEY,
|
TOKEN_KEY,
|
||||||
|
subscribeRooms,
|
||||||
|
subscribeRoom,
|
||||||
createChannel({ name, users, type }) {
|
createChannel({ name, users, type }) {
|
||||||
return call(type ? 'createChannel' : 'createPrivateGroup', name, users, type);
|
return call(type ? 'createChannel' : 'createPrivateGroup', name, users, type);
|
||||||
},
|
},
|
||||||
|
@ -97,59 +106,78 @@ const RocketChat = {
|
||||||
reduxStore.dispatch(setActiveUser(this.activeUsers));
|
reduxStore.dispatch(setActiveUser(this.activeUsers));
|
||||||
this._setUserTimer = null;
|
this._setUserTimer = null;
|
||||||
return this.activeUsers = {};
|
return this.activeUsers = {};
|
||||||
}, 3000);
|
}, 1000);
|
||||||
|
|
||||||
this.activeUsers[ddpMessage.id] = ddpMessage.fields;
|
this.activeUsers[ddpMessage.id] = ddpMessage.fields;
|
||||||
},
|
},
|
||||||
reconnect() {
|
async loginSuccess(user) {
|
||||||
if (this.ddp) {
|
if (!user) {
|
||||||
this.ddp.reconnect();
|
const { user: u } = reduxStore.getState().login;
|
||||||
|
user = Object.assign({}, u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: one api call
|
||||||
|
// call /me only one time
|
||||||
|
if (!user.username) {
|
||||||
|
const me = await this.me({ token: user.token, userId: user.id });
|
||||||
|
// eslint-disable-next-line
|
||||||
|
user.username = me.username;
|
||||||
|
}
|
||||||
|
if (user.username) {
|
||||||
|
const userInfo = await this.userInfo({ token: user.token, userId: user.id });
|
||||||
|
user.username = userInfo.user.username;
|
||||||
|
if (userInfo.user.roles) {
|
||||||
|
user.roles = userInfo.user.roles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reduxStore.dispatch(loginSuccess(user));
|
||||||
},
|
},
|
||||||
connect(url) {
|
connect(url, login) {
|
||||||
if (this.ddp) {
|
|
||||||
this.ddp.disconnect();
|
|
||||||
}
|
|
||||||
this.ddp = new Ddp(url);
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.ddp.on('disconnected_by_user', () => {
|
if (this.ddp) {
|
||||||
reduxStore.dispatch(disconnect_by_user());
|
this.ddp.disconnect();
|
||||||
});
|
delete this.ddp;
|
||||||
this.ddp.on('disconnected', () => {
|
}
|
||||||
|
|
||||||
|
this.ddp = new Ddp(url, login);
|
||||||
|
if (login) {
|
||||||
|
protectedFunction(() => RocketChat.getRooms());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ddp.on('login', protectedFunction(() => reduxStore.dispatch(loginRequest())));
|
||||||
|
|
||||||
|
this.ddp.on('logginError', protectedFunction(err => reduxStore.dispatch(loginFailure(err))));
|
||||||
|
|
||||||
|
this.ddp.on('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
|
||||||
|
|
||||||
|
this.ddp.on('background', () => this.getRooms().catch(e => console.warn('background getRooms', e)));
|
||||||
|
|
||||||
|
this.ddp.on('disconnected', () => console.log('disconnected'));
|
||||||
|
|
||||||
|
this.ddp.on('logged', protectedFunction((user) => {
|
||||||
|
this.getRooms().catch(e => console.warn('logged getRooms', e));
|
||||||
|
this.loginSuccess(user);
|
||||||
|
}));
|
||||||
|
this.ddp.once('logged', protectedFunction(({ id }) => { this.subscribeRooms(id); }));
|
||||||
|
|
||||||
|
this.ddp.on('disconnected', protectedFunction(() => {
|
||||||
reduxStore.dispatch(disconnect());
|
reduxStore.dispatch(disconnect());
|
||||||
});
|
}));
|
||||||
// this.ddp.on('open', async() => {
|
|
||||||
// resolve(reduxStore.dispatch(connectSuccess()));
|
|
||||||
// });
|
|
||||||
this.ddp.on('connected', () => {
|
|
||||||
resolve(reduxStore.dispatch(connectSuccess()));
|
|
||||||
RocketChat.getSettings();
|
|
||||||
RocketChat.getPermissions();
|
|
||||||
RocketChat.getCustomEmoji();
|
|
||||||
this.ddp.subscribe('activeUsers');
|
|
||||||
this.ddp.subscribe('roles');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ddp.on('error', (err) => {
|
|
||||||
alert(JSON.stringify(err));
|
|
||||||
reduxStore.dispatch(connectFailure());
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ddp.on('users', ddpMessage => RocketChat._setUser(ddpMessage));
|
|
||||||
|
|
||||||
this.ddp.on('stream-room-messages', (ddpMessage) => {
|
this.ddp.on('stream-room-messages', (ddpMessage) => {
|
||||||
const message = this._buildMessage(ddpMessage.fields.args[0]);
|
const message = _buildMessage(ddpMessage.fields.args[0]);
|
||||||
return reduxStore.dispatch(roomMessageReceived(message));
|
requestAnimationFrame(() => reduxStore.dispatch(roomMessageReceived(message)));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ddp.on('stream-notify-room', (ddpMessage) => {
|
this.ddp.on('stream-notify-room', protectedFunction((ddpMessage) => {
|
||||||
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
if (ev !== 'typing') {
|
if (ev !== 'typing') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
|
return reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.ddp.on('stream-notify-user', (ddpMessage) => {
|
this.ddp.on('stream-notify-user', protectedFunction((ddpMessage) => {
|
||||||
const [type, data] = ddpMessage.fields.args;
|
const [type, data] = ddpMessage.fields.args;
|
||||||
const [, ev] = ddpMessage.fields.eventName.split('/');
|
const [, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
if (/subscriptions/.test(ev)) {
|
if (/subscriptions/.test(ev)) {
|
||||||
|
@ -161,6 +189,11 @@ const RocketChat = {
|
||||||
} else {
|
} else {
|
||||||
data.blocked = false;
|
data.blocked = false;
|
||||||
}
|
}
|
||||||
|
if (data.mobilePushNotifications === 'nothing') {
|
||||||
|
data.notifications = true;
|
||||||
|
} else {
|
||||||
|
data.notifications = false;
|
||||||
|
}
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
database.create('subscriptions', data, true);
|
database.create('subscriptions', data, true);
|
||||||
});
|
});
|
||||||
|
@ -178,11 +211,33 @@ const RocketChat = {
|
||||||
sub.reactWhenReadOnly = data.reactWhenReadOnly;
|
sub.reactWhenReadOnly = data.reactWhenReadOnly;
|
||||||
sub.archived = data.archived;
|
sub.archived = data.archived;
|
||||||
sub.joinCodeRequired = data.joinCodeRequired;
|
sub.joinCodeRequired = data.joinCodeRequired;
|
||||||
|
if (data.muted) {
|
||||||
|
sub.muted = data.muted.map(m => ({ value: m }));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
if (/message/.test(ev)) {
|
||||||
|
const [args] = ddpMessage.fields.args;
|
||||||
|
const _id = Random.id();
|
||||||
|
const message = {
|
||||||
|
_id,
|
||||||
|
rid: args.rid,
|
||||||
|
msg: args.msg,
|
||||||
|
ts: new Date(),
|
||||||
|
_updatedAt: new Date(),
|
||||||
|
status: messagesStatus.SENT,
|
||||||
|
u: {
|
||||||
|
_id,
|
||||||
|
username: 'rocket.cat'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
requestAnimationFrame(() => database.write(() => {
|
||||||
|
database.create('messages', message, true);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
this.ddp.on('rocketchat_starred_message', (ddpMessage) => {
|
this.ddp.on('rocketchat_starred_message', protectedFunction((ddpMessage) => {
|
||||||
if (ddpMessage.msg === 'added') {
|
if (ddpMessage.msg === 'added') {
|
||||||
this.starredMessages = this.starredMessages || [];
|
this.starredMessages = this.starredMessages || [];
|
||||||
|
|
||||||
|
@ -191,14 +246,14 @@ const RocketChat = {
|
||||||
this.starredMessagesTimer = null;
|
this.starredMessagesTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.starredMessagesTimer = setTimeout(() => {
|
this.starredMessagesTimer = setTimeout(protectedFunction(() => {
|
||||||
reduxStore.dispatch(starredMessagesReceived(this.starredMessages));
|
reduxStore.dispatch(starredMessagesReceived(this.starredMessages));
|
||||||
this.starredMessagesTimer = null;
|
this.starredMessagesTimer = null;
|
||||||
return this.starredMessages = [];
|
return this.starredMessages = [];
|
||||||
}, 1000);
|
}), 1000);
|
||||||
const message = ddpMessage.fields;
|
const message = ddpMessage.fields;
|
||||||
message._id = ddpMessage.id;
|
message._id = ddpMessage.id;
|
||||||
const starredMessage = this._buildMessage(message);
|
const starredMessage = _buildMessage(message);
|
||||||
this.starredMessages = [...this.starredMessages, starredMessage];
|
this.starredMessages = [...this.starredMessages, starredMessage];
|
||||||
}
|
}
|
||||||
if (ddpMessage.msg === 'removed') {
|
if (ddpMessage.msg === 'removed') {
|
||||||
|
@ -206,9 +261,9 @@ const RocketChat = {
|
||||||
return reduxStore.dispatch(starredMessageUnstarred(ddpMessage.id));
|
return reduxStore.dispatch(starredMessageUnstarred(ddpMessage.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.ddp.on('rocketchat_pinned_message', (ddpMessage) => {
|
this.ddp.on('rocketchat_pinned_message', protectedFunction((ddpMessage) => {
|
||||||
if (ddpMessage.msg === 'added') {
|
if (ddpMessage.msg === 'added') {
|
||||||
this.pinnedMessages = this.pinnedMessages || [];
|
this.pinnedMessages = this.pinnedMessages || [];
|
||||||
|
|
||||||
|
@ -224,7 +279,7 @@ const RocketChat = {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
const message = ddpMessage.fields;
|
const message = ddpMessage.fields;
|
||||||
message._id = ddpMessage.id;
|
message._id = ddpMessage.id;
|
||||||
const pinnedMessage = this._buildMessage(message);
|
const pinnedMessage = _buildMessage(message);
|
||||||
this.pinnedMessages = [...this.pinnedMessages, pinnedMessage];
|
this.pinnedMessages = [...this.pinnedMessages, pinnedMessage];
|
||||||
}
|
}
|
||||||
if (ddpMessage.msg === 'removed') {
|
if (ddpMessage.msg === 'removed') {
|
||||||
|
@ -232,9 +287,9 @@ const RocketChat = {
|
||||||
return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id));
|
return reduxStore.dispatch(pinnedMessageUnpinned(ddpMessage.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.ddp.on('rocketchat_mentioned_message', (ddpMessage) => {
|
this.ddp.on('rocketchat_mentioned_message', protectedFunction((ddpMessage) => {
|
||||||
if (ddpMessage.msg === 'added') {
|
if (ddpMessage.msg === 'added') {
|
||||||
this.mentionedMessages = this.mentionedMessages || [];
|
this.mentionedMessages = this.mentionedMessages || [];
|
||||||
|
|
||||||
|
@ -250,12 +305,12 @@ const RocketChat = {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
const message = ddpMessage.fields;
|
const message = ddpMessage.fields;
|
||||||
message._id = ddpMessage.id;
|
message._id = ddpMessage.id;
|
||||||
const mentionedMessage = this._buildMessage(message);
|
const mentionedMessage = _buildMessage(message);
|
||||||
this.mentionedMessages = [...this.mentionedMessages, mentionedMessage];
|
this.mentionedMessages = [...this.mentionedMessages, mentionedMessage];
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.ddp.on('rocketchat_snippeted_message', (ddpMessage) => {
|
this.ddp.on('rocketchat_snippeted_message', protectedFunction((ddpMessage) => {
|
||||||
if (ddpMessage.msg === 'added') {
|
if (ddpMessage.msg === 'added') {
|
||||||
this.snippetedMessages = this.snippetedMessages || [];
|
this.snippetedMessages = this.snippetedMessages || [];
|
||||||
|
|
||||||
|
@ -271,12 +326,12 @@ const RocketChat = {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
const message = ddpMessage.fields;
|
const message = ddpMessage.fields;
|
||||||
message._id = ddpMessage.id;
|
message._id = ddpMessage.id;
|
||||||
const snippetedMessage = this._buildMessage(message);
|
const snippetedMessage = _buildMessage(message);
|
||||||
this.snippetedMessages = [...this.snippetedMessages, snippetedMessage];
|
this.snippetedMessages = [...this.snippetedMessages, snippetedMessage];
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.ddp.on('room_files', (ddpMessage) => {
|
this.ddp.on('room_files', protectedFunction((ddpMessage) => {
|
||||||
if (ddpMessage.msg === 'added') {
|
if (ddpMessage.msg === 'added') {
|
||||||
this.roomFiles = this.roomFiles || [];
|
this.roomFiles = this.roomFiles || [];
|
||||||
|
|
||||||
|
@ -318,9 +373,9 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
this.roomFiles = [...this.roomFiles, message];
|
this.roomFiles = [...this.roomFiles, message];
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.ddp.on('meteor_accounts_loginServiceConfiguration', (ddpMessage) => {
|
this.ddp.on('meteor_accounts_loginServiceConfiguration', protectedFunction((ddpMessage) => {
|
||||||
if (ddpMessage.msg === 'added') {
|
if (ddpMessage.msg === 'added') {
|
||||||
this.loginServices = this.loginServices || {};
|
this.loginServices = this.loginServices || {};
|
||||||
if (this.loginServiceTimer) {
|
if (this.loginServiceTimer) {
|
||||||
|
@ -340,9 +395,9 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
this.loginServiceTimer = setTimeout(() => reduxStore.dispatch(removeLoginServices()), 1000);
|
this.loginServiceTimer = setTimeout(() => reduxStore.dispatch(removeLoginServices()), 1000);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.ddp.on('rocketchat_roles', (ddpMessage) => {
|
this.ddp.on('rocketchat_roles', protectedFunction((ddpMessage) => {
|
||||||
this.roles = this.roles || {};
|
this.roles = this.roles || {};
|
||||||
|
|
||||||
if (this.roleTimer) {
|
if (this.roleTimer) {
|
||||||
|
@ -353,39 +408,38 @@ const RocketChat = {
|
||||||
reduxStore.dispatch(setRoles(this.roles));
|
reduxStore.dispatch(setRoles(this.roles));
|
||||||
|
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
_.forEach(this.roles, (description, _id) => {
|
foreach(this.roles, (description, _id) => {
|
||||||
database.create('roles', { _id, description }, true);
|
database.create('roles', { _id, description }, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.roleTimer = null;
|
this.roleTimer = null;
|
||||||
return this.roles = {};
|
return this.roles = {};
|
||||||
}, 5000);
|
}, 1000);
|
||||||
this.roles[ddpMessage.id] = ddpMessage.fields.description;
|
this.roles[ddpMessage.id] = (ddpMessage.fields && ddpMessage.fields.description) || undefined;
|
||||||
});
|
}));
|
||||||
}).catch(console.log);
|
|
||||||
},
|
|
||||||
|
|
||||||
me({ server, token, userId }) {
|
this.ddp.on('error', protectedFunction((err) => {
|
||||||
return fetch(`${ server }/api/v1/me`, {
|
console.warn('onError', JSON.stringify(err));
|
||||||
method: 'get',
|
Answers.logCustom('disconnect', err);
|
||||||
headers: {
|
reduxStore.dispatch(connectFailure());
|
||||||
'Content-Type': 'application/json',
|
}));
|
||||||
'X-Auth-Token': token,
|
|
||||||
'X-User-Id': userId
|
|
||||||
}
|
|
||||||
}).then(response => response.json());
|
|
||||||
},
|
|
||||||
|
|
||||||
userInfo({ server, token, userId }) {
|
// TODO: fix api (get emojis by date/version....)
|
||||||
return fetch(`${ server }/api/v1/users.info?userId=${ userId }`, {
|
|
||||||
method: 'get',
|
this.ddp.on('open', protectedFunction(() => {
|
||||||
headers: {
|
RocketChat.getSettings();
|
||||||
'Content-Type': 'application/json',
|
RocketChat.getPermissions();
|
||||||
'X-Auth-Token': token,
|
reduxStore.dispatch(connectSuccess());
|
||||||
'X-User-Id': userId
|
resolve();
|
||||||
}
|
}));
|
||||||
}).then(response => response.json());
|
|
||||||
|
this.ddp.once('open', protectedFunction(() => {
|
||||||
|
this.ddp.subscribe('activeUsers');
|
||||||
|
this.ddp.subscribe('roles');
|
||||||
|
RocketChat.getCustomEmoji();
|
||||||
|
}));
|
||||||
|
}).catch(err => console.warn(`asd ${ err }`));
|
||||||
},
|
},
|
||||||
|
|
||||||
register({ credentials }) {
|
register({ credentials }) {
|
||||||
|
@ -442,19 +496,18 @@ const RocketChat = {
|
||||||
return this.login(params, callback);
|
return this.login(params, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
loadSubscriptions(cb) {
|
login(params) {
|
||||||
this.ddp.call('subscriptions/get').then((data) => {
|
return this.ddp.login(params);
|
||||||
if (data.length) {
|
|
||||||
database.write(() => {
|
|
||||||
data.forEach((subscription) => {
|
|
||||||
database.create('subscriptions', subscription, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return cb && cb();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
logout({ server }) {
|
||||||
|
if (this.ddp) {
|
||||||
|
this.ddp.logout();
|
||||||
|
}
|
||||||
|
database.deleteAll();
|
||||||
|
AsyncStorage.removeItem(TOKEN_KEY);
|
||||||
|
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`);
|
||||||
|
},
|
||||||
|
|
||||||
registerPushToken(id, token) {
|
registerPushToken(id, token) {
|
||||||
const key = Platform.OS === 'ios' ? 'apn' : 'gcm';
|
const key = Platform.OS === 'ios' ? 'apn' : 'gcm';
|
||||||
const data = {
|
const data = {
|
||||||
|
@ -470,92 +523,32 @@ const RocketChat = {
|
||||||
updatePushToken(pushId) {
|
updatePushToken(pushId) {
|
||||||
return call('raix:push-setuser', pushId);
|
return call('raix:push-setuser', pushId);
|
||||||
},
|
},
|
||||||
|
loadMissedMessages,
|
||||||
_parseUrls(urls) {
|
loadMessagesForRoom,
|
||||||
return urls.filter(url => url.meta && !url.ignoreParse).map((url, index) => {
|
getMessage,
|
||||||
const tmp = {};
|
sendMessage,
|
||||||
const { meta } = url;
|
getRooms,
|
||||||
tmp._id = index;
|
readMessages,
|
||||||
tmp.title = meta.ogTitle || meta.twitterTitle || meta.title || meta.pageTitle || meta.oembedTitle;
|
me({ server = reduxStore.getState().server.server, token, userId }) {
|
||||||
tmp.description = meta.ogDescription || meta.twitterDescription || meta.description || meta.oembedAuthorName;
|
return fetch(`${ server }/api/v1/me`, {
|
||||||
let decodedOgImage;
|
method: 'get',
|
||||||
if (meta.ogImage) {
|
headers: {
|
||||||
decodedOgImage = meta.ogImage.replace(/&/g, '&');
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Token': token,
|
||||||
|
'X-User-Id': userId
|
||||||
}
|
}
|
||||||
tmp.image = decodedOgImage || meta.twitterImage || meta.oembedThumbnailUrl;
|
}).then(response => response.json());
|
||||||
tmp.url = url.url;
|
|
||||||
return tmp;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_buildMessage(message) {
|
|
||||||
message.status = messagesStatus.SENT;
|
|
||||||
normalizeMessage(message);
|
|
||||||
message.urls = message.urls ? RocketChat._parseUrls(message.urls) : [];
|
|
||||||
message._updatedAt = new Date();
|
|
||||||
// loadHistory returns message.starred as object
|
|
||||||
// stream-room-messages returns message.starred as an array
|
|
||||||
message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred);
|
|
||||||
return message;
|
|
||||||
},
|
|
||||||
loadMessagesForRoom(rid, end, cb) {
|
|
||||||
return this.ddp.call('loadHistory', rid, end, 20).then((data) => {
|
|
||||||
if (data && data.messages.length) {
|
|
||||||
const messages = data.messages.map(message => this._buildMessage(message));
|
|
||||||
database.write(() => {
|
|
||||||
messages.forEach((message) => {
|
|
||||||
database.create('messages', message, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (cb) {
|
|
||||||
cb({ end: data && data.messages.length < 20 });
|
|
||||||
}
|
|
||||||
return data.message;
|
|
||||||
}, (err) => {
|
|
||||||
if (err) {
|
|
||||||
if (cb) {
|
|
||||||
cb({ end: true });
|
|
||||||
}
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getMessage(rid, msg = {}) {
|
userInfo({ server = reduxStore.getState().server.server, token, userId }) {
|
||||||
const _id = Random.id();
|
return fetch(`${ server }/api/v1/users.info?userId=${ userId }`, {
|
||||||
const message = {
|
method: 'get',
|
||||||
_id,
|
headers: {
|
||||||
rid,
|
'Content-Type': 'application/json',
|
||||||
msg,
|
'X-Auth-Token': token,
|
||||||
ts: new Date(),
|
'X-User-Id': userId
|
||||||
_updatedAt: new Date(),
|
|
||||||
status: messagesStatus.TEMP,
|
|
||||||
u: {
|
|
||||||
_id: reduxStore.getState().login.user.id || '1',
|
|
||||||
username: reduxStore.getState().login.user.username
|
|
||||||
}
|
}
|
||||||
};
|
}).then(response => response.json());
|
||||||
|
|
||||||
database.write(() => {
|
|
||||||
database.create('messages', message, true);
|
|
||||||
});
|
|
||||||
return message;
|
|
||||||
},
|
|
||||||
async _sendMessageCall(message) {
|
|
||||||
const { _id, rid, msg } = message;
|
|
||||||
const sendMessageCall = call('sendMessage', { _id, rid, msg });
|
|
||||||
const timeoutCall = new Promise(resolve => setTimeout(resolve, SERVER_TIMEOUT, 'timeout'));
|
|
||||||
const result = await Promise.race([sendMessageCall, timeoutCall]);
|
|
||||||
if (result === 'timeout') {
|
|
||||||
database.write(() => {
|
|
||||||
message.status = messagesStatus.ERROR;
|
|
||||||
database.create('messages', message, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async sendMessage(rid, msg) {
|
|
||||||
const tempMessage = this.getMessage(rid, msg);
|
|
||||||
return RocketChat._sendMessageCall(tempMessage);
|
|
||||||
},
|
},
|
||||||
async resendMessage(messageId) {
|
async resendMessage(messageId) {
|
||||||
const message = await database.objects('messages').filtered('_id = $0', messageId)[0];
|
const message = await database.objects('messages').filtered('_id = $0', messageId)[0];
|
||||||
|
@ -563,7 +556,7 @@ const RocketChat = {
|
||||||
message.status = messagesStatus.TEMP;
|
message.status = messagesStatus.TEMP;
|
||||||
database.create('messages', message, true);
|
database.create('messages', message, true);
|
||||||
});
|
});
|
||||||
return RocketChat._sendMessageCall(message);
|
return _sendMessageCall(JSON.parse(JSON.stringify(message)));
|
||||||
},
|
},
|
||||||
|
|
||||||
spotlight(search, usernames, type) {
|
spotlight(search, usernames, type) {
|
||||||
|
@ -573,16 +566,6 @@ const RocketChat = {
|
||||||
createDirectMessage(username) {
|
createDirectMessage(username) {
|
||||||
return call('createDirectMessage', username);
|
return call('createDirectMessage', username);
|
||||||
},
|
},
|
||||||
async readMessages(rid) {
|
|
||||||
const ret = await call('readMessages', rid);
|
|
||||||
|
|
||||||
const [subscription] = database.objects('subscriptions').filtered('rid = $0', rid);
|
|
||||||
database.write(() => {
|
|
||||||
subscription.lastOpen = new Date();
|
|
||||||
});
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
joinRoom(rid) {
|
joinRoom(rid) {
|
||||||
return call('joinRoom', rid);
|
return call('joinRoom', rid);
|
||||||
},
|
},
|
||||||
|
@ -646,6 +629,7 @@ const RocketChat = {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return e;
|
return e;
|
||||||
} finally {
|
} finally {
|
||||||
|
// TODO: fix that
|
||||||
try {
|
try {
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
const msg = database.objects('messages').filtered('_id = $0', placeholder._id);
|
const msg = database.objects('messages').filtered('_id = $0', placeholder._id);
|
||||||
|
@ -656,93 +640,9 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getRooms() {
|
getSettings,
|
||||||
const { login } = reduxStore.getState();
|
getPermissions,
|
||||||
let lastMessage = database
|
getCustomEmoji,
|
||||||
.objects('subscriptions')
|
|
||||||
.sorted('roomUpdatedAt', true)[0];
|
|
||||||
lastMessage = lastMessage && new Date(lastMessage.roomUpdatedAt);
|
|
||||||
let [subscriptions, rooms] = await Promise.all([call('subscriptions/get', lastMessage), call('rooms/get', lastMessage)]);
|
|
||||||
|
|
||||||
if (lastMessage) {
|
|
||||||
subscriptions = subscriptions.update;
|
|
||||||
rooms = rooms.update;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = subscriptions.map((subscription) => {
|
|
||||||
const room = rooms.find(({ _id }) => _id === subscription.rid);
|
|
||||||
if (room) {
|
|
||||||
subscription.roomUpdatedAt = room._updatedAt;
|
|
||||||
subscription.lastMessage = normalizeMessage(room.lastMessage);
|
|
||||||
subscription.ro = room.ro;
|
|
||||||
subscription.description = room.description;
|
|
||||||
subscription.topic = room.topic;
|
|
||||||
subscription.announcement = room.announcement;
|
|
||||||
subscription.reactWhenReadOnly = room.reactWhenReadOnly;
|
|
||||||
subscription.archived = room.archived;
|
|
||||||
subscription.joinCodeRequired = room.joinCodeRequired;
|
|
||||||
}
|
|
||||||
if (subscription.roles) {
|
|
||||||
subscription.roles = subscription.roles.map(role => ({ value: role }));
|
|
||||||
}
|
|
||||||
return subscription;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
database.write(() => {
|
|
||||||
data.forEach(subscription => database.create('subscriptions', subscription, true));
|
|
||||||
// rooms.forEach(room => database.create('rooms', room, true));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
this.ddp.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false);
|
|
||||||
this.ddp.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false);
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
disconnect() {
|
|
||||||
if (!this.ddp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reduxStore.dispatch(disconnect_by_user());
|
|
||||||
delete this.ddp;
|
|
||||||
return this.ddp.disconnect();
|
|
||||||
},
|
|
||||||
login(params, callback) {
|
|
||||||
return this.ddp.call('login', params).then((result) => {
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
callback(null, result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}, (err) => {
|
|
||||||
if (/user not found/i.test(err.reason)) {
|
|
||||||
err.error = 1;
|
|
||||||
err.reason = 'User or Password incorrect';
|
|
||||||
err.message = 'User or Password incorrect';
|
|
||||||
}
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
callback(err, null);
|
|
||||||
}
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
logout({ server }) {
|
|
||||||
if (this.ddp) {
|
|
||||||
this.ddp.logout();
|
|
||||||
}
|
|
||||||
database.deleteAll();
|
|
||||||
AsyncStorage.removeItem(TOKEN_KEY);
|
|
||||||
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`);
|
|
||||||
},
|
|
||||||
async getSettings() {
|
|
||||||
const temp = database.objects('settings').sorted('_updatedAt', true)[0];
|
|
||||||
const result = await (!temp ? call('public-settings/get') : call('public-settings/get', new Date(temp._updatedAt)));
|
|
||||||
const settings = temp ? result.update : result;
|
|
||||||
const filteredSettings = RocketChat._prepareSettings(RocketChat._filterSettings(settings));
|
|
||||||
database.write(() => {
|
|
||||||
filteredSettings.forEach(setting => database.create('settings', setting, true));
|
|
||||||
});
|
|
||||||
reduxStore.dispatch(actions.addSettings(RocketChat.parseSettings(filteredSettings)));
|
|
||||||
},
|
|
||||||
parseSettings: settings => settings.reduce((ret, item) => {
|
parseSettings: settings => settings.reduce((ret, item) => {
|
||||||
ret[item._id] = item[settingsType[item.type]] || item.valueAsString || item.valueAsNumber ||
|
ret[item._id] = item[settingsType[item.type]] || item.valueAsString || item.valueAsNumber ||
|
||||||
item.valueAsBoolean || item.value;
|
item.valueAsBoolean || item.value;
|
||||||
|
@ -755,16 +655,6 @@ const RocketChat = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
_filterSettings: settings => settings.filter(setting => settingsType[setting.type] && setting.value),
|
_filterSettings: settings => settings.filter(setting => settingsType[setting.type] && setting.value),
|
||||||
async getPermissions() {
|
|
||||||
const temp = database.objects('permissions').sorted('_updatedAt', true)[0];
|
|
||||||
const result = await (!temp ? call('permissions/get') : call('permissions/get', new Date(temp._updatedAt)));
|
|
||||||
let permissions = temp ? result.update : result;
|
|
||||||
permissions = RocketChat._preparePermissions(permissions);
|
|
||||||
database.write(() => {
|
|
||||||
permissions.forEach(permission => database.create('permissions', permission, true));
|
|
||||||
});
|
|
||||||
reduxStore.dispatch(actions.setAllPermissions(RocketChat.parsePermissions(permissions)));
|
|
||||||
},
|
|
||||||
parsePermissions: permissions => permissions.reduce((ret, item) => {
|
parsePermissions: permissions => permissions.reduce((ret, item) => {
|
||||||
ret[item._id] = item.roles.reduce((roleRet, role) => [...roleRet, role.value], []);
|
ret[item._id] = item.roles.reduce((roleRet, role) => [...roleRet, role.value], []);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -775,16 +665,6 @@ const RocketChat = {
|
||||||
});
|
});
|
||||||
return permissions;
|
return permissions;
|
||||||
},
|
},
|
||||||
async getCustomEmoji() {
|
|
||||||
const temp = database.objects('customEmojis').sorted('_updatedAt', true)[0];
|
|
||||||
let emojis = await call('listEmojiCustom');
|
|
||||||
emojis = emojis.filter(emoji => !temp || emoji._updatedAt > temp._updatedAt);
|
|
||||||
emojis = RocketChat._prepareEmojis(emojis);
|
|
||||||
database.write(() => {
|
|
||||||
emojis.forEach(emoji => database.create('customEmojis', emoji, true));
|
|
||||||
});
|
|
||||||
reduxStore.dispatch(actions.setCustomEmojis(RocketChat.parseEmojis(emojis)));
|
|
||||||
},
|
|
||||||
parseEmojis: emojis => emojis.reduce((ret, item) => {
|
parseEmojis: emojis => emojis.reduce((ret, item) => {
|
||||||
ret[item.name] = item.extension;
|
ret[item.name] = item.extension;
|
||||||
item.aliases.forEach((alias) => {
|
item.aliases.forEach((alias) => {
|
||||||
|
@ -815,20 +695,21 @@ const RocketChat = {
|
||||||
return call('pinMessage', message);
|
return call('pinMessage', message);
|
||||||
},
|
},
|
||||||
getRoom(rid) {
|
getRoom(rid) {
|
||||||
const result = database.objects('subscriptions').filtered('rid = $0', rid);
|
const [result] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||||
if (result.length === 0) {
|
if (!result) {
|
||||||
return Promise.reject(new Error('Room not found'));
|
return Promise.reject(new Error('Room not found'));
|
||||||
}
|
}
|
||||||
return Promise.resolve(result[0]);
|
return Promise.resolve(result);
|
||||||
},
|
},
|
||||||
async getPermalink(message) {
|
async getPermalink(message) {
|
||||||
const room = await RocketChat.getRoom(message.rid);
|
const room = await RocketChat.getRoom(message.rid);
|
||||||
|
const { server } = reduxStore.getState().server;
|
||||||
const roomType = {
|
const roomType = {
|
||||||
p: 'group',
|
p: 'group',
|
||||||
c: 'channel',
|
c: 'channel',
|
||||||
d: 'direct'
|
d: 'direct'
|
||||||
}[room.t];
|
}[room.t];
|
||||||
return `${ room._server.id }/${ roomType }/${ room.name }?msg=${ message._id }`;
|
return `${ server }/${ roomType }/${ room.name }?msg=${ message._id }`;
|
||||||
},
|
},
|
||||||
subscribe(...args) {
|
subscribe(...args) {
|
||||||
return this.ddp.subscribe(...args);
|
return this.ddp.subscribe(...args);
|
||||||
|
@ -878,6 +759,12 @@ const RocketChat = {
|
||||||
eraseRoom(rid) {
|
eraseRoom(rid) {
|
||||||
return call('eraseRoom', rid);
|
return call('eraseRoom', rid);
|
||||||
},
|
},
|
||||||
|
toggleMuteUserInRoom(rid, username, mute) {
|
||||||
|
if (mute) {
|
||||||
|
return call('muteUserInRoom', { rid, username });
|
||||||
|
}
|
||||||
|
return call('unmuteUserInRoom', { rid, username });
|
||||||
|
},
|
||||||
toggleArchiveRoom(rid, archive) {
|
toggleArchiveRoom(rid, archive) {
|
||||||
if (archive) {
|
if (archive) {
|
||||||
return call('archiveRoom', rid);
|
return call('archiveRoom', rid);
|
||||||
|
@ -887,6 +774,17 @@ const RocketChat = {
|
||||||
saveRoomSettings(rid, params) {
|
saveRoomSettings(rid, params) {
|
||||||
return call('saveRoomSettings', rid, params);
|
return call('saveRoomSettings', rid, params);
|
||||||
},
|
},
|
||||||
|
saveNotificationSettings(rid, param, value) {
|
||||||
|
return call('saveNotificationSettings', rid, param, value);
|
||||||
|
},
|
||||||
|
messageSearch(text, rid, limit) {
|
||||||
|
return call('messageSearch', text, rid, limit);
|
||||||
|
},
|
||||||
|
addUsersToRoom(rid) {
|
||||||
|
let { users } = reduxStore.getState().selectedUsers;
|
||||||
|
users = users.map(u => u.name);
|
||||||
|
return call('addUsersToRoom', { rid, users });
|
||||||
|
},
|
||||||
hasPermission(permissions, rid) {
|
hasPermission(permissions, rid) {
|
||||||
// get the room from realm
|
// get the room from realm
|
||||||
const room = database.objects('subscriptions').filtered('rid = $0', rid)[0];
|
const room = database.objects('subscriptions').filtered('rid = $0', rid)[0];
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
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 } from 'react-native';
|
import { View, Text, StyleSheet, ViewPropTypes } from 'react-native';
|
||||||
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import SimpleMarkdown from 'simple-markdown';
|
import SimpleMarkdown from 'simple-markdown';
|
||||||
|
|
||||||
|
import messagesStatus from '../constants/messagesStatus';
|
||||||
|
|
||||||
import Avatar from '../containers/Avatar';
|
import Avatar from '../containers/Avatar';
|
||||||
import Status from '../containers/status';
|
import Status from '../containers/status';
|
||||||
import Touch from '../utils/touch/index'; //eslint-disable-line
|
import Touch from '../utils/touch/index'; //eslint-disable-line
|
||||||
|
@ -44,7 +46,6 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
color: '#444',
|
color: '#444',
|
||||||
|
|
||||||
marginRight: 8
|
marginRight: 8
|
||||||
},
|
},
|
||||||
lastMessage: {
|
lastMessage: {
|
||||||
|
@ -64,8 +65,8 @@ const styles = StyleSheet.create({
|
||||||
// backgroundColor: '#eee'
|
// backgroundColor: '#eee'
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
width: '100%',
|
// width: '100%',
|
||||||
flex: 1,
|
// flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'flex-end',
|
alignItems: 'flex-end',
|
||||||
justifyContent: 'flex-end'
|
justifyContent: 'flex-end'
|
||||||
|
@ -145,41 +146,57 @@ const renderNumber = (unread, userMentions) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const attrs = ['name', 'unread', 'userMentions', 'alert', 'showLastMessage', 'type', '_updatedAt'];
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
user: state.login && state.login.user,
|
user: state.login && state.login.user,
|
||||||
StoreLastMessage: state.settings.Store_Last_Message,
|
StoreLastMessage: state.settings.Store_Last_Message
|
||||||
customEmojis: state.customEmojis
|
|
||||||
}))
|
}))
|
||||||
export default class RoomItem extends React.PureComponent {
|
export default class RoomItem extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
StoreLastMessage: PropTypes.bool,
|
StoreLastMessage: PropTypes.bool,
|
||||||
_updatedAt: PropTypes.instanceOf(Date),
|
_updatedAt: PropTypes.instanceOf(Date),
|
||||||
lastMessage: PropTypes.object,
|
lastMessage: PropTypes.object,
|
||||||
|
showLastMessage: PropTypes.bool,
|
||||||
favorite: PropTypes.bool,
|
favorite: PropTypes.bool,
|
||||||
alert: PropTypes.bool,
|
alert: PropTypes.bool,
|
||||||
unread: PropTypes.number,
|
unread: PropTypes.number,
|
||||||
userMentions: PropTypes.number,
|
userMentions: PropTypes.number,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
onPress: PropTypes.func,
|
onPress: PropTypes.func,
|
||||||
customEmojis: PropTypes.object,
|
onLongPress: PropTypes.func,
|
||||||
user: PropTypes.object
|
user: PropTypes.object,
|
||||||
|
avatarSize: PropTypes.number,
|
||||||
|
statusStyle: ViewPropTypes.style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
showLastMessage: true,
|
||||||
|
avatarSize: 46
|
||||||
|
}
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
const oldlastMessage = this.props.lastMessage;
|
||||||
|
const newLastmessage = nextProps.lastMessage;
|
||||||
|
|
||||||
|
if (oldlastMessage && newLastmessage && oldlastMessage.ts !== newLastmessage.ts) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return attrs.some(key => nextProps[key] !== this.props[key]);
|
||||||
|
}
|
||||||
get icon() {
|
get icon() {
|
||||||
const {
|
const {
|
||||||
type, name, id
|
type, name, id, avatarSize, statusStyle
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (<Avatar text={name} size={46} type={type}>{type === 'd' ? <Status style={styles.status} id={id} /> : null }</Avatar>);
|
return (<Avatar text={name} size={avatarSize} type={type}>{type === 'd' ? <Status style={[styles.status, statusStyle]} id={id} /> : null }</Avatar>);
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastMessage() {
|
get lastMessage() {
|
||||||
const {
|
const {
|
||||||
lastMessage, type
|
lastMessage, type, showLastMessage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!this.props.StoreLastMessage) {
|
if (!this.props.StoreLastMessage || !showLastMessage) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (!lastMessage) {
|
if (!lastMessage) {
|
||||||
|
@ -208,7 +225,7 @@ export default class RoomItem extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
favorite, unread, userMentions, name, _updatedAt, customEmojis, alert
|
favorite, unread, userMentions, name, _updatedAt, alert, status
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const date = this.formatDate(_updatedAt);
|
const date = this.formatDate(_updatedAt);
|
||||||
|
@ -224,10 +241,19 @@ export default class RoomItem extends React.PureComponent {
|
||||||
accessibilityLabel += ', you were mentioned';
|
accessibilityLabel += ', you were mentioned';
|
||||||
}
|
}
|
||||||
|
|
||||||
accessibilityLabel += `, last message ${ date }`;
|
if (date) {
|
||||||
|
accessibilityLabel += `, last message ${ date }`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Touch onPress={this.props.onPress} underlayColor='#FFFFFF' activeOpacity={0.5} accessibilityLabel={accessibilityLabel} accessibilityTraits='selected'>
|
<Touch
|
||||||
|
onPress={this.props.onPress}
|
||||||
|
onLongPress={this.props.onLongPress}
|
||||||
|
underlayColor='#FFFFFF'
|
||||||
|
activeOpacity={0.5}
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
accessibilityTraits='selected'
|
||||||
|
>
|
||||||
<View style={[styles.container, favorite && styles.favorite]}>
|
<View style={[styles.container, favorite && styles.favorite]}>
|
||||||
{this.icon}
|
{this.icon}
|
||||||
<View style={styles.roomNameView}>
|
<View style={styles.roomNameView}>
|
||||||
|
@ -236,9 +262,9 @@ export default class RoomItem extends React.PureComponent {
|
||||||
{_updatedAt ? <Text style={[styles.update, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
|
{_updatedAt ? <Text style={[styles.update, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ date }</Text> : null}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
|
{status === messagesStatus.ERROR ? <Icon name='error-outline' color='red' size={12} style={{ marginRight: 5, alignSelf: 'center' }} /> : null }
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={this.lastMessage}
|
msg={this.lastMessage}
|
||||||
customEmojis={customEmojis}
|
|
||||||
style={styles.lastMessage}
|
style={styles.lastMessage}
|
||||||
markdownStyle={markdownStyle}
|
markdownStyle={markdownStyle}
|
||||||
customRules={customRules}
|
customRules={customRules}
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { CREATE_CHANNEL } from '../actions/actionsTypes';
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
failure: false,
|
failure: false,
|
||||||
users: []
|
result: '',
|
||||||
|
error: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function messages(state = initialState, action) {
|
export default function messages(state = initialState, action) {
|
||||||
|
@ -11,9 +12,9 @@ export default function messages(state = initialState, action) {
|
||||||
case CREATE_CHANNEL.REQUEST:
|
case CREATE_CHANNEL.REQUEST:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
error: undefined,
|
isFetching: true,
|
||||||
failure: false,
|
failure: false,
|
||||||
isFetching: true
|
error: ''
|
||||||
};
|
};
|
||||||
case CREATE_CHANNEL.SUCCESS:
|
case CREATE_CHANNEL.SUCCESS:
|
||||||
return {
|
return {
|
||||||
|
@ -29,18 +30,6 @@ export default function messages(state = initialState, action) {
|
||||||
failure: true,
|
failure: true,
|
||||||
error: action.err
|
error: action.err
|
||||||
};
|
};
|
||||||
case CREATE_CHANNEL.ADD_USER:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
users: state.users.concat(action.user)
|
|
||||||
};
|
|
||||||
case CREATE_CHANNEL.REMOVE_USER:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
users: state.users.filter(item => item.name !== action.user.name)
|
|
||||||
};
|
|
||||||
case CREATE_CHANNEL.RESET:
|
|
||||||
return initialState;
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import room from './room';
|
||||||
import rooms from './rooms';
|
import rooms from './rooms';
|
||||||
import server from './server';
|
import server from './server';
|
||||||
import navigator from './navigator';
|
import navigator from './navigator';
|
||||||
|
import selectedUsers from './selectedUsers';
|
||||||
import createChannel from './createChannel';
|
import createChannel from './createChannel';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
import permissions from './permissions';
|
import permissions from './permissions';
|
||||||
|
@ -26,6 +27,7 @@ export default combineReducers({
|
||||||
messages,
|
messages,
|
||||||
server,
|
server,
|
||||||
navigator,
|
navigator,
|
||||||
|
selectedUsers,
|
||||||
createChannel,
|
createChannel,
|
||||||
app,
|
app,
|
||||||
room,
|
room,
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
import { MENTIONED_MESSAGES } from '../actions/actionsTypes';
|
import { MENTIONED_MESSAGES } from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
messages: []
|
messages: [],
|
||||||
|
ready: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function server(state = initialState, action) {
|
export default function server(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case MENTIONED_MESSAGES.OPEN:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: false
|
||||||
|
};
|
||||||
|
case MENTIONED_MESSAGES.READY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: true
|
||||||
|
};
|
||||||
case MENTIONED_MESSAGES.MESSAGES_RECEIVED:
|
case MENTIONED_MESSAGES.MESSAGES_RECEIVED:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -2,7 +2,8 @@ import { PINNED_MESSAGES } from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
messages: [],
|
messages: [],
|
||||||
isOpen: false
|
isOpen: false,
|
||||||
|
ready: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function server(state = initialState, action) {
|
export default function server(state = initialState, action) {
|
||||||
|
@ -10,7 +11,13 @@ export default function server(state = initialState, action) {
|
||||||
case PINNED_MESSAGES.OPEN:
|
case PINNED_MESSAGES.OPEN:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isOpen: true
|
isOpen: true,
|
||||||
|
ready: false
|
||||||
|
};
|
||||||
|
case PINNED_MESSAGES.READY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: true
|
||||||
};
|
};
|
||||||
case PINNED_MESSAGES.MESSAGES_RECEIVED:
|
case PINNED_MESSAGES.MESSAGES_RECEIVED:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
import { ROOM_FILES } from '../actions/actionsTypes';
|
import { ROOM_FILES } from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
messages: []
|
messages: [],
|
||||||
|
ready: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function server(state = initialState, action) {
|
export default function server(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case ROOM_FILES.OPEN:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: false
|
||||||
|
};
|
||||||
|
case ROOM_FILES.READY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: true
|
||||||
|
};
|
||||||
case ROOM_FILES.MESSAGES_RECEIVED:
|
case ROOM_FILES.MESSAGES_RECEIVED:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { SELECTED_USERS } from '../actions/actionsTypes';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
users: [],
|
||||||
|
loading: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function messages(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case SELECTED_USERS.ADD_USER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
users: state.users.concat(action.user)
|
||||||
|
};
|
||||||
|
case SELECTED_USERS.REMOVE_USER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
users: state.users.filter(item => item.name !== action.user.name)
|
||||||
|
};
|
||||||
|
case SELECTED_USERS.SET_LOADING:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: action.loading
|
||||||
|
};
|
||||||
|
case SELECTED_USERS.RESET:
|
||||||
|
return initialState;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,8 @@ const initialState = {
|
||||||
connected: false,
|
connected: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
failure: false,
|
failure: false,
|
||||||
server: ''
|
server: '',
|
||||||
|
adding: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +33,17 @@ export default function server(state = initialState, action) {
|
||||||
failure: true,
|
failure: true,
|
||||||
errorMessage: action.err
|
errorMessage: action.err
|
||||||
};
|
};
|
||||||
|
case SERVER.ADD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
adding: true
|
||||||
|
};
|
||||||
case SERVER.SELECT:
|
case SERVER.SELECT:
|
||||||
return { ...state, server: action.server };
|
return {
|
||||||
|
...state,
|
||||||
|
server: action.server,
|
||||||
|
adding: false
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
import { SNIPPETED_MESSAGES } from '../actions/actionsTypes';
|
import { SNIPPETED_MESSAGES } from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
messages: []
|
messages: [],
|
||||||
|
ready: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function server(state = initialState, action) {
|
export default function server(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case SNIPPETED_MESSAGES.OPEN:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: false
|
||||||
|
};
|
||||||
|
case SNIPPETED_MESSAGES.READY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: true
|
||||||
|
};
|
||||||
case SNIPPETED_MESSAGES.MESSAGES_RECEIVED:
|
case SNIPPETED_MESSAGES.MESSAGES_RECEIVED:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -2,7 +2,8 @@ import { STARRED_MESSAGES } from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
messages: [],
|
messages: [],
|
||||||
isOpen: false
|
isOpen: false,
|
||||||
|
ready: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function server(state = initialState, action) {
|
export default function server(state = initialState, action) {
|
||||||
|
@ -10,7 +11,13 @@ export default function server(state = initialState, action) {
|
||||||
case STARRED_MESSAGES.OPEN:
|
case STARRED_MESSAGES.OPEN:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isOpen: true
|
isOpen: true,
|
||||||
|
ready: false
|
||||||
|
};
|
||||||
|
case STARRED_MESSAGES.READY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: true
|
||||||
};
|
};
|
||||||
case STARRED_MESSAGES.MESSAGES_RECEIVED:
|
case STARRED_MESSAGES.MESSAGES_RECEIVED:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
import { call, takeLatest, select, take, race } from 'redux-saga/effects';
|
import { call, takeLatest, select, put, all } from 'redux-saga/effects';
|
||||||
import { delay } from 'redux-saga';
|
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';
|
||||||
|
import { setToken } from '../actions/login';
|
||||||
|
|
||||||
const getServer = ({ server }) => server.server;
|
const getServer = ({ server }) => server.server;
|
||||||
|
const getToken = function* getToken() {
|
||||||
|
const currentServer = yield select(getServer);
|
||||||
const connect = url => RocketChat.connect(url);
|
const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
||||||
const watchConnect = function* watchConnect() {
|
if (user) {
|
||||||
const { disconnect } = yield race({
|
yield put(setToken(JSON.parse(user)));
|
||||||
disconnect: take(METEOR.DISCONNECT),
|
try {
|
||||||
disconnected_by_user: take(METEOR.DISCONNECT_BY_USER)
|
yield call([AsyncStorage, 'setItem'], RocketChat.TOKEN_KEY, JSON.parse(user).token || '');
|
||||||
});
|
} catch (error) {
|
||||||
if (disconnect) {
|
console.warn('getToken', error);
|
||||||
while (true) {
|
|
||||||
const { connected } = yield race({
|
|
||||||
connected: take(METEOR.SUCCESS),
|
|
||||||
timeout: call(delay, 1000)
|
|
||||||
});
|
|
||||||
if (connected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
yield RocketChat.reconnect();
|
|
||||||
}
|
}
|
||||||
|
return JSON.parse(user);
|
||||||
}
|
}
|
||||||
|
return yield put(setToken());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const connect = (...args) => RocketChat.connect(...args);
|
||||||
|
|
||||||
const test = function* test() {
|
const test = function* test() {
|
||||||
// try {
|
try {
|
||||||
const server = yield select(getServer);
|
const server = yield select(getServer);
|
||||||
// const response =
|
const user = yield call(getToken);
|
||||||
yield call(connect, server);
|
// const response =
|
||||||
|
yield all([call(connect, server, user && user.token ? { resume: user.token, ...user.user } : undefined)]);// , put(loginRequest({ resume: user.token }))]);
|
||||||
// yield put(connectSuccess(response));
|
// yield put(connectSuccess(response));
|
||||||
// } catch (err) {
|
} catch (err) {
|
||||||
|
console.warn('test', err);
|
||||||
// yield put(connectFailure(err.status));
|
// yield put(connectFailure(err.status));
|
||||||
// }
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(METEOR.REQUEST, test);
|
yield takeLatest(METEOR.REQUEST, test);
|
||||||
// yield take(METEOR.SUCCESS, watchConnect);
|
// yield take(METEOR.SUCCESS, watchConnect);
|
||||||
yield takeLatest(METEOR.SUCCESS, watchConnect);
|
// yield takeLatest(METEOR.SUCCESS, watchConnect);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -28,4 +28,5 @@ const handleRequest = function* handleRequest({ data }) {
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(CREATE_CHANNEL.REQUEST, handleRequest);
|
yield takeLatest(CREATE_CHANNEL.REQUEST, handleRequest);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { take, fork } from 'redux-saga/effects';
|
|
||||||
|
|
||||||
const foreverAlone = function* foreverAlone() {
|
|
||||||
yield take('FOI');
|
|
||||||
console.log('FOIIIIIII');
|
|
||||||
yield take('voa');
|
|
||||||
console.log('o');
|
|
||||||
};
|
|
||||||
|
|
||||||
const root = function* root() {
|
|
||||||
yield fork(foreverAlone);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default root;
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { all } from 'redux-saga/effects';
|
import { all } from 'redux-saga/effects';
|
||||||
import hello from './hello';
|
|
||||||
import login from './login';
|
import login from './login';
|
||||||
import connect from './connect';
|
import connect from './connect';
|
||||||
import rooms from './rooms';
|
import rooms from './rooms';
|
||||||
|
@ -18,7 +17,6 @@ const root = function* root() {
|
||||||
yield all([
|
yield all([
|
||||||
init(),
|
init(),
|
||||||
createChannel(),
|
createChannel(),
|
||||||
hello(),
|
|
||||||
rooms(),
|
rooms(),
|
||||||
login(),
|
login(),
|
||||||
connect(),
|
connect(),
|
||||||
|
|
|
@ -2,15 +2,13 @@ import { AsyncStorage } from 'react-native';
|
||||||
import { call, put, takeLatest } from 'redux-saga/effects';
|
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import { setServer } from '../actions/server';
|
import { setServer } from '../actions/server';
|
||||||
import { restoreToken } from '../actions/login';
|
import { restoreToken, setUser } from '../actions/login';
|
||||||
import { APP } from '../actions/actionsTypes';
|
import { APP } from '../actions/actionsTypes';
|
||||||
import { setRoles } from '../actions/roles';
|
|
||||||
import database from '../lib/realm';
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
||||||
const restore = function* restore() {
|
const restore = function* restore() {
|
||||||
try {
|
try {
|
||||||
const token = yield call([AsyncStorage, 'getItem'], 'reactnativemeteor_usertoken');
|
const token = yield call([AsyncStorage, 'getItem'], RocketChat.TOKEN_KEY);
|
||||||
if (token) {
|
if (token) {
|
||||||
yield put(restoreToken(token));
|
yield put(restoreToken(token));
|
||||||
}
|
}
|
||||||
|
@ -18,21 +16,16 @@ const restore = function* restore() {
|
||||||
const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer');
|
const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer');
|
||||||
if (currentServer) {
|
if (currentServer) {
|
||||||
yield put(setServer(currentServer));
|
yield put(setServer(currentServer));
|
||||||
const settings = database.objects('settings');
|
|
||||||
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
const login = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
||||||
const permissions = database.objects('permissions');
|
if (login && login.user) {
|
||||||
yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length))));
|
yield put(setUser(login.user));
|
||||||
const emojis = database.objects('customEmojis');
|
}
|
||||||
yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length))));
|
|
||||||
const roles = database.objects('roles');
|
|
||||||
yield put(setRoles(roles.reduce((result, role) => {
|
|
||||||
result[role._id] = role.description;
|
|
||||||
return result;
|
|
||||||
}, {})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(actions.appReady({}));
|
yield put(actions.appReady({}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.warn('restore', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { put, call, takeLatest, select, all, take } from 'redux-saga/effects';
|
import { put, call, take, takeLatest, select, all } from 'redux-saga/effects';
|
||||||
import { Answers } from 'react-native-fabric';
|
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import {
|
import {
|
||||||
loginRequest,
|
// loginRequest,
|
||||||
loginSubmit,
|
// loginSubmit,
|
||||||
registerRequest,
|
registerRequest,
|
||||||
registerIncomplete,
|
registerIncomplete,
|
||||||
loginSuccess,
|
// loginSuccess,
|
||||||
loginFailure,
|
loginFailure,
|
||||||
logout,
|
// logout,
|
||||||
setToken,
|
// setToken,
|
||||||
registerSuccess,
|
registerSuccess,
|
||||||
setUsernameRequest,
|
setUsernameRequest,
|
||||||
setUsernameSuccess,
|
setUsernameSuccess,
|
||||||
|
@ -23,40 +23,41 @@ import * as NavigationService from '../containers/routes/NavigationService';
|
||||||
const getUser = state => state.login;
|
const getUser = state => state.login;
|
||||||
const getServer = state => state.server.server;
|
const getServer = state => state.server.server;
|
||||||
const getIsConnected = state => state.meteor.connected;
|
const getIsConnected = state => state.meteor.connected;
|
||||||
const loginCall = args => ((args.resume || args.oauth) ? RocketChat.login(args) : RocketChat.loginWithPassword(args));
|
|
||||||
|
// const loginCall = args => ((args.resume || args.oauth) ? RocketChat.login(args) : RocketChat.loginWithPassword(args));
|
||||||
|
const loginCall = args => RocketChat.loginWithPassword(args);
|
||||||
const registerCall = args => RocketChat.register(args);
|
const registerCall = args => RocketChat.register(args);
|
||||||
const setUsernameCall = args => RocketChat.setUsername(args);
|
const setUsernameCall = args => RocketChat.setUsername(args);
|
||||||
|
const loginSuccessCall = () => RocketChat.loginSuccess();
|
||||||
const logoutCall = args => RocketChat.logout(args);
|
const logoutCall = args => RocketChat.logout(args);
|
||||||
const meCall = args => RocketChat.me(args);
|
|
||||||
const forgotPasswordCall = args => RocketChat.forgotPassword(args);
|
const forgotPasswordCall = args => RocketChat.forgotPassword(args);
|
||||||
const userInfoCall = args => RocketChat.userInfo(args);
|
|
||||||
|
|
||||||
const getToken = function* getToken() {
|
// const getToken = function* getToken() {
|
||||||
const currentServer = yield select(getServer);
|
// const currentServer = yield select(getServer);
|
||||||
const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
// const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
||||||
if (user) {
|
// if (user) {
|
||||||
try {
|
// try {
|
||||||
yield put(setToken(JSON.parse(user)));
|
// yield put(setToken(JSON.parse(user)));
|
||||||
yield call([AsyncStorage, 'setItem'], RocketChat.TOKEN_KEY, JSON.parse(user).token || '');
|
// yield call([AsyncStorage, 'setItem'], RocketChat.TOKEN_KEY, JSON.parse(user).token || '');
|
||||||
return JSON.parse(user);
|
// return JSON.parse(user);
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
console.log('getTokenerr', e);
|
// console.log('getTokenerr', e);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
return yield put(setToken());
|
// return yield put(setToken());
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
|
// const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
|
||||||
try {
|
// try {
|
||||||
const user = yield call(getToken);
|
// const user = yield call(getToken);
|
||||||
if (user.token) {
|
// if (user.token) {
|
||||||
yield put(loginRequest({ resume: user.token }));
|
// yield put(loginRequest({ resume: user.token }));
|
||||||
}
|
// }
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
console.log(e);
|
// console.log(e);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const saveToken = function* saveToken() {
|
const saveToken = function* saveToken() {
|
||||||
const [server, user] = yield all([select(getServer), select(getUser)]);
|
const [server, user] = yield all([select(getServer), select(getUser)]);
|
||||||
|
@ -64,41 +65,29 @@ const saveToken = function* saveToken() {
|
||||||
yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
|
yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
|
||||||
const token = yield AsyncStorage.getItem('pushId');
|
const token = yield AsyncStorage.getItem('pushId');
|
||||||
if (token) {
|
if (token) {
|
||||||
RocketChat.registerPushToken(user.user.id, token);
|
yield RocketChat.registerPushToken(user.user.id, token);
|
||||||
}
|
}
|
||||||
Answers.logLogin('Email', true, { server });
|
if (!user.user.username && !user.isRegistering) {
|
||||||
};
|
yield put(registerIncomplete());
|
||||||
|
|
||||||
const handleLoginRequest = function* handleLoginRequest({ credentials }) {
|
|
||||||
try {
|
|
||||||
const server = yield select(getServer);
|
|
||||||
const user = yield call(loginCall, credentials);
|
|
||||||
|
|
||||||
// GET /me from REST API
|
|
||||||
const me = yield call(meCall, { server, token: user.token, userId: user.id });
|
|
||||||
|
|
||||||
// if user has username
|
|
||||||
if (me.username) {
|
|
||||||
const userInfo = yield call(userInfoCall, { server, token: user.token, userId: user.id });
|
|
||||||
user.username = userInfo.user.username;
|
|
||||||
if (userInfo.user.roles) {
|
|
||||||
user.roles = userInfo.user.roles;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
yield put(registerIncomplete());
|
|
||||||
}
|
|
||||||
yield put(loginSuccess(user));
|
|
||||||
} catch (err) {
|
|
||||||
if (err.error === 403) {
|
|
||||||
return yield put(logout());
|
|
||||||
}
|
|
||||||
yield put(loginFailure(err));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginSubmit = function* handleLoginSubmit({ credentials }) {
|
// const handleLoginRequest = function* handleLoginRequest({ credentials }) {
|
||||||
yield put(loginRequest(credentials));
|
// try {
|
||||||
};
|
// // const server = yield select(getServer);
|
||||||
|
// const user = yield call(loginCall, credentials);
|
||||||
|
// yield put(loginSuccess(user));
|
||||||
|
// } catch (err) {
|
||||||
|
// if (err.error === 403) {
|
||||||
|
// return yield put(logout());
|
||||||
|
// }
|
||||||
|
// yield put(loginFailure(err));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const handleLoginSubmit = function* handleLoginSubmit({ credentials }) {
|
||||||
|
// yield put(loginRequest(credentials));
|
||||||
|
// };
|
||||||
|
|
||||||
const handleRegisterSubmit = function* handleRegisterSubmit({ credentials }) {
|
const handleRegisterSubmit = function* handleRegisterSubmit({ credentials }) {
|
||||||
yield put(registerRequest(credentials));
|
yield put(registerRequest(credentials));
|
||||||
|
@ -114,10 +103,14 @@ const handleRegisterRequest = function* handleRegisterRequest({ credentials }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegisterSuccess = function* handleRegisterSuccess({ credentials }) {
|
const handleRegisterSuccess = function* handleRegisterSuccess({ credentials }) {
|
||||||
yield put(loginSubmit({
|
try {
|
||||||
username: credentials.email,
|
yield call(loginCall, {
|
||||||
password: credentials.pass
|
username: credentials.email,
|
||||||
}));
|
password: credentials.pass
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
yield put(loginFailure(err));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetUsernameSubmit = function* handleSetUsernameSubmit({ credentials }) {
|
const handleSetUsernameSubmit = function* handleSetUsernameSubmit({ credentials }) {
|
||||||
|
@ -128,6 +121,7 @@ const handleSetUsernameRequest = function* handleSetUsernameRequest({ credential
|
||||||
try {
|
try {
|
||||||
yield call(setUsernameCall, { credentials });
|
yield call(setUsernameCall, { credentials });
|
||||||
yield put(setUsernameSuccess());
|
yield put(setUsernameSuccess());
|
||||||
|
yield call(loginSuccessCall);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
yield put(loginFailure(err));
|
yield put(loginFailure(err));
|
||||||
}
|
}
|
||||||
|
@ -154,20 +148,24 @@ const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ emai
|
||||||
};
|
};
|
||||||
|
|
||||||
const watchLoginOpen = function* watchLoginOpen() {
|
const watchLoginOpen = function* watchLoginOpen() {
|
||||||
const isConnected = yield select(getIsConnected);
|
try {
|
||||||
if (!isConnected) {
|
const isConnected = yield select(getIsConnected);
|
||||||
yield take(types.METEOR.SUCCESS);
|
if (!isConnected) {
|
||||||
|
yield take(types.METEOR.SUCCESS);
|
||||||
|
}
|
||||||
|
const sub = yield RocketChat.subscribe('meteor.loginServiceConfiguration');
|
||||||
|
yield take(types.LOGIN.CLOSE);
|
||||||
|
sub.unsubscribe().catch(e => console.warn('watchLoginOpen unsubscribe', e));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('watchLoginOpen', error);
|
||||||
}
|
}
|
||||||
const sub = yield RocketChat.subscribe('meteor.loginServiceConfiguration');
|
|
||||||
yield take(types.LOGIN.CLOSE);
|
|
||||||
sub.unsubscribe().catch(e => alert(e));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.METEOR.SUCCESS, handleLoginWhenServerChanges);
|
// yield takeLatest(types.METEOR.SUCCESS, handleLoginWhenServerChanges);
|
||||||
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
// yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
||||||
yield takeLatest(types.LOGIN.SUCCESS, saveToken);
|
yield takeLatest(types.LOGIN.SUCCESS, saveToken);
|
||||||
yield takeLatest(types.LOGIN.SUBMIT, handleLoginSubmit);
|
// yield takeLatest(types.LOGIN.SUBMIT, handleLoginSubmit);
|
||||||
yield takeLatest(types.LOGIN.REGISTER_REQUEST, handleRegisterRequest);
|
yield takeLatest(types.LOGIN.REGISTER_REQUEST, handleRegisterRequest);
|
||||||
yield takeLatest(types.LOGIN.REGISTER_SUBMIT, handleRegisterSubmit);
|
yield takeLatest(types.LOGIN.REGISTER_SUBMIT, handleRegisterSubmit);
|
||||||
yield takeLatest(types.LOGIN.REGISTER_SUCCESS, handleRegisterSuccess);
|
yield takeLatest(types.LOGIN.REGISTER_SUCCESS, handleRegisterSuccess);
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
import { take, takeLatest } from 'redux-saga/effects';
|
import { put, takeLatest } from 'redux-saga/effects';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { readyMentionedMessages } from '../actions/mentionedMessages';
|
||||||
|
|
||||||
const watchMentionedMessagesRoom = function* watchMentionedMessagesRoom({ rid }) {
|
let sub;
|
||||||
const sub = yield RocketChat.subscribe('mentionedMessages', rid, 50);
|
let newSub;
|
||||||
yield take(types.MENTIONED_MESSAGES.CLOSE);
|
|
||||||
sub.unsubscribe().catch(e => alert(e));
|
const openMentionedMessagesRoom = function* openMentionedMessagesRoom({ rid, limit }) {
|
||||||
|
newSub = yield RocketChat.subscribe('mentionedMessages', rid, limit);
|
||||||
|
yield put(readyMentionedMessages());
|
||||||
|
if (sub) {
|
||||||
|
sub.unsubscribe().catch(e => console.warn('openMentionedMessagesRoom', e));
|
||||||
|
}
|
||||||
|
sub = newSub;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeMentionedMessagesRoom = function* closeMentionedMessagesRoom() {
|
||||||
|
if (sub) {
|
||||||
|
yield sub.unsubscribe().catch(e => console.warn('closeMentionedMessagesRoom sub', e));
|
||||||
|
}
|
||||||
|
if (newSub) {
|
||||||
|
yield newSub.unsubscribe().catch(e => console.warn('closeMentionedMessagesRoom newSub', e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.MENTIONED_MESSAGES.OPEN, watchMentionedMessagesRoom);
|
yield takeLatest(types.MENTIONED_MESSAGES.OPEN, openMentionedMessagesRoom);
|
||||||
|
yield takeLatest(types.MENTIONED_MESSAGES.CLOSE, closeMentionedMessagesRoom);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { takeLatest, select, take, put, call } from 'redux-saga/effects';
|
import { takeLatest, put, call } from 'redux-saga/effects';
|
||||||
import { MESSAGES, LOGIN } from '../actions/actionsTypes';
|
import { MESSAGES } from '../actions/actionsTypes';
|
||||||
import {
|
import {
|
||||||
messagesSuccess,
|
messagesSuccess,
|
||||||
messagesFailure,
|
messagesFailure,
|
||||||
|
@ -22,16 +22,16 @@ const toggleStarMessage = message => RocketChat.toggleStarMessage(message);
|
||||||
const getPermalink = message => RocketChat.getPermalink(message);
|
const getPermalink = message => RocketChat.getPermalink(message);
|
||||||
const togglePinMessage = message => RocketChat.togglePinMessage(message);
|
const togglePinMessage = message => RocketChat.togglePinMessage(message);
|
||||||
|
|
||||||
const get = function* get({ rid }) {
|
const get = function* get({ room }) {
|
||||||
const auth = yield select(state => state.login.isAuthenticated);
|
|
||||||
if (!auth) {
|
|
||||||
yield take(LOGIN.SUCCESS);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
yield RocketChat.loadMessagesForRoom(rid, null);
|
if (room.lastOpen) {
|
||||||
|
yield RocketChat.loadMissedMessages(room);
|
||||||
|
} else {
|
||||||
|
yield RocketChat.loadMessagesForRoom(room);
|
||||||
|
}
|
||||||
yield put(messagesSuccess());
|
yield put(messagesSuccess());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.warn('messagesFailure', err);
|
||||||
yield put(messagesFailure(err.status));
|
yield put(messagesFailure(err.status));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
import { take, takeLatest } from 'redux-saga/effects';
|
import { put, takeLatest } from 'redux-saga/effects';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { readyPinnedMessages } from '../actions/pinnedMessages';
|
||||||
|
|
||||||
const watchPinnedMessagesRoom = function* watchPinnedMessagesRoom({ rid }) {
|
let sub;
|
||||||
const sub = yield RocketChat.subscribe('pinnedMessages', rid, 50);
|
let newSub;
|
||||||
yield take(types.PINNED_MESSAGES.CLOSE);
|
|
||||||
sub.unsubscribe().catch(e => alert(e));
|
const openPinnedMessagesRoom = function* openPinnedMessagesRoom({ rid, limit }) {
|
||||||
|
newSub = yield RocketChat.subscribe('pinnedMessages', rid, limit);
|
||||||
|
yield put(readyPinnedMessages());
|
||||||
|
if (sub) {
|
||||||
|
sub.unsubscribe().catch(e => console.warn('openPinnedMessagesRoom', e));
|
||||||
|
}
|
||||||
|
sub = newSub;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closePinnedMessagesRoom = function* closePinnedMessagesRoom() {
|
||||||
|
if (sub) {
|
||||||
|
yield sub.unsubscribe().catch(e => console.warn('closePinnedMessagesRoom sub', e));
|
||||||
|
}
|
||||||
|
if (newSub) {
|
||||||
|
yield newSub.unsubscribe().catch(e => console.warn('closePinnedMessagesRoom newSub', e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.PINNED_MESSAGES.OPEN, watchPinnedMessagesRoom);
|
yield takeLatest(types.PINNED_MESSAGES.OPEN, openPinnedMessagesRoom);
|
||||||
|
yield takeLatest(types.PINNED_MESSAGES.CLOSE, closePinnedMessagesRoom);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
import { take, takeLatest } from 'redux-saga/effects';
|
import { put, takeLatest } from 'redux-saga/effects';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { readyRoomFiles } from '../actions/roomFiles';
|
||||||
|
|
||||||
const watchRoomFiles = function* watchRoomFiles({ rid }) {
|
let sub;
|
||||||
const sub = yield RocketChat.subscribe('roomFiles', rid, 50);
|
let newSub;
|
||||||
yield take(types.ROOM_FILES.CLOSE);
|
|
||||||
sub.unsubscribe().catch(e => alert(e));
|
const openRoomFiles = function* openRoomFiles({ rid, limit }) {
|
||||||
|
newSub = yield RocketChat.subscribe('roomFiles', rid, limit);
|
||||||
|
yield put(readyRoomFiles());
|
||||||
|
if (sub) {
|
||||||
|
sub.unsubscribe().catch(e => console.warn('openRoomFiles', e));
|
||||||
|
}
|
||||||
|
sub = newSub;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeRoomFiles = function* closeRoomFiles() {
|
||||||
|
if (sub) {
|
||||||
|
yield sub.unsubscribe().catch(e => console.warn('closeRoomFiles sub', e));
|
||||||
|
}
|
||||||
|
if (newSub) {
|
||||||
|
yield newSub.unsubscribe().catch(e => console.warn('closeRoomFiles newSub', e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.ROOM_FILES.OPEN, watchRoomFiles);
|
yield takeLatest(types.ROOM_FILES.OPEN, openRoomFiles);
|
||||||
|
yield takeLatest(types.ROOM_FILES.CLOSE, closeRoomFiles);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
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 { FOREGROUND, BACKGROUND } from 'redux-enhancer-react-native-appstate';
|
import { BACKGROUND } from 'redux-enhancer-react-native-appstate';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
// import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
||||||
import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room';
|
import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room';
|
||||||
import { messagesRequest } from '../actions/messages';
|
import { messagesRequest } from '../actions/messages';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
@ -13,18 +13,18 @@ import * as NavigationService from '../containers/routes/NavigationService';
|
||||||
const leaveRoom = rid => RocketChat.leaveRoom(rid);
|
const leaveRoom = rid => RocketChat.leaveRoom(rid);
|
||||||
const eraseRoom = rid => RocketChat.eraseRoom(rid);
|
const eraseRoom = rid => RocketChat.eraseRoom(rid);
|
||||||
|
|
||||||
const getRooms = function* getRooms() {
|
// const getRooms = function* getRooms() {
|
||||||
return yield RocketChat.getRooms();
|
// return yield RocketChat.getRooms();
|
||||||
};
|
// };
|
||||||
|
|
||||||
const watchRoomsRequest = function* watchRoomsRequest() {
|
// const watchRoomsRequest = function* watchRoomsRequest() {
|
||||||
try {
|
// try {
|
||||||
yield call(getRooms);
|
// yield call(getRooms);
|
||||||
yield put(roomsSuccess());
|
// yield put(roomsSuccess());
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
yield put(roomsFailure(err.status));
|
// yield put(roomsFailure(err.status));
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const cancelTyping = function* cancelTyping(username) {
|
const cancelTyping = function* cancelTyping(username) {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -50,45 +50,46 @@ const usersTyping = function* usersTyping({ rid }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleMessageReceived = function* handleMessageReceived({ message }) {
|
const handleMessageReceived = function* handleMessageReceived({ message }) {
|
||||||
const room = yield select(state => state.room);
|
try {
|
||||||
|
const room = yield select(state => state.room);
|
||||||
|
|
||||||
if (message.rid === room.rid) {
|
if (message.rid === room.rid) {
|
||||||
database.write(() => {
|
database.write(() => {
|
||||||
database.create('messages', message, true);
|
database.create('messages', message, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
RocketChat.readMessages(room.rid);
|
RocketChat.readMessages(room.rid);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('handleMessageReceived', e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const watchRoomOpen = function* watchRoomOpen({ room }) {
|
const watchRoomOpen = function* watchRoomOpen({ room }) {
|
||||||
const auth = yield select(state => state.login.isAuthenticated);
|
yield put(messagesRequest({ ...room }));
|
||||||
if (!auth) {
|
// const { open } = yield race({
|
||||||
yield take(types.LOGIN.SUCCESS);
|
// messages: take(types.MESSAGES.SUCCESS),
|
||||||
}
|
// open: take(types.ROOM.OPEN)
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// if (open) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
yield put(messagesRequest({ rid: room.rid }));
|
|
||||||
|
|
||||||
const { open } = yield race({
|
|
||||||
messages: take(types.MESSAGES.SUCCESS),
|
|
||||||
open: take(types.ROOM.OPEN)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (open) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RocketChat.readMessages(room.rid);
|
RocketChat.readMessages(room.rid);
|
||||||
const subscriptions = yield Promise.all([RocketChat.subscribe('stream-room-messages', room.rid, false), RocketChat.subscribe('stream-notify-room', `${ room.rid }/typing`, false)]);
|
const sub = yield RocketChat.subscribeRoom(room);
|
||||||
|
// const subscriptions = yield Promise.all([RocketChat.subscribe('stream-room-messages', room.rid, false), RocketChat.subscribe('stream-notify-room', `${ room.rid }/typing`, false)]);
|
||||||
const thread = yield fork(usersTyping, { rid: room.rid });
|
const thread = yield fork(usersTyping, { rid: room.rid });
|
||||||
yield race({
|
yield race({
|
||||||
open: take(types.ROOM.OPEN),
|
open: take(types.ROOM.OPEN),
|
||||||
close: take(types.ROOM.CLOSE)
|
close: take(types.ROOM.CLOSE)
|
||||||
});
|
});
|
||||||
cancel(thread);
|
cancel(thread);
|
||||||
subscriptions.forEach((sub) => {
|
sub.stop();
|
||||||
sub.unsubscribe().catch(e => alert(e));
|
|
||||||
});
|
// subscriptions.forEach((sub) => {
|
||||||
|
// sub.unsubscribe().catch(e => alert(e));
|
||||||
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
const watchuserTyping = function* watchuserTyping({ status }) {
|
const watchuserTyping = function* watchuserTyping({ status }) {
|
||||||
|
@ -110,13 +111,13 @@ const watchuserTyping = function* watchuserTyping({ status }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRoom = function* updateRoom() {
|
// const updateRoom = function* updateRoom() {
|
||||||
const room = yield select(state => state.room);
|
// const room = yield select(state => state.room);
|
||||||
if (!room || !room.rid) {
|
// if (!room || !room.rid) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
yield put(messagesRequest({ rid: room.rid }));
|
// yield put(messagesRequest({ rid: room.rid }));
|
||||||
};
|
// };
|
||||||
|
|
||||||
const updateLastOpen = function* updateLastOpen() {
|
const updateLastOpen = function* updateLastOpen() {
|
||||||
yield put(setLastOpen());
|
yield put(setLastOpen());
|
||||||
|
@ -157,11 +158,10 @@ const handleEraseRoom = function* handleEraseRoom({ rid }) {
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping);
|
yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping);
|
||||||
yield takeLatest(types.LOGIN.SUCCESS, watchRoomsRequest);
|
|
||||||
yield takeLatest(types.ROOM.OPEN, watchRoomOpen);
|
yield takeLatest(types.ROOM.OPEN, watchRoomOpen);
|
||||||
yield takeEvery(types.ROOM.MESSAGE_RECEIVED, handleMessageReceived);
|
yield takeEvery(types.ROOM.MESSAGE_RECEIVED, handleMessageReceived);
|
||||||
yield takeLatest(FOREGROUND, updateRoom);
|
// yield takeLatest(FOREGROUND, updateRoom);
|
||||||
yield takeLatest(FOREGROUND, watchRoomsRequest);
|
// yield takeLatest(FOREGROUND, watchRoomsRequest);
|
||||||
yield takeLatest(BACKGROUND, updateLastOpen);
|
yield takeLatest(BACKGROUND, updateLastOpen);
|
||||||
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
||||||
yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
|
yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
|
||||||
|
|
|
@ -3,8 +3,9 @@ import { delay } from 'redux-saga';
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { SERVER } from '../actions/actionsTypes';
|
import { SERVER } from '../actions/actionsTypes';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import { connectRequest, disconnect, disconnect_by_user } from '../actions/connect';
|
import { connectRequest } from '../actions/connect';
|
||||||
import { changedServer, serverSuccess, serverFailure, serverRequest, setServer } from '../actions/server';
|
import { serverSuccess, serverFailure, serverRequest, setServer } from '../actions/server';
|
||||||
|
import { setRoles } from '../actions/roles';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/realm';
|
||||||
import * as NavigationService from '../containers/routes/NavigationService';
|
import * as NavigationService from '../containers/routes/NavigationService';
|
||||||
|
@ -14,16 +15,28 @@ const validate = function* validate(server) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectServer = function* selectServer({ server }) {
|
const selectServer = function* selectServer({ server }) {
|
||||||
yield database.setActiveDB(server);
|
try {
|
||||||
yield put(disconnect_by_user());
|
yield database.setActiveDB(server);
|
||||||
yield put(disconnect());
|
|
||||||
yield put(changedServer(server));
|
// yield RocketChat.disconnect();
|
||||||
yield call([AsyncStorage, 'setItem'], 'currentServer', server);
|
|
||||||
const settings = database.objects('settings');
|
yield call([AsyncStorage, 'setItem'], 'currentServer', server);
|
||||||
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
const settings = database.objects('settings');
|
||||||
const permissions = database.objects('permissions');
|
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
||||||
yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length))));
|
const permissions = database.objects('permissions');
|
||||||
yield put(connectRequest(server));
|
yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length))));
|
||||||
|
const emojis = database.objects('customEmojis');
|
||||||
|
yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length))));
|
||||||
|
const roles = database.objects('roles');
|
||||||
|
yield put(setRoles(roles.reduce((result, role) => {
|
||||||
|
result[role._id] = role.description;
|
||||||
|
return result;
|
||||||
|
}, {})));
|
||||||
|
|
||||||
|
yield put(connectRequest(server));
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('selectServer', e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateServer = function* validateServer({ server }) {
|
const validateServer = function* validateServer({ server }) {
|
||||||
|
@ -32,7 +45,7 @@ const validateServer = function* validateServer({ server }) {
|
||||||
yield call(validate, server);
|
yield call(validate, server);
|
||||||
yield put(serverSuccess());
|
yield put(serverSuccess());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.warn('validateServer', e);
|
||||||
yield put(serverFailure(e));
|
yield put(serverFailure(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
import { take, takeLatest } from 'redux-saga/effects';
|
import { put, takeLatest } from 'redux-saga/effects';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { readySnippetedMessages } from '../actions/snippetedMessages';
|
||||||
|
|
||||||
const watchSnippetedMessagesRoom = function* watchSnippetedMessagesRoom({ rid }) {
|
let sub;
|
||||||
const sub = yield RocketChat.subscribe('snippetedMessages', rid, 50);
|
let newSub;
|
||||||
yield take(types.SNIPPETED_MESSAGES.CLOSE);
|
|
||||||
sub.unsubscribe().catch(e => alert(e));
|
const openSnippetedMessagesRoom = function* openSnippetedMessagesRoom({ rid, limit }) {
|
||||||
|
newSub = yield RocketChat.subscribe('snippetedMessages', rid, limit);
|
||||||
|
yield put(readySnippetedMessages());
|
||||||
|
if (sub) {
|
||||||
|
sub.unsubscribe().catch(e => console.warn('openSnippetedMessagesRoom', e));
|
||||||
|
}
|
||||||
|
sub = newSub;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeSnippetedMessagesRoom = function* closeSnippetedMessagesRoom() {
|
||||||
|
if (sub) {
|
||||||
|
yield sub.unsubscribe().catch(e => console.warn('closeSnippetedMessagesRoom sub', e));
|
||||||
|
}
|
||||||
|
if (newSub) {
|
||||||
|
yield newSub.unsubscribe().catch(e => console.warn('closeSnippetedMessagesRoom newSub', e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.SNIPPETED_MESSAGES.OPEN, watchSnippetedMessagesRoom);
|
yield takeLatest(types.SNIPPETED_MESSAGES.OPEN, openSnippetedMessagesRoom);
|
||||||
|
yield takeLatest(types.SNIPPETED_MESSAGES.CLOSE, closeSnippetedMessagesRoom);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
import { take, takeLatest } from 'redux-saga/effects';
|
import { put, takeLatest } from 'redux-saga/effects';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { readyStarredMessages } from '../actions/starredMessages';
|
||||||
|
|
||||||
const watchStarredMessagesRoom = function* watchStarredMessagesRoom({ rid }) {
|
let sub;
|
||||||
const sub = yield RocketChat.subscribe('starredMessages', rid, 50);
|
let newSub;
|
||||||
yield take(types.STARRED_MESSAGES.CLOSE);
|
|
||||||
sub.unsubscribe().catch(e => alert(e));
|
const openStarredMessagesRoom = function* openStarredMessagesRoom({ rid, limit }) {
|
||||||
|
newSub = yield RocketChat.subscribe('starredMessages', rid, limit);
|
||||||
|
yield put(readyStarredMessages());
|
||||||
|
if (sub) {
|
||||||
|
sub.unsubscribe().catch(e => console.warn('openStarredMessagesRoom', e));
|
||||||
|
}
|
||||||
|
sub = newSub;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeStarredMessagesRoom = function* closeStarredMessagesRoom() {
|
||||||
|
if (sub) {
|
||||||
|
yield sub.unsubscribe().catch(e => console.warn('closeStarredMessagesRoom sub', e));
|
||||||
|
}
|
||||||
|
if (newSub) {
|
||||||
|
yield newSub.unsubscribe().catch(e => console.warn('closeStarredMessagesRoom newSub', e));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.STARRED_MESSAGES.OPEN, watchStarredMessagesRoom);
|
yield takeLatest(types.STARRED_MESSAGES.OPEN, openStarredMessagesRoom);
|
||||||
|
yield takeLatest(types.STARRED_MESSAGES.CLOSE, closeStarredMessagesRoom);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 20 KiB |
|
@ -2,7 +2,7 @@ export default function throttle(fn, threshhold = 250, scope) {
|
||||||
let last;
|
let last;
|
||||||
let deferTimer;
|
let deferTimer;
|
||||||
|
|
||||||
return (...args) => {
|
const _throttle = (...args) => {
|
||||||
const context = scope || this;
|
const context = scope || this;
|
||||||
|
|
||||||
const now = +new Date();
|
const now = +new Date();
|
||||||
|
@ -19,4 +19,8 @@ export default function throttle(fn, threshhold = 250, scope) {
|
||||||
fn.apply(context, args);
|
fn.apply(context, args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_throttle.stop = () => clearTimeout(deferTimer);
|
||||||
|
|
||||||
|
return _throttle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
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 { TextInput, View, Text, Switch, TouchableOpacity, SafeAreaView } from 'react-native';
|
import { View, Text, Switch, TouchableOpacity, SafeAreaView, ScrollView } from 'react-native';
|
||||||
import Spinner from 'react-native-loading-spinner-overlay';
|
|
||||||
|
|
||||||
|
import RCTextInput from '../containers/TextInput';
|
||||||
|
import Loading from '../containers/Loading';
|
||||||
import LoggedView from './View';
|
import LoggedView from './View';
|
||||||
import { createChannelRequest } from '../actions/createChannel';
|
import { createChannelRequest } from '../actions/createChannel';
|
||||||
import styles from './Styles';
|
import styles from './Styles';
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from '../presentation/KeyboardView';
|
||||||
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
|
|
||||||
@connect(
|
@connect(
|
||||||
state => ({
|
state => ({
|
||||||
createChannel: state.createChannel,
|
createChannel: state.createChannel,
|
||||||
users: state.createChannel.users
|
users: state.selectedUsers.users
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
create: data => dispatch(createChannelRequest(data))
|
create: data => dispatch(createChannelRequest(data))
|
||||||
|
@ -84,52 +86,52 @@ export default class CreateChannelView extends LoggedView {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
style={[styles.defaultViewBackground, { flex: 1 }]}
|
contentContainerStyle={styles.container}
|
||||||
contentContainerStyle={styles.defaultView}
|
keyboardVerticalOffset={128}
|
||||||
>
|
>
|
||||||
<SafeAreaView style={styles.formContainer}>
|
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||||
<Text style={styles.label_white}>Channel Name</Text>
|
<SafeAreaView>
|
||||||
<TextInput
|
<RCTextInput
|
||||||
value={this.state.channelName}
|
label='Channel Name'
|
||||||
style={styles.input_white}
|
value={this.state.channelName}
|
||||||
onChangeText={channelName => this.setState({ channelName })}
|
onChangeText={channelName => this.setState({ channelName })}
|
||||||
autoCorrect={false}
|
placeholder='Type the channel name here'
|
||||||
returnKeyType='done'
|
returnKeyType='done'
|
||||||
autoCapitalize='none'
|
autoFocus
|
||||||
autoFocus
|
/>
|
||||||
placeholder='Type the channel name here'
|
{this.renderChannelNameError()}
|
||||||
/>
|
{this.renderTypeSwitch()}
|
||||||
{this.renderChannelNameError()}
|
<Text
|
||||||
{this.renderTypeSwitch()}
|
style={[
|
||||||
<Text
|
styles.label_white,
|
||||||
style={[
|
{
|
||||||
styles.label_white,
|
color: '#9ea2a8',
|
||||||
{
|
flexGrow: 1,
|
||||||
color: '#9ea2a8',
|
paddingHorizontal: 0,
|
||||||
flexGrow: 1,
|
marginBottom: 20
|
||||||
paddingHorizontal: 0,
|
}
|
||||||
marginBottom: 20
|
]}
|
||||||
}
|
>
|
||||||
]}
|
{this.state.type ? (
|
||||||
>
|
'Everyone can access this channel'
|
||||||
{this.state.type ? (
|
) : (
|
||||||
'Everyone can access this channel'
|
'Just invited people can access this channel'
|
||||||
) : (
|
)}
|
||||||
'Just invited people can access this channel'
|
</Text>
|
||||||
)}
|
<TouchableOpacity
|
||||||
</Text>
|
onPress={() => this.submit()}
|
||||||
<TouchableOpacity
|
style={[
|
||||||
onPress={() => this.submit()}
|
styles.buttonContainer_white,
|
||||||
style={[styles.buttonContainer_white, styles.enabledButton]}
|
this.state.channelName.length === 0 || this.props.createChannel.isFetching
|
||||||
>
|
? styles.disabledButton
|
||||||
<Text style={styles.button_white}>CREATE</Text>
|
: styles.enabledButton
|
||||||
</TouchableOpacity>
|
]}
|
||||||
</SafeAreaView>
|
>
|
||||||
<Spinner
|
<Text style={styles.button_white}>CREATE</Text>
|
||||||
visible={this.props.createChannel.isFetching}
|
</TouchableOpacity>
|
||||||
textContent='Loading...'
|
<Loading visible={this.props.createChannel.isFetching} />
|
||||||
textStyle={{ color: '#FFF' }}
|
</SafeAreaView>
|
||||||
/>
|
</ScrollView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|