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:
|
||||
name: Test
|
||||
command: |
|
||||
npm test
|
||||
npm run test
|
||||
|
||||
- run:
|
||||
name: Codecov
|
||||
|
@ -151,9 +151,7 @@ jobs:
|
|||
name: Install NPM modules
|
||||
command: |
|
||||
rm -rf node_modules
|
||||
# npm install --save react-native@0.51
|
||||
npm install
|
||||
# npm install react-native
|
||||
|
||||
- run:
|
||||
name: Fix known build error
|
||||
|
@ -227,6 +225,7 @@ workflows:
|
|||
filters:
|
||||
branches:
|
||||
only:
|
||||
- beta
|
||||
- develop
|
||||
- master
|
||||
# - 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
|
||||
$ cd Rocket.Chat.ReactNative
|
||||
$ npm install -g react-native-cli
|
||||
$ yarn
|
||||
$ npm install
|
||||
```
|
||||
- Configuration
|
||||
```bash
|
||||
$ yarn fabric-ios --key="YOUR_API_KEY" --secret="YOUR_API_SECRET"
|
||||
$ yarn fabric-android --key="YOUR_API_KEY" --secret="YOUR_API_SECRET"
|
||||
$ npm run fabric-ios --key="YOUR_API_KEY" --secret="YOUR_API_SECRET"
|
||||
$ npm run fabric-android --key="YOUR_API_KEY" --secret="YOUR_API_SECRET"
|
||||
```
|
||||
|
||||
- Run application
|
||||
```bash
|
||||
$ yarn ios
|
||||
$ npm run ios
|
||||
```
|
||||
```bash
|
||||
$ yarn android
|
||||
$ npm run android
|
||||
```
|
||||
|
||||
# Storybook
|
||||
|
|
|
@ -46,7 +46,7 @@ exports[`render channel 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#00BCD4",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -147,10 +147,8 @@ exports[`render channel 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -206,7 +204,7 @@ exports[`render no icon 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#3F51B5",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -307,10 +305,8 @@ exports[`render no icon 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -366,7 +362,7 @@ exports[`render private group 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#FF9800",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -467,10 +463,8 @@ exports[`render private group 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -527,7 +521,7 @@ exports[`render unread +999 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#3F51B5",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -561,6 +555,7 @@ exports[`render unread +999 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -568,6 +563,8 @@ exports[`render unread +999 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -639,10 +636,8 @@ exports[`render unread +999 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -721,7 +716,7 @@ exports[`render unread 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#3F51B5",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -755,6 +750,7 @@ exports[`render unread 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -762,6 +758,8 @@ exports[`render unread 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -833,10 +831,8 @@ exports[`render unread 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -915,7 +911,7 @@ exports[`renders correctly 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#3F51B5",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -949,6 +945,7 @@ exports[`renders correctly 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -956,6 +953,8 @@ exports[`renders correctly 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -1027,10 +1026,8 @@ exports[`renders correctly 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -12,7 +12,7 @@ exports[`Storyshots Avatar avatar 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#3F51B5",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 25,
|
||||
"width": 25,
|
||||
},
|
||||
|
@ -48,7 +48,7 @@ exports[`Storyshots Avatar avatar 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#9C27B0",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 40,
|
||||
"width": 40,
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ exports[`Storyshots Avatar avatar 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#9C27B0",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 30,
|
||||
"width": 30,
|
||||
},
|
||||
|
@ -198,7 +198,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#8BC34A",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -232,6 +232,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -239,6 +240,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -310,10 +313,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -364,7 +365,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#8BC34A",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -398,6 +399,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -405,6 +407,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -480,10 +484,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -534,7 +536,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#8BC34A",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -568,6 +570,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -575,6 +578,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -646,10 +651,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -723,7 +726,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#795548",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -757,6 +760,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -764,6 +768,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -839,10 +845,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -916,7 +920,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#795548",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -950,6 +954,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -957,6 +962,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -1028,10 +1035,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -1105,7 +1110,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#795548",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -1139,6 +1144,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -1146,6 +1152,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -1217,10 +1225,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -1294,7 +1300,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#795548",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -1328,6 +1334,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -1335,6 +1342,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -1406,10 +1415,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -1483,7 +1490,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#795548",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -1517,6 +1524,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -1524,6 +1532,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -1595,10 +1605,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
@ -1672,7 +1680,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#E91E63",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -1706,6 +1714,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -1713,6 +1722,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -1784,10 +1795,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -1838,7 +1847,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": "#9C27B0",
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -1872,6 +1881,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -1879,6 +1889,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -1950,10 +1962,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -2004,7 +2014,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
},
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"borderRadius": 4,
|
||||
"borderRadius": 2,
|
||||
"height": 46,
|
||||
"width": 46,
|
||||
},
|
||||
|
@ -2038,6 +2048,7 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"height": 16,
|
||||
"width": 16,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 3,
|
||||
|
@ -2045,6 +2056,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
"position": "absolute",
|
||||
"right": -3,
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
Object {
|
||||
"backgroundColor": "#cbced1",
|
||||
},
|
||||
|
@ -2116,10 +2129,8 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"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
|
||||
* 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.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
def enableProguardInReleaseBuilds = true
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
|
@ -98,11 +98,12 @@ android {
|
|||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode VERSIONCODE as Integer
|
||||
versionName "1.1"
|
||||
versionName "1"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
if (project.hasProperty('KEYSTORE')) {
|
||||
|
@ -123,9 +124,15 @@ android {
|
|||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
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
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
# 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).
|
||||
-dontobfuscate
|
||||
# -dontobfuscate
|
||||
|
||||
# React Native
|
||||
|
||||
|
@ -49,6 +49,7 @@
|
|||
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
|
||||
|
||||
-dontwarn com.facebook.react.**
|
||||
-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
|
||||
|
||||
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
|
||||
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
|
||||
|
@ -68,3 +69,25 @@
|
|||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-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:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme"
|
||||
android:resizeableActivity="true"
|
||||
android:largeHeap="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
|
|
|
@ -10,4 +10,6 @@ if (__DEV__) {
|
|||
.use(reactotronRedux())
|
||||
.use(sagaPlugin())
|
||||
.connect();
|
||||
// Running on android device
|
||||
// $ adb reverse tcp:9090 tcp:9090
|
||||
}
|
||||
|
|
|
@ -74,16 +74,8 @@ export const MESSAGES = createRequestTypes('MESSAGES', [
|
|||
'CLEAR_INPUT',
|
||||
'TOGGLE_REACTION_PICKER'
|
||||
]);
|
||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [
|
||||
...defaultTypes,
|
||||
'REQUEST_USERS',
|
||||
'SUCCESS_USERS',
|
||||
'FAILURE_USERS',
|
||||
'SET_USERS',
|
||||
'ADD_USER',
|
||||
'REMOVE_USER',
|
||||
'RESET'
|
||||
]);
|
||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||
export const SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER', 'REMOVE_USER', 'RESET', 'SET_LOADING']);
|
||||
export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']);
|
||||
export const SERVER = createRequestTypes('SERVER', [
|
||||
...defaultTypes,
|
||||
|
@ -96,11 +88,11 @@ export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DI
|
|||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET']);
|
||||
export const ROLES = createRequestTypes('ROLES', ['SET']);
|
||||
export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNSTARRED']);
|
||||
export const PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNPINNED']);
|
||||
export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||
export const ROOM_FILES = createRequestTypes('ROOM_FILES', ['OPEN', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||
export const STARRED_MESSAGES = createRequestTypes('STARRED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNSTARRED']);
|
||||
export const PINNED_MESSAGES = createRequestTypes('PINNED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED', 'MESSAGE_UNPINNED']);
|
||||
export const MENTIONED_MESSAGES = createRequestTypes('MENTIONED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||
export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||
export const ROOM_FILES = createRequestTypes('ROOM_FILES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']);
|
||||
|
||||
export const INCREMENT = 'INCREMENT';
|
||||
export const DECREMENT = 'DECREMENT';
|
||||
|
|
|
@ -20,51 +20,3 @@ export function createChannelFailure(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';
|
||||
|
||||
export function openMentionedMessages(rid) {
|
||||
export function openMentionedMessages(rid, limit) {
|
||||
return {
|
||||
type: types.MENTIONED_MESSAGES.OPEN,
|
||||
rid
|
||||
rid,
|
||||
limit
|
||||
};
|
||||
}
|
||||
|
||||
export function readyMentionedMessages() {
|
||||
return {
|
||||
type: types.MENTIONED_MESSAGES.READY
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function closeMentionedMessages() {
|
||||
return {
|
||||
type: types.MENTIONED_MESSAGES.CLOSE
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function messagesRequest({ rid }) {
|
||||
export function messagesRequest(room) {
|
||||
return {
|
||||
type: types.MESSAGES.REQUEST,
|
||||
rid
|
||||
room
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function openPinnedMessages(rid) {
|
||||
export function openPinnedMessages(rid, limit) {
|
||||
return {
|
||||
type: types.PINNED_MESSAGES.OPEN,
|
||||
rid
|
||||
rid,
|
||||
limit
|
||||
};
|
||||
}
|
||||
|
||||
export function readyPinnedMessages() {
|
||||
return {
|
||||
type: types.PINNED_MESSAGES.READY
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function closePinnedMessages() {
|
||||
return {
|
||||
type: types.PINNED_MESSAGES.CLOSE
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function openRoomFiles(rid) {
|
||||
export function openRoomFiles(rid, limit) {
|
||||
return {
|
||||
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';
|
||||
|
||||
export function openSnippetedMessages(rid) {
|
||||
export function openSnippetedMessages(rid, limit) {
|
||||
return {
|
||||
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';
|
||||
|
||||
export function openStarredMessages(rid) {
|
||||
export function openStarredMessages(rid, limit) {
|
||||
return {
|
||||
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 ESLINT_FIX = null;
|
||||
export const COLOR_DANGER = '#f5455c';
|
||||
export const COLOR_BUTTON_PRIMARY = '#2D6AEA';
|
||||
export const COLOR_TEXT = '#292E35';
|
||||
export const STATUS_COLORS = {
|
||||
online: '#2de0a5',
|
||||
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() {
|
||||
const {
|
||||
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar, type = 'd'
|
||||
text = '', size = 25, baseUrl, borderRadius = 2, style, avatar, type = 'd'
|
||||
} = this.props;
|
||||
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
||||
|
||||
|
|
|
@ -6,11 +6,7 @@ import { connect } from 'react-redux';
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
bannerContainer: {
|
||||
backgroundColor: '#ddd',
|
||||
position: 'absolute',
|
||||
top: '0%',
|
||||
zIndex: 10,
|
||||
width: '100%'
|
||||
backgroundColor: '#ddd'
|
||||
},
|
||||
bannerText: {
|
||||
textAlign: 'center',
|
||||
|
@ -21,7 +17,8 @@ const styles = StyleSheet.create({
|
|||
@connect(state => ({
|
||||
connecting: state.meteor.connecting,
|
||||
authenticating: state.login.isFetching,
|
||||
offline: !state.meteor.connected
|
||||
offline: !state.meteor.connected,
|
||||
logged: !!state.login.token
|
||||
}))
|
||||
|
||||
export default class Banner extends React.PureComponent {
|
||||
|
@ -31,7 +28,9 @@ export default class Banner extends React.PureComponent {
|
|||
offline: PropTypes.bool
|
||||
}
|
||||
render() {
|
||||
const { connecting, authenticating, offline } = this.props;
|
||||
const {
|
||||
connecting, authenticating, offline, logged
|
||||
} = this.props;
|
||||
|
||||
if (offline) {
|
||||
return (
|
||||
|
@ -40,6 +39,7 @@ export default class Banner extends React.PureComponent {
|
|||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (connecting) {
|
||||
return (
|
||||
<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 { ScrollView } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
import _ from 'lodash';
|
||||
import map from 'lodash/map';
|
||||
import { emojify } from 'react-emojione';
|
||||
import TabBar from './TabBar';
|
||||
import EmojiCategory from './EmojiCategory';
|
||||
|
@ -78,7 +78,7 @@ export default class EmojiPicker extends Component {
|
|||
return emojiRow.length ? emojiRow[0].count + 1 : 1;
|
||||
}
|
||||
updateFrequentlyUsed() {
|
||||
const frequentlyUsed = _.map(this.frequentlyUsed.slice(), (item) => {
|
||||
const frequentlyUsed = map(this.frequentlyUsed.slice(), (item) => {
|
||||
if (item.isCustom) {
|
||||
return item;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ export default class EmojiPicker extends Component {
|
|||
}
|
||||
|
||||
updateCustomEmojis() {
|
||||
const customEmojis = _.map(this.customEmojis.slice(), item =>
|
||||
const customEmojis = map(this.customEmojis.slice(), item =>
|
||||
({ content: item.name, extension: item.extension, isCustom: true }));
|
||||
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) {
|
||||
this.setState({ text });
|
||||
this.props.typing(text.length > 0);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const { start, end } = this.component._lastNativeSelection;
|
||||
|
@ -174,11 +175,11 @@ export default class MessageBox extends React.PureComponent {
|
|||
};
|
||||
ImagePicker.showImagePicker(options, (response) => {
|
||||
if (response.didCancel) {
|
||||
console.log('User cancelled image picker');
|
||||
console.warn('User cancelled image picker');
|
||||
} else if (response.error) {
|
||||
console.log('ImagePicker Error: ', response.error);
|
||||
console.warn('ImagePicker Error: ', response.error);
|
||||
} else if (response.customButton) {
|
||||
console.log('User tapped custom button: ', response.customButton);
|
||||
console.warn('User tapped custom button: ', response.customButton);
|
||||
} else {
|
||||
const fileInfo = {
|
||||
name: response.fileName,
|
||||
|
@ -278,7 +279,7 @@ export default class MessageBox extends React.PureComponent {
|
|||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('spotlight canceled');
|
||||
console.warn('spotlight canceled');
|
||||
} finally {
|
||||
delete this.oldPromise;
|
||||
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.setState({ mentions: [...rooms.slice(), ...results.rooms] });
|
||||
} catch (e) {
|
||||
console.log('spotlight canceled');
|
||||
console.warn('spotlight canceled');
|
||||
} finally {
|
||||
delete this.oldPromise;
|
||||
}
|
||||
|
@ -454,7 +455,6 @@ export default class MessageBox extends React.PureComponent {
|
|||
style={{ margin: 8 }}
|
||||
text={item.username || item.name}
|
||||
size={30}
|
||||
baseUrl={this.props.baseUrl}
|
||||
/>,
|
||||
<Text key='mention-item-name'>{ item.username || item.name }</Text>
|
||||
]
|
||||
|
|
|
@ -22,8 +22,9 @@ export default StyleSheet.create({
|
|||
maxHeight: 120,
|
||||
flexGrow: 1,
|
||||
width: 1,
|
||||
paddingTop: 15,
|
||||
paddingBottom: 15,
|
||||
// paddingVertical: 12, needs to be paddingTop/paddingBottom because of iOS/Android's TextInput differences on rendering
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0
|
||||
},
|
||||
|
@ -35,7 +36,7 @@ export default StyleSheet.create({
|
|||
fontSize: 20,
|
||||
textAlign: 'center',
|
||||
padding: 15,
|
||||
paddingHorizontal: 21,
|
||||
paddingHorizontal: 12,
|
||||
flex: 0
|
||||
},
|
||||
mentionList: {
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
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 Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
||||
import sharedStyles from '../views/Styles';
|
||||
import { COLOR_DANGER } from '../constants/colors';
|
||||
import { COLOR_DANGER, COLOR_TEXT } from '../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
inputContainer: {
|
||||
marginBottom: 20
|
||||
marginBottom: 15
|
||||
},
|
||||
label: {
|
||||
marginBottom: 4,
|
||||
fontSize: 16
|
||||
marginBottom: 10,
|
||||
color: COLOR_TEXT,
|
||||
fontSize: 14,
|
||||
fontWeight: '700'
|
||||
},
|
||||
input: {
|
||||
fontSize: 14,
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
// paddingTop: 5,
|
||||
// paddingBottom: 5,
|
||||
paddingHorizontal: 10,
|
||||
borderWidth: 2,
|
||||
borderRadius: 2,
|
||||
borderRadius: 4,
|
||||
backgroundColor: 'white',
|
||||
borderColor: 'rgba(0,0,0,.15)',
|
||||
color: 'black'
|
||||
|
@ -33,14 +38,23 @@ const styles = StyleSheet.create({
|
|||
borderColor: COLOR_DANGER
|
||||
},
|
||||
wrap: {
|
||||
flex: 1,
|
||||
position: 'relative'
|
||||
},
|
||||
icon: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
padding: 10,
|
||||
color: 'rgba(0,0,0,.45)'
|
||||
color: 'rgba(0,0,0,.45)',
|
||||
height: 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 = {
|
||||
label: PropTypes.string,
|
||||
error: PropTypes.object,
|
||||
secureTextEntry: PropTypes.bool
|
||||
secureTextEntry: PropTypes.bool,
|
||||
containerStyle: ViewPropTypes.style,
|
||||
inputStyle: PropTypes.object,
|
||||
inputRef: PropTypes.func
|
||||
}
|
||||
static defaultProps = {
|
||||
error: {}
|
||||
|
@ -58,28 +75,40 @@ export default class RCTextInput extends React.PureComponent {
|
|||
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() {
|
||||
const {
|
||||
label, error, secureTextEntry, ...inputProps
|
||||
label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, ...inputProps
|
||||
} = this.props;
|
||||
const { showPassword } = this.state;
|
||||
return (
|
||||
<View style={styles.inputContainer}>
|
||||
<View style={[styles.inputContainer, containerStyle]}>
|
||||
{ label && <Text style={[styles.label, error.error && styles.labelError]}>{label}</Text> }
|
||||
<View style={styles.wrap}>
|
||||
<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}
|
||||
autoCapitalize='none'
|
||||
underlineColorAndroid='transparent'
|
||||
secureTextEntry={secureTextEntry && !showPassword}
|
||||
{...inputProps}
|
||||
/>
|
||||
{secureTextEntry && this.icon}
|
||||
{iconLeft && this.iconLeft(iconLeft)}
|
||||
{secureTextEntry && this.iconPassword(showPassword ? 'eye-off' : 'eye')}
|
||||
</View>
|
||||
{error.error && <Text style={sharedStyles.error}>{error.reason}</Text>}
|
||||
</View>
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import React from 'react';
|
||||
|
||||
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';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
typing: {
|
||||
|
||||
transform: [{ scaleY: -1 }],
|
||||
fontWeight: 'bold',
|
||||
paddingHorizontal: 15,
|
||||
height: 25
|
||||
},
|
||||
emptySpace: {
|
||||
height: 5
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -18,11 +19,13 @@ const styles = StyleSheet.create({
|
|||
username: state.login.user && state.login.user.username,
|
||||
usersTyping: state.room.usersTyping
|
||||
}))
|
||||
|
||||
export default class Typing extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return this.props.usersTyping.join() !== nextProps.usersTyping.join();
|
||||
}
|
||||
componentWillUpdate() {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
}
|
||||
onPress = () => {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
@ -31,7 +34,13 @@ export default class Typing extends React.Component {
|
|||
return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : '';
|
||||
}
|
||||
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 Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import Slider from 'react-native-slider';
|
||||
import { connect } from 'react-redux';
|
||||
import Markdown from './Markdown';
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
audioContainer: {
|
||||
flex: 1,
|
||||
|
@ -61,6 +61,9 @@ const formatTime = (t = 0, duration = 0) => {
|
|||
return `${ formattedMinutes }:${ formattedSeconds }`;
|
||||
};
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
}))
|
||||
export default class Audio extends React.PureComponent {
|
||||
static propTypes = {
|
||||
file: PropTypes.object.isRequired,
|
||||
|
@ -115,8 +118,8 @@ export default class Audio extends React.PureComponent {
|
|||
const { uri, paused } = this.state;
|
||||
const { description } = this.props.file;
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.audioContainer}>
|
||||
[
|
||||
<View key='audio' style={styles.audioContainer}>
|
||||
<Video
|
||||
ref={(ref) => {
|
||||
this.player = ref;
|
||||
|
@ -154,9 +157,9 @@ export default class Audio extends React.PureComponent {
|
|||
onValueChange={value => this.setState({ currentTime: value })}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Markdown msg={description} />
|
||||
</View>
|
||||
</View>,
|
||||
<Markdown key='description' msg={description} />
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,12 @@ import React from 'react';
|
|||
import { Text, ViewPropTypes } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { emojify } from 'react-emojione';
|
||||
import { connect } from 'react-redux';
|
||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||
|
||||
@connect(state => ({
|
||||
customEmojis: state.customEmojis
|
||||
}))
|
||||
export default class Emoji extends React.PureComponent {
|
||||
static propTypes = {
|
||||
content: PropTypes.string,
|
||||
|
|
|
@ -1,41 +1,30 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
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 Markdown from './Markdown';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
height: 320,
|
||||
borderColor: '#ccc',
|
||||
borderWidth: 1,
|
||||
borderRadius: 6
|
||||
flexDirection: 'column'
|
||||
},
|
||||
image: {
|
||||
flex: 1,
|
||||
height: undefined,
|
||||
width: undefined,
|
||||
resizeMode: 'contain'
|
||||
width: 320,
|
||||
height: 200,
|
||||
resizeMode: 'cover'
|
||||
},
|
||||
labelContainer: {
|
||||
height: 62,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
imageName: {
|
||||
fontSize: 12,
|
||||
alignSelf: 'center',
|
||||
fontStyle: 'italic'
|
||||
},
|
||||
message: {
|
||||
alignSelf: 'center',
|
||||
fontWeight: 'bold'
|
||||
alignItems: 'flex-start'
|
||||
}
|
||||
});
|
||||
|
||||
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 = {
|
||||
file: PropTypes.object.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
|
@ -45,8 +34,9 @@ export default class Image extends React.PureComponent {
|
|||
state = { modalVisible: false };
|
||||
|
||||
getDescription() {
|
||||
if (this.props.file.description) {
|
||||
return <Text style={styles.message}>{this.props.file.description}</Text>;
|
||||
const { file, customEmojis } = this.props;
|
||||
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 img = `${ baseUrl }${ file.image_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||
return (
|
||||
<View>
|
||||
[
|
||||
<TouchableOpacity
|
||||
key='image'
|
||||
onPress={() => this._onPressButton()}
|
||||
style={styles.button}
|
||||
>
|
||||
|
@ -69,18 +60,16 @@ export default class Image extends React.PureComponent {
|
|||
style={styles.image}
|
||||
source={{ uri: encodeURI(img) }}
|
||||
/>
|
||||
<View style={styles.labelContainer}>
|
||||
<Text style={styles.imageName}>{this.props.file.title}</Text>
|
||||
{this.getDescription()}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>,
|
||||
<PhotoModal
|
||||
key='modal'
|
||||
title={this.props.file.title}
|
||||
image={img}
|
||||
isVisible={this.state.modalVisible}
|
||||
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 SimpleMarkdown from 'simple-markdown';
|
||||
import { emojify } from 'react-emojione';
|
||||
import { connect } from 'react-redux';
|
||||
import styles from './styles';
|
||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||
|
||||
|
@ -17,14 +18,6 @@ const BlockCode = ({ node, state }) => (
|
|||
);
|
||||
const mentionStyle = { color: '#13679a' };
|
||||
|
||||
const Markdown = ({
|
||||
msg, customEmojis, style, markdownStyle, customRules, renderInline
|
||||
}) => {
|
||||
if (!msg) {
|
||||
return null;
|
||||
}
|
||||
msg = emojify(msg, { output: 'unicode' });
|
||||
|
||||
const defaultRules = {
|
||||
username: {
|
||||
order: -1,
|
||||
|
@ -96,7 +89,33 @@ const Markdown = ({
|
|||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
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-_.]+):/),
|
||||
|
@ -119,24 +138,19 @@ const Markdown = ({
|
|||
}
|
||||
return element;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const codeStyle = StyleSheet.flatten(styles.codeStyle);
|
||||
style = StyleSheet.flatten(style);
|
||||
return (
|
||||
<EasyMarkdown
|
||||
style={{ marginBottom: 0, ...style }}
|
||||
markdownStyles={{ code: codeStyle, ...markdownStyle }}
|
||||
rules={{ ...defaultRules, ...customRules }}
|
||||
},
|
||||
...defaultRules,
|
||||
...customRules
|
||||
}}
|
||||
renderInline={renderInline}
|
||||
>{msg}
|
||||
>{m}
|
||||
</EasyMarkdown>
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Markdown.propTypes = {
|
||||
msg: PropTypes.string.isRequired,
|
||||
msg: PropTypes.string,
|
||||
customEmojis: PropTypes.object,
|
||||
// eslint-disable-next-line react/no-typos
|
||||
style: ViewPropTypes.style,
|
||||
|
@ -149,5 +163,3 @@ BlockCode.propTypes = {
|
|||
node: 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 Modal from 'react-native-modal';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { connect } from 'react-redux';
|
||||
import Emoji from './Emoji';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -52,6 +53,10 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
const standardEmojiStyle = { fontSize: 20 };
|
||||
const customEmojiStyle = { width: 20, height: 20 };
|
||||
|
||||
@connect(state => ({
|
||||
customEmojis: state.customEmojis
|
||||
}))
|
||||
export default class ReactionsModal extends React.PureComponent {
|
||||
static propTypes = {
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
|
|
|
@ -7,7 +7,9 @@ import Avatar from '../Avatar';
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
username: {
|
||||
fontWeight: 'bold'
|
||||
color: '#000',
|
||||
fontWeight: '400',
|
||||
fontSize: 14
|
||||
},
|
||||
usernameView: {
|
||||
flexDirection: 'row',
|
||||
|
@ -22,7 +24,8 @@ const styles = StyleSheet.create({
|
|||
time: {
|
||||
fontSize: 10,
|
||||
color: '#888',
|
||||
paddingLeft: 5
|
||||
paddingLeft: 5,
|
||||
fontWeight: '400'
|
||||
},
|
||||
edited: {
|
||||
marginLeft: 5,
|
||||
|
@ -35,11 +38,10 @@ export default class User extends React.PureComponent {
|
|||
static propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
Message_TimeFormat: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func,
|
||||
baseUrl: PropTypes.string
|
||||
onPress: PropTypes.func
|
||||
}
|
||||
|
||||
renderEdited(item) {
|
||||
renderEdited = (item) => {
|
||||
if (!item.editedBy) {
|
||||
return null;
|
||||
}
|
||||
|
@ -50,7 +52,6 @@ export default class User extends React.PureComponent {
|
|||
style={{ marginLeft: 5 }}
|
||||
text={item.editedBy.username}
|
||||
size={20}
|
||||
baseUrl={this.props.baseUrl}
|
||||
avatar={item.avatar}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react';
|
||||
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 VideoPlayer from 'react-native-video-controls';
|
||||
import { connect } from 'react-redux';
|
||||
import Markdown from './Markdown';
|
||||
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 {
|
||||
static propTypes = {
|
||||
file: PropTypes.object.isRequired,
|
||||
|
@ -55,18 +59,20 @@ export default class Video extends React.PureComponent {
|
|||
const { baseUrl, user } = this.props;
|
||||
const uri = `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||
return (
|
||||
<View>
|
||||
[
|
||||
<TouchableOpacity
|
||||
key='button'
|
||||
style={styles.container}
|
||||
onPress={() => this.open()}
|
||||
>
|
||||
<Image
|
||||
source={require('../../../static/images/logo.png')}
|
||||
source={require('../../static/images/logo.png')}
|
||||
style={styles.image}
|
||||
/>
|
||||
<Markdown msg={description} />
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>,
|
||||
<Modal
|
||||
key='modal'
|
||||
isVisible={isVisible}
|
||||
style={styles.modal}
|
||||
supportedOrientations={['portrait', 'landscape']}
|
||||
|
@ -78,7 +84,7 @@ export default class Video extends React.PureComponent {
|
|||
disableVolume
|
||||
/>
|
||||
</Modal>
|
||||
</View>
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React from 'react';
|
||||
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 Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import moment from 'moment';
|
||||
import equal from 'deep-equal';
|
||||
import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||
|
||||
import { actionsShow, errorActionsShow, toggleReactionPicker } from '../../actions/messages';
|
||||
import Image from './Image';
|
||||
import User from './User';
|
||||
import Avatar from '../Avatar';
|
||||
|
@ -18,13 +17,54 @@ import Url from './Url';
|
|||
import Reply from './Reply';
|
||||
import ReactionsModal from './ReactionsModal';
|
||||
import Emoji from './Emoji';
|
||||
import messageStatus from '../../constants/messagesStatus';
|
||||
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 => ({
|
||||
message: state.messages.message,
|
||||
editing: state.messages.editing,
|
||||
customEmojis: state.customEmojis
|
||||
customEmojis: state.customEmojis,
|
||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||
Message_GroupingPeriod: state.settings.Message_GroupingPeriod
|
||||
}), dispatch => ({
|
||||
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
|
||||
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
|
||||
|
@ -35,13 +75,13 @@ export default class Message extends React.Component {
|
|||
status: PropTypes.any,
|
||||
item: PropTypes.object.isRequired,
|
||||
reactions: PropTypes.any.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
Message_TimeFormat: PropTypes.string.isRequired,
|
||||
Message_GroupingPeriod: PropTypes.number.isRequired,
|
||||
customTimeFormat: PropTypes.string,
|
||||
message: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
editing: PropTypes.bool,
|
||||
errorActionsShow: PropTypes.func,
|
||||
customEmojis: PropTypes.object,
|
||||
toggleReactionPicker: PropTypes.func,
|
||||
onReactionPress: PropTypes.func,
|
||||
style: ViewPropTypes.style,
|
||||
|
@ -63,28 +103,35 @@ export default class Message extends React.Component {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (!equal(this.props.reactions, nextProps.reactions)) {
|
||||
return true;
|
||||
}
|
||||
if (this.state.reactionsModal !== nextState.reactionsModal) {
|
||||
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 = () => {
|
||||
KeyboardUtils.dismiss();
|
||||
}
|
||||
|
||||
onLongPress() {
|
||||
onLongPress = () => {
|
||||
this.props.onLongPress(this.parseMessage());
|
||||
}
|
||||
|
||||
onErrorPress() {
|
||||
onErrorPress = () => {
|
||||
this.props.errorActionsShow(this.parseMessage());
|
||||
}
|
||||
|
||||
onReactionPress(emoji) {
|
||||
onReactionPress = (emoji) => {
|
||||
this.props.onReactionPress(emoji, this.props.item._id);
|
||||
}
|
||||
onClose() {
|
||||
|
@ -95,45 +142,9 @@ export default class Message extends React.Component {
|
|||
Vibration.vibrate(50);
|
||||
}
|
||||
|
||||
getInfoMessage() {
|
||||
let message = '';
|
||||
const {
|
||||
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;
|
||||
get timeFormat() {
|
||||
const { customTimeFormat, Message_TimeFormat } = this.props;
|
||||
return customTimeFormat || Message_TimeFormat;
|
||||
}
|
||||
|
||||
parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
|
||||
|
@ -163,64 +174,97 @@ export default class Message extends React.Component {
|
|||
}
|
||||
|
||||
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() {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const file = this.props.item.attachments[0];
|
||||
const { baseUrl, user } = this.props;
|
||||
const { user } = this.props;
|
||||
if (file.image_type) {
|
||||
return <Image file={file} baseUrl={baseUrl} user={user} />;
|
||||
} else if (file.audio_type) {
|
||||
return <Audio file={file} baseUrl={baseUrl} user={user} />;
|
||||
} else if (file.video_type) {
|
||||
return <Video file={file} baseUrl={baseUrl} user={user} />;
|
||||
return <Image file={file} user={user} />;
|
||||
}
|
||||
if (file.audio_type) {
|
||||
return <Audio file={file} 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() {
|
||||
if (this.isInfoMessage()) {
|
||||
return <Text style={styles.textInfo}>{this.getInfoMessage()}</Text>;
|
||||
}
|
||||
const { item, customEmojis, baseUrl } = this.props;
|
||||
return <Markdown msg={item.msg} customEmojis={customEmojis} baseUrl={baseUrl} />;
|
||||
}
|
||||
|
||||
renderUrl() {
|
||||
if (this.props.item.urls.length === 0) {
|
||||
renderUrl = () => {
|
||||
const { urls } = this.props.item;
|
||||
if (urls.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.props.item.urls.map(url => (
|
||||
return urls.map(url => (
|
||||
<Url url={url} key={url.url} />
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
renderError = () => {
|
||||
if (!this.hasError()) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity onPress={() => this.onErrorPress()}>
|
||||
<Icon name='error-outline' color='red' size={20} style={{ padding: 10, paddingRight: 12, paddingLeft: 0 }} />
|
||||
<TouchableOpacity onPress={this.onErrorPress}>
|
||||
<Icon name='error-outline' color='red' size={20} style={styles.errorIcon} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
renderReaction(reaction) {
|
||||
renderReaction = (reaction) => {
|
||||
const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
|
||||
const reactedContainerStyle = reacted ? { borderColor: '#bde1fe', backgroundColor: '#f3f9ff' } : {};
|
||||
const reactedCount = reacted ? { color: '#4fb0fc' } : {};
|
||||
const reactedContainerStyle = reacted && styles.reactedContainer;
|
||||
const reactedCount = reacted && styles.reactedCountText;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => this.onReactionPress(reaction.emoji)}
|
||||
|
@ -232,7 +276,6 @@ export default class Message extends React.Component {
|
|||
content={reaction.emoji}
|
||||
standardEmojiStyle={styles.reactionEmoji}
|
||||
customEmojiStyle={styles.reactionCustomEmoji}
|
||||
customEmojis={this.props.customEmojis}
|
||||
/>
|
||||
<Text style={[styles.reactionCount, reactedCount]}>{ reaction.usernames.length }</Text>
|
||||
</View>
|
||||
|
@ -246,7 +289,7 @@ export default class Message extends React.Component {
|
|||
}
|
||||
return (
|
||||
<View style={styles.reactionsContainer}>
|
||||
{this.props.item.reactions.map(reaction => this.renderReaction(reaction))}
|
||||
{this.props.item.reactions.map(this.renderReaction)}
|
||||
<TouchableOpacity
|
||||
onPress={() => this.props.toggleReactionPicker(this.parseMessage())}
|
||||
key='add-reaction'
|
||||
|
@ -260,57 +303,42 @@ export default class Message extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
item, message, editing, baseUrl, customEmojis, style, archived
|
||||
item, message, editing, style, archived
|
||||
} = this.props;
|
||||
const username = item.alias || item.u.username;
|
||||
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 (
|
||||
<TouchableHighlight
|
||||
onPress={() => this.onPress()}
|
||||
onLongPress={() => this.onLongPress()}
|
||||
disabled={this.isDeleted() || this.hasError() || archived}
|
||||
<Touch
|
||||
onPress={this.onPress}
|
||||
onLongPress={this.onLongPress}
|
||||
disabled={this.isInfoMessage() || this.hasError() || archived}
|
||||
underlayColor='#FFFFFF'
|
||||
activeOpacity={0.3}
|
||||
style={[styles.message, isEditing ? styles.editing : null, style]}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
>
|
||||
<View style={[styles.message, isEditing && styles.editing, style]}>
|
||||
{this.renderHeader(username)}
|
||||
<View style={styles.flex}>
|
||||
{this.renderError()}
|
||||
<View style={[this.isTemp() && { opacity: 0.3 }, styles.flex]}>
|
||||
<Avatar
|
||||
style={styles.avatar}
|
||||
text={item.avatar ? '' : username}
|
||||
size={40}
|
||||
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()}
|
||||
<View style={[styles.messageContent, this.isTemp() && styles.temp]}>
|
||||
{this.renderContent()}
|
||||
{this.renderAttachment()}
|
||||
{this.renderUrl()}
|
||||
{this.renderReactions()}
|
||||
</View>
|
||||
</View>
|
||||
{this.state.reactionsModal ?
|
||||
{this.state.reactionsModal &&
|
||||
<ReactionsModal
|
||||
isVisible={this.state.reactionsModal}
|
||||
onClose={this.onClose}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
customEmojis={customEmojis}
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</Touch>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { StyleSheet, Platform } from 'react-native';
|
||||
|
||||
export default StyleSheet.create({
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1
|
||||
messageContent: {
|
||||
flex: 1,
|
||||
marginLeft: 30
|
||||
},
|
||||
flex: {
|
||||
flexDirection: 'row',
|
||||
flex: 1
|
||||
},
|
||||
message: {
|
||||
padding: 12,
|
||||
paddingTop: 6,
|
||||
paddingBottom: 6,
|
||||
flexDirection: 'row',
|
||||
transform: [{ scaleY: -1 }]
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 3,
|
||||
flexDirection: 'column',
|
||||
transform: [{ scaleY: -1 }],
|
||||
flex: 1
|
||||
},
|
||||
textInfo: {
|
||||
fontStyle: 'italic',
|
||||
|
@ -27,6 +27,7 @@ export default StyleSheet.create({
|
|||
width: 16,
|
||||
height: 16
|
||||
},
|
||||
temp: { opacity: 0.3 },
|
||||
codeStyle: {
|
||||
...Platform.select({
|
||||
ios: { fontFamily: 'Courier New' },
|
||||
|
@ -40,7 +41,8 @@ export default StyleSheet.create({
|
|||
},
|
||||
reactionsContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
flexWrap: 'wrap',
|
||||
marginTop: 6
|
||||
},
|
||||
reactionContainer: {
|
||||
flexDirection: 'row',
|
||||
|
@ -70,5 +72,17 @@ export default StyleSheet.create({
|
|||
},
|
||||
avatar: {
|
||||
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 RoomActionsView from '../../views/RoomActionsView';
|
||||
import CreateChannelView from '../../views/CreateChannelView';
|
||||
import SelectUsersView from '../../views/SelectUsersView';
|
||||
import SelectedUsersView from '../../views/SelectedUsersView';
|
||||
import NewServerView from '../../views/NewServerView';
|
||||
import StarredMessagesView from '../../views/StarredMessagesView';
|
||||
import PinnedMessagesView from '../../views/PinnedMessagesView';
|
||||
import MentionedMessagesView from '../../views/MentionedMessagesView';
|
||||
import SnippetedMessagesView from '../../views/SnippetedMessagesView';
|
||||
import SearchMessagesView from '../../views/SearchMessagesView';
|
||||
import RoomFilesView from '../../views/RoomFilesView';
|
||||
import RoomMembersView from '../../views/RoomMembersView';
|
||||
import RoomInfoView from '../../views/RoomInfoView';
|
||||
|
@ -28,19 +29,22 @@ const AuthRoutes = StackNavigator(
|
|||
CreateChannel: {
|
||||
screen: CreateChannelView,
|
||||
navigationOptions: {
|
||||
title: 'Create Channel'
|
||||
title: 'Create Channel',
|
||||
headerTintColor: '#292E35'
|
||||
}
|
||||
},
|
||||
SelectUsers: {
|
||||
screen: SelectUsersView,
|
||||
SelectedUsers: {
|
||||
screen: SelectedUsersView,
|
||||
navigationOptions: {
|
||||
title: 'Select Users'
|
||||
title: 'Select Users',
|
||||
headerTintColor: '#292E35'
|
||||
}
|
||||
},
|
||||
AddServer: {
|
||||
screen: NewServerView,
|
||||
navigationOptions: {
|
||||
title: 'New server'
|
||||
title: 'New server',
|
||||
headerTintColor: '#292E35'
|
||||
}
|
||||
},
|
||||
RoomActions: {
|
||||
|
@ -78,6 +82,13 @@ const AuthRoutes = StackNavigator(
|
|||
headerTintColor: '#292E35'
|
||||
}
|
||||
},
|
||||
SearchMessages: {
|
||||
screen: SearchMessagesView,
|
||||
navigationOptions: {
|
||||
title: 'Search Messages',
|
||||
headerTintColor: '#292E35'
|
||||
}
|
||||
},
|
||||
RoomFiles: {
|
||||
screen: RoomFilesView,
|
||||
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 } })
|
||||
]
|
||||
});
|
||||
|
||||
config.navigator.dispatch(action);
|
||||
}
|
||||
|
||||
export function dispatch(action) {
|
||||
if (config.navigator) {
|
||||
config.navigator.dispatch(action);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,21 @@ import Icon from 'react-native-vector-icons/FontAwesome';
|
|||
|
||||
import ListServerView from '../../views/ListServerView';
|
||||
import NewServerView from '../../views/NewServerView';
|
||||
import LoginSignupView from '../../views/LoginSignupView';
|
||||
import LoginView from '../../views/LoginView';
|
||||
import RegisterView from '../../views/RegisterView';
|
||||
|
||||
import TermsServiceView from '../../views/TermsServiceView';
|
||||
import PrivacyPolicyView from '../../views/PrivacyPolicyView';
|
||||
import ForgotPasswordView from '../../views/ForgotPasswordView';
|
||||
import database from '../../lib/realm';
|
||||
|
||||
const PublicRoutes = StackNavigator(
|
||||
{
|
||||
const hasServers = () => {
|
||||
const db = database.databases.serversDB.objects('servers');
|
||||
return db.length > 0;
|
||||
};
|
||||
|
||||
const ServerStack = StackNavigator({
|
||||
ListServer: {
|
||||
screen: ListServerView,
|
||||
navigationOptions({ navigation }) {
|
||||
|
@ -35,44 +41,78 @@ const PublicRoutes = StackNavigator(
|
|||
AddServer: {
|
||||
screen: NewServerView,
|
||||
navigationOptions: {
|
||||
title: 'New server'
|
||||
header: null
|
||||
}
|
||||
},
|
||||
LoginSignup: {
|
||||
screen: LoginSignupView,
|
||||
navigationOptions: {
|
||||
header: null
|
||||
}
|
||||
}
|
||||
}, {
|
||||
headerMode: 'screen',
|
||||
initialRouteName: hasServers() ? 'ListServer' : 'AddServer'
|
||||
});
|
||||
|
||||
const LoginStack = StackNavigator({
|
||||
Login: {
|
||||
screen: LoginView,
|
||||
navigationOptions: {
|
||||
title: 'Login'
|
||||
}
|
||||
},
|
||||
Register: {
|
||||
screen: RegisterView,
|
||||
navigationOptions: {
|
||||
title: 'Register'
|
||||
}
|
||||
},
|
||||
TermsService: {
|
||||
screen: TermsServiceView,
|
||||
navigationOptions: {
|
||||
title: 'Terms of service'
|
||||
}
|
||||
},
|
||||
PrivacyPolicy: {
|
||||
screen: PrivacyPolicyView,
|
||||
navigationOptions: {
|
||||
title: 'Privacy policy'
|
||||
header: null
|
||||
}
|
||||
},
|
||||
ForgotPassword: {
|
||||
screen: ForgotPasswordView,
|
||||
navigationOptions: {
|
||||
title: 'Forgot my password'
|
||||
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(
|
||||
{
|
||||
Server: {
|
||||
screen: ServerStack
|
||||
},
|
||||
Login: {
|
||||
screen: LoginStack
|
||||
},
|
||||
Register: {
|
||||
screen: RegisterStack
|
||||
}
|
||||
},
|
||||
{
|
||||
navigationOptions: {
|
||||
headerTitleAllowFontScaling: false
|
||||
}
|
||||
mode: 'modal',
|
||||
headerMode: 'none'
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { createStore as reduxCreateStore, applyMiddleware, compose } from 'redux';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import logger from 'redux-logger';
|
||||
import applyAppStateListener from 'redux-enhancer-react-native-appstate';
|
||||
import Reactotron from 'reactotron-react-native' ; // eslint-disable-line
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import applyAppStateListener from 'redux-enhancer-react-native-appstate';
|
||||
|
||||
import reducers from '../reducers';
|
||||
import sagas from '../sagas';
|
||||
|
||||
|
@ -20,8 +20,7 @@ if (__DEV__) {
|
|||
enhancers = compose(
|
||||
applyAppStateListener(),
|
||||
applyMiddleware(reduxImmutableStateInvariant),
|
||||
applyMiddleware(sagaMiddleware),
|
||||
applyMiddleware(logger)
|
||||
applyMiddleware(sagaMiddleware)
|
||||
);
|
||||
} else {
|
||||
sagaMiddleware = createSagaMiddleware();
|
||||
|
|
226
app/lib/ddp.js
|
@ -1,4 +1,23 @@
|
|||
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 {
|
||||
constructor() {
|
||||
|
@ -9,6 +28,7 @@ class EventEmitter {
|
|||
this.events[event] = [];
|
||||
}
|
||||
this.events[event].push(listener);
|
||||
return listener;
|
||||
}
|
||||
removeListener(event, listener) {
|
||||
if (typeof this.events[event] === 'object') {
|
||||
|
@ -24,7 +44,8 @@ class EventEmitter {
|
|||
try {
|
||||
listener.apply(this, args);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
Answers.logCustom(e);
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -34,72 +55,195 @@ class EventEmitter {
|
|||
this.removeListener(event, g);
|
||||
listener.apply(this, args);
|
||||
});
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class Socket extends EventEmitter {
|
||||
constructor(url) {
|
||||
constructor(url, login) {
|
||||
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.subscriptions = {};
|
||||
this._connect();
|
||||
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('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) => {
|
||||
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.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() {
|
||||
const connection = new WebSocket(`${ this.url }/websocket`);
|
||||
connection.onopen = () => {
|
||||
this.emit('open');
|
||||
this.send({ msg: 'connect', version: '1', support: ['1', 'pre2', 'pre1'] });
|
||||
};
|
||||
connection.onclose = e => this.emit('disconnected', e);
|
||||
// connection.onerror = () => {
|
||||
// // alert(error.type);
|
||||
// // console.log(error);
|
||||
// // console.log(`WebSocket Error ${ JSON.stringify({...error}) }`);
|
||||
// };
|
||||
return new Promise((resolve) => {
|
||||
this.lastping = new Date();
|
||||
this._close();
|
||||
clearInterval(this.reconnect_timeout);
|
||||
this.reconnect_timeout = setInterval(() => (!this.connection || this.connection.readyState > 1 || !this.check()) && this.reconnect(), 5000);
|
||||
this.connection = new WebSocket(`${ this.url }/websocket`, null);
|
||||
|
||||
connection.onmessage = (e) => {
|
||||
this.connection.onopen = () => {
|
||||
this.emit('open');
|
||||
resolve();
|
||||
this.ddp.emit('open');
|
||||
return this._login && this.login(this._login);
|
||||
};
|
||||
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);
|
||||
}
|
||||
};
|
||||
// this.on('disconnected', e => alert(JSON.stringify(e)));
|
||||
this.connection = connection;
|
||||
});
|
||||
}
|
||||
logout() {
|
||||
this._login = null;
|
||||
return this.call('logout').then(() => this.subscriptions = {});
|
||||
}
|
||||
disconnect() {
|
||||
this.emit('disconnected_by_user');
|
||||
this.connection.close();
|
||||
this._close();
|
||||
}
|
||||
reconnect() {
|
||||
this.disconnect();
|
||||
this.once('connected', () => {
|
||||
Object.keys(this.subscriptions).forEach((key) => {
|
||||
const { name, params } = this.subscriptions[key];
|
||||
this.subscriptions[key].unsubscribe();
|
||||
this.subscribe(name, params);
|
||||
});
|
||||
});
|
||||
async reconnect() {
|
||||
if (this._timer) {
|
||||
return;
|
||||
}
|
||||
delete this.connection;
|
||||
this._logged = false;
|
||||
|
||||
this._timer = setTimeout(() => {
|
||||
delete this._timer;
|
||||
this._connect();
|
||||
}, 1000);
|
||||
}
|
||||
call(method, ...params) {
|
||||
return this.send({
|
||||
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) {
|
||||
if (!this.subscriptions[id]) {
|
||||
|
@ -109,19 +253,31 @@ export default class Socket extends EventEmitter {
|
|||
return this.send({
|
||||
msg: 'unsub',
|
||||
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) {
|
||||
console.log(name, params);
|
||||
return this.send({
|
||||
msg: 'sub', name, params
|
||||
}).then(({ id }) => {
|
||||
const args = {
|
||||
id,
|
||||
name,
|
||||
params,
|
||||
unsubscribe: () => this.unsubscribe(id)
|
||||
};
|
||||
|
||||
this.subscriptions[id] = args;
|
||||
// console.log(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',
|
||||
t: 'string',
|
||||
lastMessage: 'messages',
|
||||
description: { type: 'string', optional: true },
|
||||
_updatedAt: { type: 'date', optional: true }
|
||||
}
|
||||
};
|
||||
|
@ -63,6 +64,14 @@ const subscriptionRolesSchema = {
|
|||
}
|
||||
};
|
||||
|
||||
const userMutedInRoomSchema = {
|
||||
name: 'usersMuted',
|
||||
primaryKey: 'value',
|
||||
properties: {
|
||||
value: 'string'
|
||||
}
|
||||
};
|
||||
|
||||
const subscriptionSchema = {
|
||||
name: 'subscriptions',
|
||||
primaryKey: '_id',
|
||||
|
@ -90,7 +99,9 @@ const subscriptionSchema = {
|
|||
blocked: { type: 'bool', optional: true },
|
||||
reactWhenReadOnly: { 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 },
|
||||
ts: { type: 'date', optional: true },
|
||||
attachments: { type: 'list', objectType: 'attachment' },
|
||||
fields: { type: 'list', objectType: 'attachmentFields' }
|
||||
fields: {
|
||||
type: 'list', objectType: 'attachmentFields', default: []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -265,8 +278,48 @@ const schema = [
|
|||
customEmojisSchema,
|
||||
messagesReactionsSchema,
|
||||
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 {
|
||||
databases = {
|
||||
serversDB: new Realm({
|
||||
|
@ -296,7 +349,7 @@ class DB {
|
|||
return this.databases.activeDB;
|
||||
}
|
||||
|
||||
setActiveDB(database) {
|
||||
setActiveDB(database = '') {
|
||||
const path = database.replace(/(^\w+:|^)\/\//, '');
|
||||
return this.databases.activeDB = new Realm({
|
||||
path: `${ path }.realm`,
|
||||
|
|
|
@ -1,47 +1,56 @@
|
|||
import Random from 'react-native-meteor/lib/Random';
|
||||
import { AsyncStorage, Platform } from 'react-native';
|
||||
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 reduxStore from './createStore';
|
||||
import settingsType from '../constants/settings';
|
||||
import messagesStatus from '../constants/messagesStatus';
|
||||
import database from './realm';
|
||||
import * as actions from '../actions';
|
||||
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
||||
import { setUser, setLoginServices, removeLoginServices } from '../actions/login';
|
||||
import { disconnect, disconnect_by_user, connectSuccess, connectFailure } from '../actions/connect';
|
||||
// import * as actions from '../actions';
|
||||
|
||||
import { setUser, setLoginServices, removeLoginServices, loginRequest, loginSuccess, loginFailure } from '../actions/login';
|
||||
import { disconnect, connectSuccess, connectFailure } from '../actions/connect';
|
||||
import { setActiveUser } from '../actions/activeUsers';
|
||||
import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
|
||||
import { pinnedMessagesReceived, pinnedMessageUnpinned } from '../actions/pinnedMessages';
|
||||
import { mentionedMessagesReceived } from '../actions/mentionedMessages';
|
||||
import { snippetedMessagesReceived } from '../actions/snippetedMessages';
|
||||
import { roomFilesReceived } from '../actions/roomFiles';
|
||||
import { someoneTyping, roomMessageReceived } from '../actions/room';
|
||||
import { setRoles } from '../actions/roles';
|
||||
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 SERVER_TIMEOUT = 30000;
|
||||
|
||||
const call = (method, ...params) => RocketChat.ddp.call(method, ...params); // eslint-disable-line
|
||||
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 = {
|
||||
TOKEN_KEY,
|
||||
|
||||
subscribeRooms,
|
||||
subscribeRoom,
|
||||
createChannel({ name, users, type }) {
|
||||
return call(type ? 'createChannel' : 'createPrivateGroup', name, users, type);
|
||||
},
|
||||
|
@ -97,59 +106,78 @@ const RocketChat = {
|
|||
reduxStore.dispatch(setActiveUser(this.activeUsers));
|
||||
this._setUserTimer = null;
|
||||
return this.activeUsers = {};
|
||||
}, 3000);
|
||||
}, 1000);
|
||||
|
||||
this.activeUsers[ddpMessage.id] = ddpMessage.fields;
|
||||
},
|
||||
reconnect() {
|
||||
if (this.ddp) {
|
||||
this.ddp.reconnect();
|
||||
async loginSuccess(user) {
|
||||
if (!user) {
|
||||
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) {
|
||||
return new Promise((resolve) => {
|
||||
if (this.ddp) {
|
||||
this.ddp.disconnect();
|
||||
delete this.ddp;
|
||||
}
|
||||
this.ddp = new Ddp(url);
|
||||
return new Promise((resolve) => {
|
||||
this.ddp.on('disconnected_by_user', () => {
|
||||
reduxStore.dispatch(disconnect_by_user());
|
||||
});
|
||||
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());
|
||||
});
|
||||
// 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) => {
|
||||
const message = this._buildMessage(ddpMessage.fields.args[0]);
|
||||
return reduxStore.dispatch(roomMessageReceived(message));
|
||||
const message = _buildMessage(ddpMessage.fields.args[0]);
|
||||
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('/');
|
||||
if (ev !== 'typing') {
|
||||
return;
|
||||
}
|
||||
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 [, ev] = ddpMessage.fields.eventName.split('/');
|
||||
if (/subscriptions/.test(ev)) {
|
||||
|
@ -161,6 +189,11 @@ const RocketChat = {
|
|||
} else {
|
||||
data.blocked = false;
|
||||
}
|
||||
if (data.mobilePushNotifications === 'nothing') {
|
||||
data.notifications = true;
|
||||
} else {
|
||||
data.notifications = false;
|
||||
}
|
||||
database.write(() => {
|
||||
database.create('subscriptions', data, true);
|
||||
});
|
||||
|
@ -178,11 +211,33 @@ const RocketChat = {
|
|||
sub.reactWhenReadOnly = data.reactWhenReadOnly;
|
||||
sub.archived = data.archived;
|
||||
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') {
|
||||
this.starredMessages = this.starredMessages || [];
|
||||
|
||||
|
@ -191,14 +246,14 @@ const RocketChat = {
|
|||
this.starredMessagesTimer = null;
|
||||
}
|
||||
|
||||
this.starredMessagesTimer = setTimeout(() => {
|
||||
this.starredMessagesTimer = setTimeout(protectedFunction(() => {
|
||||
reduxStore.dispatch(starredMessagesReceived(this.starredMessages));
|
||||
this.starredMessagesTimer = null;
|
||||
return this.starredMessages = [];
|
||||
}, 1000);
|
||||
}), 1000);
|
||||
const message = ddpMessage.fields;
|
||||
message._id = ddpMessage.id;
|
||||
const starredMessage = this._buildMessage(message);
|
||||
const starredMessage = _buildMessage(message);
|
||||
this.starredMessages = [...this.starredMessages, starredMessage];
|
||||
}
|
||||
if (ddpMessage.msg === 'removed') {
|
||||
|
@ -206,9 +261,9 @@ const RocketChat = {
|
|||
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') {
|
||||
this.pinnedMessages = this.pinnedMessages || [];
|
||||
|
||||
|
@ -224,7 +279,7 @@ const RocketChat = {
|
|||
}, 1000);
|
||||
const message = ddpMessage.fields;
|
||||
message._id = ddpMessage.id;
|
||||
const pinnedMessage = this._buildMessage(message);
|
||||
const pinnedMessage = _buildMessage(message);
|
||||
this.pinnedMessages = [...this.pinnedMessages, pinnedMessage];
|
||||
}
|
||||
if (ddpMessage.msg === 'removed') {
|
||||
|
@ -232,9 +287,9 @@ const RocketChat = {
|
|||
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') {
|
||||
this.mentionedMessages = this.mentionedMessages || [];
|
||||
|
||||
|
@ -250,12 +305,12 @@ const RocketChat = {
|
|||
}, 1000);
|
||||
const message = ddpMessage.fields;
|
||||
message._id = ddpMessage.id;
|
||||
const mentionedMessage = this._buildMessage(message);
|
||||
const mentionedMessage = _buildMessage(message);
|
||||
this.mentionedMessages = [...this.mentionedMessages, mentionedMessage];
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.ddp.on('rocketchat_snippeted_message', (ddpMessage) => {
|
||||
this.ddp.on('rocketchat_snippeted_message', protectedFunction((ddpMessage) => {
|
||||
if (ddpMessage.msg === 'added') {
|
||||
this.snippetedMessages = this.snippetedMessages || [];
|
||||
|
||||
|
@ -271,12 +326,12 @@ const RocketChat = {
|
|||
}, 1000);
|
||||
const message = ddpMessage.fields;
|
||||
message._id = ddpMessage.id;
|
||||
const snippetedMessage = this._buildMessage(message);
|
||||
const snippetedMessage = _buildMessage(message);
|
||||
this.snippetedMessages = [...this.snippetedMessages, snippetedMessage];
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.ddp.on('room_files', (ddpMessage) => {
|
||||
this.ddp.on('room_files', protectedFunction((ddpMessage) => {
|
||||
if (ddpMessage.msg === 'added') {
|
||||
this.roomFiles = this.roomFiles || [];
|
||||
|
||||
|
@ -318,9 +373,9 @@ const RocketChat = {
|
|||
}
|
||||
this.roomFiles = [...this.roomFiles, message];
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.ddp.on('meteor_accounts_loginServiceConfiguration', (ddpMessage) => {
|
||||
this.ddp.on('meteor_accounts_loginServiceConfiguration', protectedFunction((ddpMessage) => {
|
||||
if (ddpMessage.msg === 'added') {
|
||||
this.loginServices = this.loginServices || {};
|
||||
if (this.loginServiceTimer) {
|
||||
|
@ -340,9 +395,9 @@ const RocketChat = {
|
|||
}
|
||||
this.loginServiceTimer = setTimeout(() => reduxStore.dispatch(removeLoginServices()), 1000);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.ddp.on('rocketchat_roles', (ddpMessage) => {
|
||||
this.ddp.on('rocketchat_roles', protectedFunction((ddpMessage) => {
|
||||
this.roles = this.roles || {};
|
||||
|
||||
if (this.roleTimer) {
|
||||
|
@ -353,39 +408,38 @@ const RocketChat = {
|
|||
reduxStore.dispatch(setRoles(this.roles));
|
||||
|
||||
database.write(() => {
|
||||
_.forEach(this.roles, (description, _id) => {
|
||||
foreach(this.roles, (description, _id) => {
|
||||
database.create('roles', { _id, description }, true);
|
||||
});
|
||||
});
|
||||
|
||||
this.roleTimer = null;
|
||||
return this.roles = {};
|
||||
}, 5000);
|
||||
this.roles[ddpMessage.id] = ddpMessage.fields.description;
|
||||
});
|
||||
}).catch(console.log);
|
||||
},
|
||||
}, 1000);
|
||||
this.roles[ddpMessage.id] = (ddpMessage.fields && ddpMessage.fields.description) || undefined;
|
||||
}));
|
||||
|
||||
me({ server, token, userId }) {
|
||||
return fetch(`${ server }/api/v1/me`, {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': token,
|
||||
'X-User-Id': userId
|
||||
}
|
||||
}).then(response => response.json());
|
||||
},
|
||||
this.ddp.on('error', protectedFunction((err) => {
|
||||
console.warn('onError', JSON.stringify(err));
|
||||
Answers.logCustom('disconnect', err);
|
||||
reduxStore.dispatch(connectFailure());
|
||||
}));
|
||||
|
||||
userInfo({ server, token, userId }) {
|
||||
return fetch(`${ server }/api/v1/users.info?userId=${ userId }`, {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': token,
|
||||
'X-User-Id': userId
|
||||
}
|
||||
}).then(response => response.json());
|
||||
// TODO: fix api (get emojis by date/version....)
|
||||
|
||||
this.ddp.on('open', protectedFunction(() => {
|
||||
RocketChat.getSettings();
|
||||
RocketChat.getPermissions();
|
||||
reduxStore.dispatch(connectSuccess());
|
||||
resolve();
|
||||
}));
|
||||
|
||||
this.ddp.once('open', protectedFunction(() => {
|
||||
this.ddp.subscribe('activeUsers');
|
||||
this.ddp.subscribe('roles');
|
||||
RocketChat.getCustomEmoji();
|
||||
}));
|
||||
}).catch(err => console.warn(`asd ${ err }`));
|
||||
},
|
||||
|
||||
register({ credentials }) {
|
||||
|
@ -442,19 +496,18 @@ const RocketChat = {
|
|||
return this.login(params, callback);
|
||||
},
|
||||
|
||||
loadSubscriptions(cb) {
|
||||
this.ddp.call('subscriptions/get').then((data) => {
|
||||
if (data.length) {
|
||||
database.write(() => {
|
||||
data.forEach((subscription) => {
|
||||
database.create('subscriptions', subscription, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return cb && cb();
|
||||
});
|
||||
login(params) {
|
||||
return this.ddp.login(params);
|
||||
},
|
||||
logout({ server }) {
|
||||
if (this.ddp) {
|
||||
this.ddp.logout();
|
||||
}
|
||||
database.deleteAll();
|
||||
AsyncStorage.removeItem(TOKEN_KEY);
|
||||
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`);
|
||||
},
|
||||
|
||||
registerPushToken(id, token) {
|
||||
const key = Platform.OS === 'ios' ? 'apn' : 'gcm';
|
||||
const data = {
|
||||
|
@ -470,92 +523,32 @@ const RocketChat = {
|
|||
updatePushToken(pushId) {
|
||||
return call('raix:push-setuser', pushId);
|
||||
},
|
||||
|
||||
_parseUrls(urls) {
|
||||
return 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, '&');
|
||||
loadMissedMessages,
|
||||
loadMessagesForRoom,
|
||||
getMessage,
|
||||
sendMessage,
|
||||
getRooms,
|
||||
readMessages,
|
||||
me({ server = reduxStore.getState().server.server, token, userId }) {
|
||||
return fetch(`${ server }/api/v1/me`, {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': token,
|
||||
'X-User-Id': userId
|
||||
}
|
||||
tmp.image = decodedOgImage || meta.twitterImage || meta.oembedThumbnailUrl;
|
||||
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);
|
||||
}
|
||||
});
|
||||
}).then(response => response.json());
|
||||
},
|
||||
|
||||
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
|
||||
userInfo({ server = reduxStore.getState().server.server, token, userId }) {
|
||||
return fetch(`${ server }/api/v1/users.info?userId=${ userId }`, {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': token,
|
||||
'X-User-Id': userId
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}).then(response => response.json());
|
||||
},
|
||||
async resendMessage(messageId) {
|
||||
const message = await database.objects('messages').filtered('_id = $0', messageId)[0];
|
||||
|
@ -563,7 +556,7 @@ const RocketChat = {
|
|||
message.status = messagesStatus.TEMP;
|
||||
database.create('messages', message, true);
|
||||
});
|
||||
return RocketChat._sendMessageCall(message);
|
||||
return _sendMessageCall(JSON.parse(JSON.stringify(message)));
|
||||
},
|
||||
|
||||
spotlight(search, usernames, type) {
|
||||
|
@ -573,16 +566,6 @@ const RocketChat = {
|
|||
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) {
|
||||
return call('joinRoom', rid);
|
||||
},
|
||||
|
@ -646,6 +629,7 @@ const RocketChat = {
|
|||
} catch (e) {
|
||||
return e;
|
||||
} finally {
|
||||
// TODO: fix that
|
||||
try {
|
||||
database.write(() => {
|
||||
const msg = database.objects('messages').filtered('_id = $0', placeholder._id);
|
||||
|
@ -656,93 +640,9 @@ const RocketChat = {
|
|||
}
|
||||
}
|
||||
},
|
||||
async getRooms() {
|
||||
const { login } = reduxStore.getState();
|
||||
let lastMessage = database
|
||||
.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)));
|
||||
},
|
||||
getSettings,
|
||||
getPermissions,
|
||||
getCustomEmoji,
|
||||
parseSettings: settings => settings.reduce((ret, item) => {
|
||||
ret[item._id] = item[settingsType[item.type]] || item.valueAsString || item.valueAsNumber ||
|
||||
item.valueAsBoolean || item.value;
|
||||
|
@ -755,16 +655,6 @@ const RocketChat = {
|
|||
});
|
||||
},
|
||||
_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) => {
|
||||
ret[item._id] = item.roles.reduce((roleRet, role) => [...roleRet, role.value], []);
|
||||
return ret;
|
||||
|
@ -775,16 +665,6 @@ const RocketChat = {
|
|||
});
|
||||
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) => {
|
||||
ret[item.name] = item.extension;
|
||||
item.aliases.forEach((alias) => {
|
||||
|
@ -815,20 +695,21 @@ const RocketChat = {
|
|||
return call('pinMessage', message);
|
||||
},
|
||||
getRoom(rid) {
|
||||
const result = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
if (result.length === 0) {
|
||||
const [result] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
if (!result) {
|
||||
return Promise.reject(new Error('Room not found'));
|
||||
}
|
||||
return Promise.resolve(result[0]);
|
||||
return Promise.resolve(result);
|
||||
},
|
||||
async getPermalink(message) {
|
||||
const room = await RocketChat.getRoom(message.rid);
|
||||
const { server } = reduxStore.getState().server;
|
||||
const roomType = {
|
||||
p: 'group',
|
||||
c: 'channel',
|
||||
d: 'direct'
|
||||
}[room.t];
|
||||
return `${ room._server.id }/${ roomType }/${ room.name }?msg=${ message._id }`;
|
||||
return `${ server }/${ roomType }/${ room.name }?msg=${ message._id }`;
|
||||
},
|
||||
subscribe(...args) {
|
||||
return this.ddp.subscribe(...args);
|
||||
|
@ -878,6 +759,12 @@ const RocketChat = {
|
|||
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) {
|
||||
if (archive) {
|
||||
return call('archiveRoom', rid);
|
||||
|
@ -887,6 +774,17 @@ const RocketChat = {
|
|||
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) {
|
||||
// get the room from realm
|
||||
const room = database.objects('subscriptions').filtered('rid = $0', rid)[0];
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
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 SimpleMarkdown from 'simple-markdown';
|
||||
|
||||
import messagesStatus from '../constants/messagesStatus';
|
||||
|
||||
import Avatar from '../containers/Avatar';
|
||||
import Status from '../containers/status';
|
||||
import Touch from '../utils/touch/index'; //eslint-disable-line
|
||||
|
@ -44,7 +46,6 @@ const styles = StyleSheet.create({
|
|||
flex: 1,
|
||||
fontSize: 18,
|
||||
color: '#444',
|
||||
|
||||
marginRight: 8
|
||||
},
|
||||
lastMessage: {
|
||||
|
@ -64,8 +65,8 @@ const styles = StyleSheet.create({
|
|||
// backgroundColor: '#eee'
|
||||
},
|
||||
row: {
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
// width: '100%',
|
||||
// flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'flex-end'
|
||||
|
@ -145,41 +146,57 @@ const renderNumber = (unread, userMentions) => {
|
|||
);
|
||||
};
|
||||
|
||||
const attrs = ['name', 'unread', 'userMentions', 'alert', 'showLastMessage', 'type', '_updatedAt'];
|
||||
@connect(state => ({
|
||||
user: state.login && state.login.user,
|
||||
StoreLastMessage: state.settings.Store_Last_Message,
|
||||
customEmojis: state.customEmojis
|
||||
StoreLastMessage: state.settings.Store_Last_Message
|
||||
}))
|
||||
export default class RoomItem extends React.PureComponent {
|
||||
export default class RoomItem extends React.Component {
|
||||
static propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
StoreLastMessage: PropTypes.bool,
|
||||
_updatedAt: PropTypes.instanceOf(Date),
|
||||
lastMessage: PropTypes.object,
|
||||
showLastMessage: PropTypes.bool,
|
||||
favorite: PropTypes.bool,
|
||||
alert: PropTypes.bool,
|
||||
unread: PropTypes.number,
|
||||
userMentions: PropTypes.number,
|
||||
id: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
customEmojis: PropTypes.object,
|
||||
user: PropTypes.object
|
||||
onLongPress: PropTypes.func,
|
||||
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() {
|
||||
const {
|
||||
type, name, id
|
||||
type, name, id, avatarSize, statusStyle
|
||||
} = 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() {
|
||||
const {
|
||||
lastMessage, type
|
||||
lastMessage, type, showLastMessage
|
||||
} = this.props;
|
||||
|
||||
if (!this.props.StoreLastMessage) {
|
||||
if (!this.props.StoreLastMessage || !showLastMessage) {
|
||||
return '';
|
||||
}
|
||||
if (!lastMessage) {
|
||||
|
@ -208,7 +225,7 @@ export default class RoomItem extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
favorite, unread, userMentions, name, _updatedAt, customEmojis, alert
|
||||
favorite, unread, userMentions, name, _updatedAt, alert, status
|
||||
} = this.props;
|
||||
|
||||
const date = this.formatDate(_updatedAt);
|
||||
|
@ -224,10 +241,19 @@ export default class RoomItem extends React.PureComponent {
|
|||
accessibilityLabel += ', you were mentioned';
|
||||
}
|
||||
|
||||
if (date) {
|
||||
accessibilityLabel += `, last message ${ date }`;
|
||||
}
|
||||
|
||||
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]}>
|
||||
{this.icon}
|
||||
<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}
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
{status === messagesStatus.ERROR ? <Icon name='error-outline' color='red' size={12} style={{ marginRight: 5, alignSelf: 'center' }} /> : null }
|
||||
<Markdown
|
||||
msg={this.lastMessage}
|
||||
customEmojis={customEmojis}
|
||||
style={styles.lastMessage}
|
||||
markdownStyle={markdownStyle}
|
||||
customRules={customRules}
|
||||
|
|
|
@ -3,7 +3,8 @@ import { CREATE_CHANNEL } from '../actions/actionsTypes';
|
|||
const initialState = {
|
||||
isFetching: false,
|
||||
failure: false,
|
||||
users: []
|
||||
result: '',
|
||||
error: ''
|
||||
};
|
||||
|
||||
export default function messages(state = initialState, action) {
|
||||
|
@ -11,9 +12,9 @@ export default function messages(state = initialState, action) {
|
|||
case CREATE_CHANNEL.REQUEST:
|
||||
return {
|
||||
...state,
|
||||
error: undefined,
|
||||
isFetching: true,
|
||||
failure: false,
|
||||
isFetching: true
|
||||
error: ''
|
||||
};
|
||||
case CREATE_CHANNEL.SUCCESS:
|
||||
return {
|
||||
|
@ -29,18 +30,6 @@ export default function messages(state = initialState, action) {
|
|||
failure: true,
|
||||
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:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import room from './room';
|
|||
import rooms from './rooms';
|
||||
import server from './server';
|
||||
import navigator from './navigator';
|
||||
import selectedUsers from './selectedUsers';
|
||||
import createChannel from './createChannel';
|
||||
import app from './app';
|
||||
import permissions from './permissions';
|
||||
|
@ -26,6 +27,7 @@ export default combineReducers({
|
|||
messages,
|
||||
server,
|
||||
navigator,
|
||||
selectedUsers,
|
||||
createChannel,
|
||||
app,
|
||||
room,
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
import { MENTIONED_MESSAGES } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
messages: []
|
||||
messages: [],
|
||||
ready: false
|
||||
};
|
||||
|
||||
export default function server(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case MENTIONED_MESSAGES.OPEN:
|
||||
return {
|
||||
...state,
|
||||
ready: false
|
||||
};
|
||||
case MENTIONED_MESSAGES.READY:
|
||||
return {
|
||||
...state,
|
||||
ready: true
|
||||
};
|
||||
case MENTIONED_MESSAGES.MESSAGES_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -2,7 +2,8 @@ import { PINNED_MESSAGES } from '../actions/actionsTypes';
|
|||
|
||||
const initialState = {
|
||||
messages: [],
|
||||
isOpen: false
|
||||
isOpen: false,
|
||||
ready: false
|
||||
};
|
||||
|
||||
export default function server(state = initialState, action) {
|
||||
|
@ -10,7 +11,13 @@ export default function server(state = initialState, action) {
|
|||
case PINNED_MESSAGES.OPEN:
|
||||
return {
|
||||
...state,
|
||||
isOpen: true
|
||||
isOpen: true,
|
||||
ready: false
|
||||
};
|
||||
case PINNED_MESSAGES.READY:
|
||||
return {
|
||||
...state,
|
||||
ready: true
|
||||
};
|
||||
case PINNED_MESSAGES.MESSAGES_RECEIVED:
|
||||
return {
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
import { ROOM_FILES } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
messages: []
|
||||
messages: [],
|
||||
ready: false
|
||||
};
|
||||
|
||||
export default function server(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ROOM_FILES.OPEN:
|
||||
return {
|
||||
...state,
|
||||
ready: false
|
||||
};
|
||||
case ROOM_FILES.READY:
|
||||
return {
|
||||
...state,
|
||||
ready: true
|
||||
};
|
||||
case ROOM_FILES.MESSAGES_RECEIVED:
|
||||
return {
|
||||
...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,
|
||||
errorMessage: '',
|
||||
failure: false,
|
||||
server: ''
|
||||
server: '',
|
||||
adding: false
|
||||
};
|
||||
|
||||
|
||||
|
@ -32,8 +33,17 @@ export default function server(state = initialState, action) {
|
|||
failure: true,
|
||||
errorMessage: action.err
|
||||
};
|
||||
case SERVER.ADD:
|
||||
return {
|
||||
...state,
|
||||
adding: true
|
||||
};
|
||||
case SERVER.SELECT:
|
||||
return { ...state, server: action.server };
|
||||
return {
|
||||
...state,
|
||||
server: action.server,
|
||||
adding: false
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
import { SNIPPETED_MESSAGES } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
messages: []
|
||||
messages: [],
|
||||
ready: false
|
||||
};
|
||||
|
||||
export default function server(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case SNIPPETED_MESSAGES.OPEN:
|
||||
return {
|
||||
...state,
|
||||
ready: false
|
||||
};
|
||||
case SNIPPETED_MESSAGES.READY:
|
||||
return {
|
||||
...state,
|
||||
ready: true
|
||||
};
|
||||
case SNIPPETED_MESSAGES.MESSAGES_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -2,7 +2,8 @@ import { STARRED_MESSAGES } from '../actions/actionsTypes';
|
|||
|
||||
const initialState = {
|
||||
messages: [],
|
||||
isOpen: false
|
||||
isOpen: false,
|
||||
ready: false
|
||||
};
|
||||
|
||||
export default function server(state = initialState, action) {
|
||||
|
@ -10,7 +11,13 @@ export default function server(state = initialState, action) {
|
|||
case STARRED_MESSAGES.OPEN:
|
||||
return {
|
||||
...state,
|
||||
isOpen: true
|
||||
isOpen: true,
|
||||
ready: false
|
||||
};
|
||||
case STARRED_MESSAGES.READY:
|
||||
return {
|
||||
...state,
|
||||
ready: true
|
||||
};
|
||||
case STARRED_MESSAGES.MESSAGES_RECEIVED:
|
||||
return {
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
import { call, takeLatest, select, take, race } from 'redux-saga/effects';
|
||||
import { delay } from 'redux-saga';
|
||||
import { call, takeLatest, select, put, all } from 'redux-saga/effects';
|
||||
import { AsyncStorage } from 'react-native';
|
||||
import { METEOR } from '../actions/actionsTypes';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import { setToken } from '../actions/login';
|
||||
|
||||
const getServer = ({ server }) => server.server;
|
||||
|
||||
|
||||
const connect = url => RocketChat.connect(url);
|
||||
const watchConnect = function* watchConnect() {
|
||||
const { disconnect } = yield race({
|
||||
disconnect: take(METEOR.DISCONNECT),
|
||||
disconnected_by_user: take(METEOR.DISCONNECT_BY_USER)
|
||||
});
|
||||
if (disconnect) {
|
||||
while (true) {
|
||||
const { connected } = yield race({
|
||||
connected: take(METEOR.SUCCESS),
|
||||
timeout: call(delay, 1000)
|
||||
});
|
||||
if (connected) {
|
||||
return;
|
||||
}
|
||||
yield RocketChat.reconnect();
|
||||
const getToken = function* getToken() {
|
||||
const currentServer = yield select(getServer);
|
||||
const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
||||
if (user) {
|
||||
yield put(setToken(JSON.parse(user)));
|
||||
try {
|
||||
yield call([AsyncStorage, 'setItem'], RocketChat.TOKEN_KEY, JSON.parse(user).token || '');
|
||||
} catch (error) {
|
||||
console.warn('getToken', error);
|
||||
}
|
||||
return JSON.parse(user);
|
||||
}
|
||||
return yield put(setToken());
|
||||
};
|
||||
|
||||
|
||||
const connect = (...args) => RocketChat.connect(...args);
|
||||
|
||||
const test = function* test() {
|
||||
// try {
|
||||
try {
|
||||
const server = yield select(getServer);
|
||||
const user = yield call(getToken);
|
||||
// const response =
|
||||
yield call(connect, server);
|
||||
yield all([call(connect, server, user && user.token ? { resume: user.token, ...user.user } : undefined)]);// , put(loginRequest({ resume: user.token }))]);
|
||||
// yield put(connectSuccess(response));
|
||||
// } catch (err) {
|
||||
} catch (err) {
|
||||
console.warn('test', err);
|
||||
// yield put(connectFailure(err.status));
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(METEOR.REQUEST, test);
|
||||
// yield take(METEOR.SUCCESS, watchConnect);
|
||||
yield takeLatest(METEOR.SUCCESS, watchConnect);
|
||||
// yield takeLatest(METEOR.SUCCESS, watchConnect);
|
||||
};
|
||||
export default root;
|
||||
|
|
|
@ -28,4 +28,5 @@ const handleRequest = function* handleRequest({ data }) {
|
|||
const root = function* root() {
|
||||
yield takeLatest(CREATE_CHANNEL.REQUEST, handleRequest);
|
||||
};
|
||||
|
||||
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 hello from './hello';
|
||||
import login from './login';
|
||||
import connect from './connect';
|
||||
import rooms from './rooms';
|
||||
|
@ -18,7 +17,6 @@ const root = function* root() {
|
|||
yield all([
|
||||
init(),
|
||||
createChannel(),
|
||||
hello(),
|
||||
rooms(),
|
||||
login(),
|
||||
connect(),
|
||||
|
|
|
@ -2,15 +2,13 @@ import { AsyncStorage } from 'react-native';
|
|||
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||
import * as actions from '../actions';
|
||||
import { setServer } from '../actions/server';
|
||||
import { restoreToken } from '../actions/login';
|
||||
import { restoreToken, setUser } from '../actions/login';
|
||||
import { APP } from '../actions/actionsTypes';
|
||||
import { setRoles } from '../actions/roles';
|
||||
import database from '../lib/realm';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
const restore = function* restore() {
|
||||
try {
|
||||
const token = yield call([AsyncStorage, 'getItem'], 'reactnativemeteor_usertoken');
|
||||
const token = yield call([AsyncStorage, 'getItem'], RocketChat.TOKEN_KEY);
|
||||
if (token) {
|
||||
yield put(restoreToken(token));
|
||||
}
|
||||
|
@ -18,21 +16,16 @@ const restore = function* restore() {
|
|||
const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer');
|
||||
if (currentServer) {
|
||||
yield put(setServer(currentServer));
|
||||
const settings = database.objects('settings');
|
||||
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
||||
const permissions = database.objects('permissions');
|
||||
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;
|
||||
}, {})));
|
||||
|
||||
const login = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
||||
if (login && login.user) {
|
||||
yield put(setUser(login.user));
|
||||
}
|
||||
}
|
||||
|
||||
yield put(actions.appReady({}));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.warn('restore', e);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { put, call, takeLatest, select, all, take } from 'redux-saga/effects';
|
||||
import { Answers } from 'react-native-fabric';
|
||||
import { put, call, take, takeLatest, select, all } from 'redux-saga/effects';
|
||||
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import {
|
||||
loginRequest,
|
||||
loginSubmit,
|
||||
// loginRequest,
|
||||
// loginSubmit,
|
||||
registerRequest,
|
||||
registerIncomplete,
|
||||
loginSuccess,
|
||||
// loginSuccess,
|
||||
loginFailure,
|
||||
logout,
|
||||
setToken,
|
||||
// logout,
|
||||
// setToken,
|
||||
registerSuccess,
|
||||
setUsernameRequest,
|
||||
setUsernameSuccess,
|
||||
|
@ -23,40 +23,41 @@ import * as NavigationService from '../containers/routes/NavigationService';
|
|||
const getUser = state => state.login;
|
||||
const getServer = state => state.server.server;
|
||||
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 setUsernameCall = args => RocketChat.setUsername(args);
|
||||
const loginSuccessCall = () => RocketChat.loginSuccess();
|
||||
const logoutCall = args => RocketChat.logout(args);
|
||||
const meCall = args => RocketChat.me(args);
|
||||
const forgotPasswordCall = args => RocketChat.forgotPassword(args);
|
||||
const userInfoCall = args => RocketChat.userInfo(args);
|
||||
|
||||
const getToken = function* getToken() {
|
||||
const currentServer = yield select(getServer);
|
||||
const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
||||
if (user) {
|
||||
try {
|
||||
yield put(setToken(JSON.parse(user)));
|
||||
yield call([AsyncStorage, 'setItem'], RocketChat.TOKEN_KEY, JSON.parse(user).token || '');
|
||||
return JSON.parse(user);
|
||||
} catch (e) {
|
||||
console.log('getTokenerr', e);
|
||||
}
|
||||
} else {
|
||||
return yield put(setToken());
|
||||
}
|
||||
};
|
||||
// const getToken = function* getToken() {
|
||||
// const currentServer = yield select(getServer);
|
||||
// const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`);
|
||||
// if (user) {
|
||||
// try {
|
||||
// yield put(setToken(JSON.parse(user)));
|
||||
// yield call([AsyncStorage, 'setItem'], RocketChat.TOKEN_KEY, JSON.parse(user).token || '');
|
||||
// return JSON.parse(user);
|
||||
// } catch (e) {
|
||||
// console.log('getTokenerr', e);
|
||||
// }
|
||||
// } else {
|
||||
// return yield put(setToken());
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
|
||||
try {
|
||||
const user = yield call(getToken);
|
||||
if (user.token) {
|
||||
yield put(loginRequest({ resume: user.token }));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
// const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
|
||||
// try {
|
||||
// const user = yield call(getToken);
|
||||
// if (user.token) {
|
||||
// yield put(loginRequest({ resume: user.token }));
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.log(e);
|
||||
// }
|
||||
// };
|
||||
|
||||
const saveToken = function* saveToken() {
|
||||
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));
|
||||
const token = yield AsyncStorage.getItem('pushId');
|
||||
if (token) {
|
||||
RocketChat.registerPushToken(user.user.id, token);
|
||||
yield RocketChat.registerPushToken(user.user.id, token);
|
||||
}
|
||||
Answers.logLogin('Email', true, { server });
|
||||
};
|
||||
|
||||
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 {
|
||||
if (!user.user.username && !user.isRegistering) {
|
||||
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 }) {
|
||||
yield put(loginRequest(credentials));
|
||||
};
|
||||
// const handleLoginRequest = function* handleLoginRequest({ 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 }) {
|
||||
yield put(registerRequest(credentials));
|
||||
|
@ -114,10 +103,14 @@ const handleRegisterRequest = function* handleRegisterRequest({ credentials }) {
|
|||
};
|
||||
|
||||
const handleRegisterSuccess = function* handleRegisterSuccess({ credentials }) {
|
||||
yield put(loginSubmit({
|
||||
try {
|
||||
yield call(loginCall, {
|
||||
username: credentials.email,
|
||||
password: credentials.pass
|
||||
}));
|
||||
});
|
||||
} catch (err) {
|
||||
yield put(loginFailure(err));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSetUsernameSubmit = function* handleSetUsernameSubmit({ credentials }) {
|
||||
|
@ -128,6 +121,7 @@ const handleSetUsernameRequest = function* handleSetUsernameRequest({ credential
|
|||
try {
|
||||
yield call(setUsernameCall, { credentials });
|
||||
yield put(setUsernameSuccess());
|
||||
yield call(loginSuccessCall);
|
||||
} catch (err) {
|
||||
yield put(loginFailure(err));
|
||||
}
|
||||
|
@ -154,20 +148,24 @@ const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ emai
|
|||
};
|
||||
|
||||
const watchLoginOpen = function* watchLoginOpen() {
|
||||
try {
|
||||
const isConnected = yield select(getIsConnected);
|
||||
if (!isConnected) {
|
||||
yield take(types.METEOR.SUCCESS);
|
||||
}
|
||||
const sub = yield RocketChat.subscribe('meteor.loginServiceConfiguration');
|
||||
yield take(types.LOGIN.CLOSE);
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
sub.unsubscribe().catch(e => console.warn('watchLoginOpen unsubscribe', e));
|
||||
} catch (error) {
|
||||
console.warn('watchLoginOpen', error);
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(types.METEOR.SUCCESS, handleLoginWhenServerChanges);
|
||||
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
||||
// yield takeLatest(types.METEOR.SUCCESS, handleLoginWhenServerChanges);
|
||||
// yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
||||
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_SUBMIT, handleRegisterSubmit);
|
||||
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 RocketChat from '../lib/rocketchat';
|
||||
import { readyMentionedMessages } from '../actions/mentionedMessages';
|
||||
|
||||
const watchMentionedMessagesRoom = function* watchMentionedMessagesRoom({ rid }) {
|
||||
const sub = yield RocketChat.subscribe('mentionedMessages', rid, 50);
|
||||
yield take(types.MENTIONED_MESSAGES.CLOSE);
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
let sub;
|
||||
let newSub;
|
||||
|
||||
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() {
|
||||
yield takeLatest(types.MENTIONED_MESSAGES.OPEN, watchMentionedMessagesRoom);
|
||||
yield takeLatest(types.MENTIONED_MESSAGES.OPEN, openMentionedMessagesRoom);
|
||||
yield takeLatest(types.MENTIONED_MESSAGES.CLOSE, closeMentionedMessagesRoom);
|
||||
};
|
||||
export default root;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { takeLatest, select, take, put, call } from 'redux-saga/effects';
|
||||
import { MESSAGES, LOGIN } from '../actions/actionsTypes';
|
||||
import { takeLatest, put, call } from 'redux-saga/effects';
|
||||
import { MESSAGES } from '../actions/actionsTypes';
|
||||
import {
|
||||
messagesSuccess,
|
||||
messagesFailure,
|
||||
|
@ -22,16 +22,16 @@ const toggleStarMessage = message => RocketChat.toggleStarMessage(message);
|
|||
const getPermalink = message => RocketChat.getPermalink(message);
|
||||
const togglePinMessage = message => RocketChat.togglePinMessage(message);
|
||||
|
||||
const get = function* get({ rid }) {
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
if (!auth) {
|
||||
yield take(LOGIN.SUCCESS);
|
||||
}
|
||||
const get = function* get({ room }) {
|
||||
try {
|
||||
yield RocketChat.loadMessagesForRoom(rid, null);
|
||||
if (room.lastOpen) {
|
||||
yield RocketChat.loadMissedMessages(room);
|
||||
} else {
|
||||
yield RocketChat.loadMessagesForRoom(room);
|
||||
}
|
||||
yield put(messagesSuccess());
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.warn('messagesFailure', err);
|
||||
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 RocketChat from '../lib/rocketchat';
|
||||
import { readyPinnedMessages } from '../actions/pinnedMessages';
|
||||
|
||||
const watchPinnedMessagesRoom = function* watchPinnedMessagesRoom({ rid }) {
|
||||
const sub = yield RocketChat.subscribe('pinnedMessages', rid, 50);
|
||||
yield take(types.PINNED_MESSAGES.CLOSE);
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
let sub;
|
||||
let newSub;
|
||||
|
||||
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() {
|
||||
yield takeLatest(types.PINNED_MESSAGES.OPEN, watchPinnedMessagesRoom);
|
||||
yield takeLatest(types.PINNED_MESSAGES.OPEN, openPinnedMessagesRoom);
|
||||
yield takeLatest(types.PINNED_MESSAGES.CLOSE, closePinnedMessagesRoom);
|
||||
};
|
||||
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 RocketChat from '../lib/rocketchat';
|
||||
import { readyRoomFiles } from '../actions/roomFiles';
|
||||
|
||||
const watchRoomFiles = function* watchRoomFiles({ rid }) {
|
||||
const sub = yield RocketChat.subscribe('roomFiles', rid, 50);
|
||||
yield take(types.ROOM_FILES.CLOSE);
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
let sub;
|
||||
let newSub;
|
||||
|
||||
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() {
|
||||
yield takeLatest(types.ROOM_FILES.OPEN, watchRoomFiles);
|
||||
yield takeLatest(types.ROOM_FILES.OPEN, openRoomFiles);
|
||||
yield takeLatest(types.ROOM_FILES.CLOSE, closeRoomFiles);
|
||||
};
|
||||
export default root;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Alert } from 'react-native';
|
||||
import { put, call, takeLatest, take, select, race, fork, cancel, takeEvery } from 'redux-saga/effects';
|
||||
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 { roomsSuccess, roomsFailure } from '../actions/rooms';
|
||||
// import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
||||
import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room';
|
||||
import { messagesRequest } from '../actions/messages';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
@ -13,18 +13,18 @@ import * as NavigationService from '../containers/routes/NavigationService';
|
|||
const leaveRoom = rid => RocketChat.leaveRoom(rid);
|
||||
const eraseRoom = rid => RocketChat.eraseRoom(rid);
|
||||
|
||||
const getRooms = function* getRooms() {
|
||||
return yield RocketChat.getRooms();
|
||||
};
|
||||
// const getRooms = function* getRooms() {
|
||||
// return yield RocketChat.getRooms();
|
||||
// };
|
||||
|
||||
const watchRoomsRequest = function* watchRoomsRequest() {
|
||||
try {
|
||||
yield call(getRooms);
|
||||
yield put(roomsSuccess());
|
||||
} catch (err) {
|
||||
yield put(roomsFailure(err.status));
|
||||
}
|
||||
};
|
||||
// const watchRoomsRequest = function* watchRoomsRequest() {
|
||||
// try {
|
||||
// yield call(getRooms);
|
||||
// yield put(roomsSuccess());
|
||||
// } catch (err) {
|
||||
// yield put(roomsFailure(err.status));
|
||||
// }
|
||||
// };
|
||||
|
||||
const cancelTyping = function* cancelTyping(username) {
|
||||
while (true) {
|
||||
|
@ -50,6 +50,7 @@ const usersTyping = function* usersTyping({ rid }) {
|
|||
}
|
||||
};
|
||||
const handleMessageReceived = function* handleMessageReceived({ message }) {
|
||||
try {
|
||||
const room = yield select(state => state.room);
|
||||
|
||||
if (message.rid === room.rid) {
|
||||
|
@ -59,36 +60,36 @@ const handleMessageReceived = function* handleMessageReceived({ message }) {
|
|||
|
||||
RocketChat.readMessages(room.rid);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('handleMessageReceived', e);
|
||||
}
|
||||
};
|
||||
|
||||
const watchRoomOpen = function* watchRoomOpen({ room }) {
|
||||
const auth = yield select(state => state.login.isAuthenticated);
|
||||
if (!auth) {
|
||||
yield take(types.LOGIN.SUCCESS);
|
||||
}
|
||||
yield put(messagesRequest({ ...room }));
|
||||
// const { open } = yield race({
|
||||
// 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);
|
||||
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 });
|
||||
yield race({
|
||||
open: take(types.ROOM.OPEN),
|
||||
close: take(types.ROOM.CLOSE)
|
||||
});
|
||||
cancel(thread);
|
||||
subscriptions.forEach((sub) => {
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
});
|
||||
sub.stop();
|
||||
|
||||
// subscriptions.forEach((sub) => {
|
||||
// sub.unsubscribe().catch(e => alert(e));
|
||||
// });
|
||||
};
|
||||
|
||||
const watchuserTyping = function* watchuserTyping({ status }) {
|
||||
|
@ -110,13 +111,13 @@ const watchuserTyping = function* watchuserTyping({ status }) {
|
|||
}
|
||||
};
|
||||
|
||||
const updateRoom = function* updateRoom() {
|
||||
const room = yield select(state => state.room);
|
||||
if (!room || !room.rid) {
|
||||
return;
|
||||
}
|
||||
yield put(messagesRequest({ rid: room.rid }));
|
||||
};
|
||||
// const updateRoom = function* updateRoom() {
|
||||
// const room = yield select(state => state.room);
|
||||
// if (!room || !room.rid) {
|
||||
// return;
|
||||
// }
|
||||
// yield put(messagesRequest({ rid: room.rid }));
|
||||
// };
|
||||
|
||||
const updateLastOpen = function* updateLastOpen() {
|
||||
yield put(setLastOpen());
|
||||
|
@ -157,11 +158,10 @@ const handleEraseRoom = function* handleEraseRoom({ rid }) {
|
|||
|
||||
const root = function* root() {
|
||||
yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping);
|
||||
yield takeLatest(types.LOGIN.SUCCESS, watchRoomsRequest);
|
||||
yield takeLatest(types.ROOM.OPEN, watchRoomOpen);
|
||||
yield takeEvery(types.ROOM.MESSAGE_RECEIVED, handleMessageReceived);
|
||||
yield takeLatest(FOREGROUND, updateRoom);
|
||||
yield takeLatest(FOREGROUND, watchRoomsRequest);
|
||||
// yield takeLatest(FOREGROUND, updateRoom);
|
||||
// yield takeLatest(FOREGROUND, watchRoomsRequest);
|
||||
yield takeLatest(BACKGROUND, updateLastOpen);
|
||||
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
||||
yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
|
||||
|
|
|
@ -3,8 +3,9 @@ import { delay } from 'redux-saga';
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { SERVER } from '../actions/actionsTypes';
|
||||
import * as actions from '../actions';
|
||||
import { connectRequest, disconnect, disconnect_by_user } from '../actions/connect';
|
||||
import { changedServer, serverSuccess, serverFailure, serverRequest, setServer } from '../actions/server';
|
||||
import { connectRequest } from '../actions/connect';
|
||||
import { serverSuccess, serverFailure, serverRequest, setServer } from '../actions/server';
|
||||
import { setRoles } from '../actions/roles';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/realm';
|
||||
import * as NavigationService from '../containers/routes/NavigationService';
|
||||
|
@ -14,16 +15,28 @@ const validate = function* validate(server) {
|
|||
};
|
||||
|
||||
const selectServer = function* selectServer({ server }) {
|
||||
try {
|
||||
yield database.setActiveDB(server);
|
||||
yield put(disconnect_by_user());
|
||||
yield put(disconnect());
|
||||
yield put(changedServer(server));
|
||||
|
||||
// yield RocketChat.disconnect();
|
||||
|
||||
yield call([AsyncStorage, 'setItem'], 'currentServer', server);
|
||||
const settings = database.objects('settings');
|
||||
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
||||
const permissions = database.objects('permissions');
|
||||
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 }) {
|
||||
|
@ -32,7 +45,7 @@ const validateServer = function* validateServer({ server }) {
|
|||
yield call(validate, server);
|
||||
yield put(serverSuccess());
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.warn('validateServer', 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 RocketChat from '../lib/rocketchat';
|
||||
import { readySnippetedMessages } from '../actions/snippetedMessages';
|
||||
|
||||
const watchSnippetedMessagesRoom = function* watchSnippetedMessagesRoom({ rid }) {
|
||||
const sub = yield RocketChat.subscribe('snippetedMessages', rid, 50);
|
||||
yield take(types.SNIPPETED_MESSAGES.CLOSE);
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
let sub;
|
||||
let newSub;
|
||||
|
||||
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() {
|
||||
yield takeLatest(types.SNIPPETED_MESSAGES.OPEN, watchSnippetedMessagesRoom);
|
||||
yield takeLatest(types.SNIPPETED_MESSAGES.OPEN, openSnippetedMessagesRoom);
|
||||
yield takeLatest(types.SNIPPETED_MESSAGES.CLOSE, closeSnippetedMessagesRoom);
|
||||
};
|
||||
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 RocketChat from '../lib/rocketchat';
|
||||
import { readyStarredMessages } from '../actions/starredMessages';
|
||||
|
||||
const watchStarredMessagesRoom = function* watchStarredMessagesRoom({ rid }) {
|
||||
const sub = yield RocketChat.subscribe('starredMessages', rid, 50);
|
||||
yield take(types.STARRED_MESSAGES.CLOSE);
|
||||
sub.unsubscribe().catch(e => alert(e));
|
||||
let sub;
|
||||
let newSub;
|
||||
|
||||
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() {
|
||||
yield takeLatest(types.STARRED_MESSAGES.OPEN, watchStarredMessagesRoom);
|
||||
yield takeLatest(types.STARRED_MESSAGES.OPEN, openStarredMessagesRoom);
|
||||
yield takeLatest(types.STARRED_MESSAGES.CLOSE, closeStarredMessagesRoom);
|
||||
};
|
||||
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 deferTimer;
|
||||
|
||||
return (...args) => {
|
||||
const _throttle = (...args) => {
|
||||
const context = scope || this;
|
||||
|
||||
const now = +new Date();
|
||||
|
@ -19,4 +19,8 @@ export default function throttle(fn, threshhold = 250, scope) {
|
|||
fn.apply(context, args);
|
||||
}
|
||||
};
|
||||
|
||||
_throttle.stop = () => clearTimeout(deferTimer);
|
||||
|
||||
return _throttle;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TextInput, View, Text, Switch, TouchableOpacity, SafeAreaView } from 'react-native';
|
||||
import Spinner from 'react-native-loading-spinner-overlay';
|
||||
import { View, Text, Switch, TouchableOpacity, SafeAreaView, ScrollView } from 'react-native';
|
||||
|
||||
import RCTextInput from '../containers/TextInput';
|
||||
import Loading from '../containers/Loading';
|
||||
import LoggedView from './View';
|
||||
import { createChannelRequest } from '../actions/createChannel';
|
||||
import styles from './Styles';
|
||||
import KeyboardView from '../presentation/KeyboardView';
|
||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
createChannel: state.createChannel,
|
||||
users: state.createChannel.users
|
||||
users: state.selectedUsers.users
|
||||
}),
|
||||
dispatch => ({
|
||||
create: data => dispatch(createChannelRequest(data))
|
||||
|
@ -84,20 +86,18 @@ export default class CreateChannelView extends LoggedView {
|
|||
render() {
|
||||
return (
|
||||
<KeyboardView
|
||||
style={[styles.defaultViewBackground, { flex: 1 }]}
|
||||
contentContainerStyle={styles.defaultView}
|
||||
contentContainerStyle={styles.container}
|
||||
keyboardVerticalOffset={128}
|
||||
>
|
||||
<SafeAreaView style={styles.formContainer}>
|
||||
<Text style={styles.label_white}>Channel Name</Text>
|
||||
<TextInput
|
||||
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||
<SafeAreaView>
|
||||
<RCTextInput
|
||||
label='Channel Name'
|
||||
value={this.state.channelName}
|
||||
style={styles.input_white}
|
||||
onChangeText={channelName => this.setState({ channelName })}
|
||||
autoCorrect={false}
|
||||
returnKeyType='done'
|
||||
autoCapitalize='none'
|
||||
autoFocus
|
||||
placeholder='Type the channel name here'
|
||||
returnKeyType='done'
|
||||
autoFocus
|
||||
/>
|
||||
{this.renderChannelNameError()}
|
||||
{this.renderTypeSwitch()}
|
||||
|
@ -120,16 +120,18 @@ export default class CreateChannelView extends LoggedView {
|
|||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => this.submit()}
|
||||
style={[styles.buttonContainer_white, styles.enabledButton]}
|
||||
style={[
|
||||
styles.buttonContainer_white,
|
||||
this.state.channelName.length === 0 || this.props.createChannel.isFetching
|
||||
? styles.disabledButton
|
||||
: styles.enabledButton
|
||||
]}
|
||||
>
|
||||
<Text style={styles.button_white}>CREATE</Text>
|
||||
</TouchableOpacity>
|
||||
<Loading visible={this.props.createChannel.isFetching} />
|
||||
</SafeAreaView>
|
||||
<Spinner
|
||||
visible={this.props.createChannel.isFetching}
|
||||
textContent='Loading...'
|
||||
textStyle={{ color: '#FFF' }}
|
||||
/>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
);
|
||||
}
|
||||
|
|