Merge 4.15.0 into single-server (#2986)

* [FIX] RoomItem using deprecated animated event signature (#2771)

* [FIX] Server autocomplete text breaking line (#2774)

* [FIX] ServerDropdown flashing bigger server icon (#2775)

* [FIX] ServerDropdown flashing bigger server icon

* Remove unused logo and update image path where needed

* Minor tweak

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Rooms list not being updated on some cases (#2765)

* Request subscriptions on RoomsListView.constructor

* Removes opened rooms from last message persisting

* Change server reducer

* Prevent undefined ids causing query error

* [FIX] Share Extension hitting memory limit on iOS (#2788)

* [FIX] Disallow swipe to dismiss on share extension

* Limit query to 20 and clean up props

* Remove rn-extension-share branch pointer

* Test new branch

* Remove branch

* [IMPROVEMENT] Threads layout tweaks (#2686)

* improvement: Thread Details

* fix: re-render Thread Messages Item

* fix: update snapshots

* improve: thread details component

* fix: cast replies length

* improvement: format date of threads

* improvement: thread details styles

* fix: wrap text

* tests: update snapshot

* improvement: use same date format for all dates

* Icon size 24

* Remove date

* Remove prop drill

* Badge position

* Badge container tweak

* Fix inline style

* Move ThreadDetails to containers

* Update stories

* Fix lint

* Remove wrong prop

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] Remove some migrations (#2792)

* Remove force rooms refresh

* Remove MMKV migration

* Bump version to 4.14.0 (#2797)

* [FIX] Messagebox tracking lost on pop gesture navigation (#2799)

* Use setTimeout instead of InteractionManager

* Update tracking lib

* [FIX] Back button closing activity when on root stack screen (#2804)

* Make hardware back button to behave as home button on root screens

* Remove unnecessary code

* Remove handleBackPress from OnboardingView

* Fix lint

* [i18n] Add missing German strings (#2715)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [NEW] Encrypted Discussions  (#2813)

* I18n key fix

* Add encrypted switch

* Remove unused i18n keys

* Add enabled to encryption reducer

* Show encrypted option on CreateDiscussionView only when e2e encryption is properly set

* Add localSearch and use it on search

* Use encrypted from parent channel

* Fix method calls as rest api with 2fa enabled

* Fix logout after reset keys

* Use encryption reducer instead of lib directly to check render

* Check for room type logic to display encryption option on create discussion

* Check toggle-room-e2e-encryption permission on RoomActionsView

* Check for encryption status instead of setting on server

* Fix

* Disable switch instead of hide it

* Fix spotlight for DMs

* Fix server test

* [FIX] Messagebox missing style for text color (#2786)

* Changing auxilaryTintColor

* Changed Placeholder color to BodyText color

* added color prop

* eslint changes

* used array for styles

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [I18N] Update arabic (#2696)

* Update ar.js

* Update ar.js

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Workspace input without i18n (#2689)

* [FIX] Translation of strings in Login page

* Strings are added for translation.

fixes: #2620

* Add pt-BR

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Spotlight returning duplicated entries (#2805)

* Update rocketchat.js

* Updated search function

* Minor improvements

* Remove atIndex

* Add remove logic to remove duplicate data from response

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] Refactor ServerItem (#2778)

* Updated ServerDropdown and ServerItem

* Added ServerItem stories

* Update ServerDropdown.js

* Updated ServerItem stories

* Updated ServerItem stories and ServerItem component

* Updated SelectServerView, ServerItem and ServerItem stories

* Updated ServerItem stories

* Updated ServerItem stories

* Update tests

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [DOCS] Updated Quick Start docs link in e2e/readme (#2802)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [I18N] Add Turkish (#2793)

* Turkish language support added

* Update tr.js

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Lint on #2793 (#2818)

* [I18N] Add missing german strings (#2689) (#2820)

* [I18N] Add missing italian strings (#2817)

* [FIX] Server version becoming null on server change (#2821)

* [FIX] Wrong styling on E2E encryption banner (#2767)

* [FIX] Wrong styling on E2E encryption banner

* [FIX] Wrong styling on E2E encryption banner

* [FIX] Wrong styling on E2E encryption banner

* [FIX] Wrong styling on E2E encryption banner (#2767)

* Updated SortDropdown, ListHeader, ListItem and added stories for List.Item

* Updated SortDropdown

* Removed unused component

* Updated List.Item and stories

* Reverted unnecessary changes and updated ListItem stories

* Fix minor indentation

* Stop breaking Touch's default underlay color

* Fix indentation

* Remove falsy comparison from render

* Fix left icon

* Use List.Item on OmnichannelStatus

* Add missing separator

* Lint

* Fix sort dropdown

* Remove unnecessary styles

* Fix detox

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] App Store using Experimental's app id (#2826)

* [FIX] Wrong username on push notifications (#2825)

* [FIX] Share extension memory issues on iOS (#2845)

* Remove unnecessary class prop

* Stop rendering servers when there's only one

* Map and alloc only necessary columns from query

* Fetch servers count instead of all servers records

* Fetch only needed servers

* Separators

* Remove renderContent

* Minor fix

* Refactor query

* Smaller avatars in memory

* Fix getItemLayout

* Add topic

* Load less pods

* tests

* Import only used functions from lodash

* Fix pods

* Import only used functions from semver

* Fix media sharing

* Update pods

* Disables preview and thumb on iOS

* Update expo-video-thumbnail

* Unnecessary change

* [FIX] Logout from other locations not prompting confirmation option (#2854)

* Fixed logout toast bug for the iOS

* Removing callToAction and replacing with confirmationText

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* Bump version to 4.14.1 (#2859)

* [IMPROVEMENT] Check for focused rooms on in-app notifications (#2857)

* Update InAppNotification and room reducer

* Update InAppNotification

This reverts commit 60330a1e04cfe8d2e5aa311f367083d831682c49.

* Stop subscribing to threads

* Remove ref

* Fix prop-types

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Real name being ignored in SearchMessagesView (#2838)

Co-authored-by: Gerzon Z <gerzonc@icloud.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] Remove unnecessary share reducer calls (#2861)

* Remove unnecesary share reducer calls

* Update Avatar

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Breadcrumbs exceeding characters limit (#2862)

* [FIX] breadcrumbs exceeding

* fix.breadcrumbs-exceeding-change-events

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] App compressing videos on iOS (#2915)

* Update index.js

* Update index.js

* [FIX] Real name setting ignored on reply preview (#2908)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Reply component sending unused prop to Description (#2900)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] BackdropOpacity based on themes (#2863)

* Added backdropOpacity based on theme

* Updated ActionSheet, ReactionsModal, ReactionPicker and Sidebar

* Updated MultiSelect

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Webview not falling back to default auth challenge when no cert is provided (#2918)

* [FIX] Android - fallback to default auth challenge handling when no cert is provided

* If a certificate auth challenge is requested on Android the webview will hang if no certificate is loaded.
  To prevent this, fallback to default Android behavior and cancel the challenge with request.cancel()

* No client certificate case defaults to super implementation

* Update react-native-webview

* Downgrade to previous dependency version

Co-authored-by: Diego Mello <diegolmello@gmail.com>
Co-authored-by: Gerzon Z <gerzonc@icloud.com>
Co-authored-by: Jan Garaj <jan.garaj@gmail.com>

* [FIX] Support Jitsi_URL_Room_Hash (#2905)

* [FIX] Temp attachment files not being flushed after saved to gallery (#2871)

* Update AttachmentView.js

* Update AttachmentView.js

* Update AttachmentView.js

* Update AttachmentView.js

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] Update iOS profiles for Experimental app (#2933)

* [IMPROVE] Deleted thread reply redirects to thread (#2840)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Thread showing typing indicator from main room (#2869)

* [FIX] Remove typing indicator from thread's header

* remove unnecessary props and change usersTyping condition

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] DM rooms show typing status from last group room (#2878)

* [FIX] DM rooms show typing status from last group room

* Undo changes

* Check if current typing is from focused room before dispatching to redux

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Can't copy or edit media's description (#2885)

* [FIX] Image descriptions issues

* shorten the condition string

* fix selectedMessage state

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] RightButtonsContainer re-render check not returning default value (#2899)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] Remove InteractionManager blocks (#2906)

* [FIX] Remove InteractionManager blocks

* Minor fix

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] App not sending second argument for EventEmitter.removeListener on some places (#2909)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Temp message ignoring real name (#2919)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] System message of e2e encryption is missing (#2888)

* [FIX] System message of e2e encryption missing

* add new encryption string

* add to stories

* Add pt-BR

* Move stories

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] Add permissions to Redux (#2914)

* [FIX] Add permissions to Redux store

* add only permissions being used in the app

* add clear permissions reducer

* call RocketChat.hasPermission from reducer

* add server version comparison on getPermissions

* refactor hasPermission function

* refactor hasPermission function

* remove uncomment code

* use Q.experimentalSortBy()

* add coerce function

* Change Rocketchat.hasPermission

* Apply on isReadOnly

* Apply to RoomInfoEditView

* Apply to RoomInfoView and RoomInfoEditView

* canAutoTranslate

* Unnecessary clear permissions

* Revert getUpdatedSince

* Naming fix

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] Add hold step for ios and android build experimental (#2943)

* [CHORE] Add hold step for ios-build-experimental and android-build-experimental

* Android hold step

* add ios hold step

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [IMPROVEMENT] Remove lodash.isEqual (#2893)

* Added dequal and react-fast-compare as substitutes to lodash.isEqual

* Update ReplyPreview.js

* Remove react-fast-compare

* Removed deep-equal and upgrade babel-eslint dev dependency

* Fix avatar

* Fix Messagebox

* Fix CreateDiscussionView

* ModalBlockView

* NewMessageView

* ProfileView

* RoomInfoEditView

* ServerDropdown

* Return local search as object instead of observable

* SelectedUsersView

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [I18N] Add missing Russian strings (#2946)

* [i18n] Add missing Russian strings

* Couple fixes

* Fix Direct_message

Translate Direct_message as already has been translated

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [CHORE] Use shortcut syntax for get collections (#2932)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Use List.Separator in all places (#2931)

* [FIX] Use List.Separator in all places

* add List.Separator

* change List.Separator

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Limit new message list query size to 50 (#2947)

* Limit query to 50

* Remove observable

* [FIX] Support chats order for older versions of the server (#2934)

* Update mergeSubscriptionsRooms.js

* Update mergeSubscriptionsRooms.js

* Update mergeSubscriptionsRooms.js

* Minor refactor

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Reactions modal's backdrop color too light (#2949)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* Bump version to 4.15.0 (#2950)

* [FIX] Share extension not working correctly on Official app (#2963)

* [FIX] Cannot read property 'some' of undefined on hasPermission (#2966)

Co-authored-by: Diego Mello <diegolmello@gmail.com>

* [FIX] Deep linking and other connectivity issues (#2894)

* Navigate from push notification only if necessary

* Use JS SDK branch

* Stop reconnecting if it's already connected

* Fix RoomsListView forever loading after tapping push notification of another server

* Execute fewer operations on app/index

* Remove roomsRequest call from onForeground

* Apply check and reopen

* Stop opening in-app notification when the app is on backgorund

* Connecting tweaks

* Fix deep linking not working if the app is on background

* Force reset yarn cache

* Upgrade JS SDK

* Remove listener on unmount

* Fix resume on Android after back button is pressed

* Fix local authentication resume

* Fix back button android

* Change JS SDK branch

* [FIX] Messagebox's placeholder color is too bright (#2968)

Co-authored-by: Gerzon Z <gerzonzcanario@gmail.com>
Co-authored-by: Gerzon Z <gerzonc@icloud.com>
Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
Co-authored-by: phriedrich <info@phriedrich.de>
Co-authored-by: yash-rajpal <58601732+yash-rajpal@users.noreply.github.com>
Co-authored-by: Fazil Boudjelal <fazildiablou@hotmail.fr>
Co-authored-by: Sumukha Hegde <SUMUKHA214@GMAIL.COM>
Co-authored-by: Hakan YILMAZ <mukerrem.yilmaz@hotmail.com>
Co-authored-by: Vincenzo Esposito <aenon.esposito@gmail.com>
Co-authored-by: Arkadyuti Bandyopadhyay <bandyopadhyayarkadyuti@gmail.com>
Co-authored-by: Anant Bhasin <38764067+aKn1ghtOut@users.noreply.github.com>
Co-authored-by: Gung Wah <41157464+kresnaputra@users.noreply.github.com>
Co-authored-by: Billy Newman <newmanw10@gmail.com>
Co-authored-by: Jan Garaj <jan.garaj@gmail.com>
Co-authored-by: ankar84 <ankar84@gmail.com>
This commit is contained in:
Diego Mello 2021-03-15 17:16:34 -03:00 committed by GitHub
parent 322dabc762
commit a6e937f247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
128 changed files with 1365 additions and 1015 deletions

View File

@ -237,9 +237,13 @@ commands:
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
/usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY_OFFICIAL" ./RocketChatRN/Info.plist /usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY_OFFICIAL" ./RocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./RocketChatRN/Info.plist /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./RocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./ShareRocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./NotificationService/Info.plist
else else
/usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY" ./RocketChatRN/Info.plist /usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY" ./RocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./RocketChatRN/Info.plist /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./RocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./ShareRocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist
fi fi
if [[ $APP_STORE_CONNECT_API_KEY ]]; then if [[ $APP_STORE_CONNECT_API_KEY ]]; then
@ -416,9 +420,13 @@ workflows:
- lint-testunit - lint-testunit
# iOS Experimental # iOS Experimental
- ios-hold-build-experimental:
type: approval
requires:
- lint-testunit
- ios-build-experimental: - ios-build-experimental:
requires: requires:
- lint-testunit - ios-hold-build-experimental
- ios-hold-testflight-experimental: - ios-hold-testflight-experimental:
type: approval type: approval
requires: requires:
@ -444,9 +452,13 @@ workflows:
- ios-hold-testflight-official - ios-hold-testflight-official
# Android Experimental # Android Experimental
- android-hold-build-experimental:
type: approval
requires:
- lint-testunit
- android-build-experimental: - android-build-experimental:
requires: requires:
- lint-testunit - android-hold-build-experimental
- android-hold-google-play-beta-experimental: - android-hold-google-play-beta-experimental:
type: approval type: approval
requires: requires:

View File

@ -6,7 +6,7 @@ module.exports = {
} }
} }
}, },
"parser": "babel-eslint", "parser": "@babel/eslint-parser",
"extends": "airbnb", "extends": "airbnb",
"parserOptions": { "parserOptions": {
"sourceType": "module", "sourceType": "module",
@ -21,7 +21,8 @@ module.exports = {
"react", "react",
"jsx-a11y", "jsx-a11y",
"import", "import",
"react-native" "react-native",
"@babel"
], ],
"env": { "env": {
"browser": true, "browser": true,
@ -148,7 +149,8 @@ module.exports = {
"react/jsx-curly-newline": [0], "react/jsx-curly-newline": [0],
"react/state-in-constructor": [0], "react/state-in-constructor": [0],
"no-async-promise-executor": [0], "no-async-promise-executor": [0],
"max-classes-per-file": [0] "max-classes-per-file": [0],
"no-multiple-empty-lines": [0]
}, },
"globals": { "globals": {
"__DEV__": true "__DEV__": true

View File

@ -44404,6 +44404,309 @@ exports[`Storyshots Message list message 1`] = `
</View> </View>
</View> </View>
</View> </View>
<Text
style={
Array [
Object {
"fontSize": 20,
"fontWeight": "300",
"marginLeft": 10,
"marginVertical": 30,
},
Object {
"color": "#0d0e12",
},
Object {
"marginBottom": 0,
"marginTop": 30,
},
]
}
>
Toggle e2e encryption
</Text>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View>
<View
style={
Array [
Object {
"flexDirection": "column",
"paddingHorizontal": 14,
"paddingVertical": 4,
"width": "100%",
},
undefined,
]
}
>
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Array [
Object {
"borderRadius": 2,
"height": 20,
"width": 20,
},
Object {
"marginLeft": 16,
},
]
}
>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View
style={
Array [
Object {
"overflow": "hidden",
},
Object {
"borderRadius": 2,
"height": 20,
"width": 20,
},
]
}
>
<FastImageView
resizeMode="cover"
source={
Object {
"headers": undefined,
"priority": "high",
"uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=20",
}
}
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
</View>
</View>
</View>
<View
style={
Array [
Object {
"flex": 1,
"marginLeft": 46,
},
Object {
"marginLeft": 10,
},
]
}
>
<Text
accessibilityLabel="This room's encryption has been disabled by diego.mello"
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 16,
"fontStyle": "italic",
"fontWeight": "400",
"textAlign": "left",
},
Object {
"color": "#9ca2a8",
},
]
}
>
This room's encryption has been disabled by diego.mello
</Text>
</View>
</View>
</View>
</View>
</View>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View>
<View
style={
Array [
Object {
"flexDirection": "column",
"paddingHorizontal": 14,
"paddingVertical": 4,
"width": "100%",
},
undefined,
]
}
>
<View
style={
Object {
"flexDirection": "row",
}
}
>
<View
style={
Array [
Object {
"borderRadius": 2,
"height": 20,
"width": 20,
},
Object {
"marginLeft": 16,
},
]
}
>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View
style={
Array [
Object {
"overflow": "hidden",
},
Object {
"borderRadius": 2,
"height": 20,
"width": 20,
},
]
}
>
<FastImageView
resizeMode="cover"
source={
Object {
"headers": undefined,
"priority": "high",
"uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=20",
}
}
style={
Object {
"bottom": 0,
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
}
}
/>
</View>
</View>
</View>
<View
style={
Array [
Object {
"flex": 1,
"marginLeft": 46,
},
Object {
"marginLeft": 10,
},
]
}
>
<Text
accessibilityLabel="This room's encryption has been enabled by diego.mello"
style={
Array [
Object {
"backgroundColor": "transparent",
"fontFamily": "System",
"fontSize": 16,
"fontStyle": "italic",
"fontWeight": "400",
"textAlign": "left",
},
Object {
"color": "#9ca2a8",
},
]
}
>
This room's encryption has been enabled by diego.mello
</Text>
</View>
</View>
</View>
</View>
</View>
<Text <Text
style={ style={
Array [ Array [

View File

@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer versionCode VERSIONCODE as Integer
versionName "4.14.1" versionName "4.15.0"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
if (!isFoss) { if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]

View File

@ -70,3 +70,5 @@ export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']); export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']); export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']); export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']);
export const PERMISSIONS = createRequestTypes('PERMISSIONS', ['SET']);

View File

@ -0,0 +1,8 @@
import * as types from './actionsTypes';
export function setPermissions(permissions) {
return {
type: types.PERMISSIONS.SET,
permissions
};
}

View File

@ -63,6 +63,7 @@ export const themes = {
passcodeDotFull: '#6C727A', passcodeDotFull: '#6C727A',
previewBackground: '#1F2329', previewBackground: '#1F2329',
previewTintColor: '#ffffff', previewTintColor: '#ffffff',
backdropOpacity: 0.3,
...mentions ...mentions
}, },
dark: { dark: {
@ -109,6 +110,7 @@ export const themes = {
passcodeDotFull: '#6C727A', passcodeDotFull: '#6C727A',
previewBackground: '#030b1b', previewBackground: '#030b1b',
previewTintColor: '#ffffff', previewTintColor: '#ffffff',
backdropOpacity: 0.9,
...mentions ...mentions
}, },
black: { black: {
@ -155,6 +157,7 @@ export const themes = {
passcodeDotFull: '#6C727A', passcodeDotFull: '#6C727A',
previewBackground: '#000000', previewBackground: '#000000',
previewTintColor: '#ffffff', previewTintColor: '#ffffff',
backdropOpacity: 0.9,
...mentions ...mentions
} }
}; };

View File

@ -1,6 +1,6 @@
import { getBundleId, isIOS } from '../utils/deviceInfo'; import { getBundleId, isIOS } from '../utils/deviceInfo';
import { appStoreID as APP_STORE_ID } from '../../app.json'; const APP_STORE_ID = '1148741252';
export const PLAY_MARKET_LINK = `https://play.google.com/store/apps/details?id=${ getBundleId }`; export const PLAY_MARKET_LINK = `https://play.google.com/store/apps/details?id=${ getBundleId }`;
export const FDROID_MARKET_LINK = 'https://f-droid.org/en/packages/chat.rocket.android'; export const FDROID_MARKET_LINK = 'https://f-droid.org/en/packages/chat.rocket.android';

View File

@ -101,6 +101,9 @@ export default {
Jitsi_Enabled_TokenAuth: { Jitsi_Enabled_TokenAuth: {
type: 'valueAsBoolean' type: 'valueAsBoolean'
}, },
Jitsi_URL_Room_Hash: {
type: 'valueAsBoolean'
},
Jitsi_URL_Room_Prefix: { Jitsi_URL_Room_Prefix: {
type: 'valueAsString' type: 'valueAsString'
}, },

View File

@ -147,7 +147,7 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
const animatedPosition = React.useRef(new Value(0)); const animatedPosition = React.useRef(new Value(0));
const opacity = interpolate(animatedPosition.current, { const opacity = interpolate(animatedPosition.current, {
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, 0.7], outputRange: [0, themes[theme].backdropOpacity],
extrapolate: Extrapolate.CLAMP extrapolate: Extrapolate.CLAMP
}); });

View File

@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import isEqual from 'react-fast-compare';
import database from '../../lib/database'; import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
@ -34,7 +33,8 @@ class AvatarContainer extends React.Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (!isEqual(prevProps, this.props)) { const { text, type } = this.props;
if (prevProps.text !== text || prevProps.type !== type) {
this.init(); this.init();
} }
} }
@ -52,8 +52,8 @@ class AvatarContainer extends React.Component {
init = async() => { init = async() => {
const db = database.active; const db = database.active;
const usersCollection = db.collections.get('users'); const usersCollection = db.get('users');
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.get('subscriptions');
let record; let record;
try { try {

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ScrollableTabView from 'react-native-scrollable-tab-view'; import ScrollableTabView from 'react-native-scrollable-tab-view';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import orderBy from 'lodash/orderBy'; import orderBy from 'lodash/orderBy';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
@ -67,7 +67,7 @@ class EmojiPicker extends Component {
if (nextState.width !== width) { if (nextState.width !== width) {
return true; return true;
} }
if (!equal(nextState.frequentlyUsed, frequentlyUsed)) { if (!dequal(nextState.frequentlyUsed, frequentlyUsed)) {
return true; return true;
} }
return false; return false;
@ -95,7 +95,7 @@ class EmojiPicker extends Component {
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
_addFrequentlyUsed = protectedFunction(async(emoji) => { _addFrequentlyUsed = protectedFunction(async(emoji) => {
const db = database.active; const db = database.active;
const freqEmojiCollection = db.collections.get('frequently_used_emojis'); const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojiRecord; let freqEmojiRecord;
try { try {
freqEmojiRecord = await freqEmojiCollection.find(emoji.content); freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
@ -120,7 +120,7 @@ class EmojiPicker extends Component {
updateFrequentlyUsed = async() => { updateFrequentlyUsed = async() => {
const db = database.active; const db = database.active;
const frequentlyUsedRecords = await db.collections.get('frequently_used_emojis').query().fetch(); const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
let frequentlyUsed = orderBy(frequentlyUsedRecords, ['count'], ['desc']); let frequentlyUsed = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
frequentlyUsed = frequentlyUsed.map((item) => { frequentlyUsed = frequentlyUsed.map((item) => {
if (item.isCustom) { if (item.isCustom) {

View File

@ -1,5 +1,8 @@
import React, { memo, useEffect } from 'react'; import React, { memo, useEffect } from 'react';
import PropTypes from 'prop-types';
import { NotifierRoot, Notifier, Easing } from 'react-native-notifier'; import { NotifierRoot, Notifier, Easing } from 'react-native-notifier';
import { connect } from 'react-redux';
import { dequal } from 'dequal';
import NotifierComponent from './NotifierComponent'; import NotifierComponent from './NotifierComponent';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
@ -8,13 +11,17 @@ import { getActiveRoute } from '../../utils/navigation';
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp'; export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
const InAppNotification = memo(() => { const InAppNotification = memo(({ rooms, appState }) => {
const show = (notification) => { const show = (notification) => {
if (appState !== 'foreground') {
return;
}
const { payload } = notification; const { payload } = notification;
const state = Navigation.navigationRef.current?.getRootState(); const state = Navigation.navigationRef.current?.getRootState();
const route = getActiveRoute(state); const route = getActiveRoute(state);
if (payload.rid) { if (payload.rid) {
if ((route?.name === 'RoomView' && route.params?.rid === payload.rid) || route?.name === 'JitsiMeetView') { if (rooms.includes(payload.rid) || route?.name === 'JitsiMeetView') {
return; return;
} }
Notifier.showNotification({ Notifier.showNotification({
@ -28,13 +35,23 @@ const InAppNotification = memo(() => {
}; };
useEffect(() => { useEffect(() => {
EventEmitter.addEventListener(INAPP_NOTIFICATION_EMITTER, show); const listener = EventEmitter.addEventListener(INAPP_NOTIFICATION_EMITTER, show);
return () => { return () => {
EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER); EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener);
}; };
}, []); }, [rooms]);
return <NotifierRoot />; return <NotifierRoot />;
}, (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms));
const mapStateToProps = state => ({
rooms: state.room.rooms,
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
}); });
export default InAppNotification; InAppNotification.propTypes = {
rooms: PropTypes.array,
appState: PropTypes.string
};
export default connect(mapStateToProps)(InAppNotification);

View File

@ -96,7 +96,7 @@ const Header = React.memo(({
const setEmojis = async() => { const setEmojis = async() => {
try { try {
const db = database.active; const db = database.active;
const freqEmojiCollection = db.collections.get('frequently_used_emojis'); const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojis = await freqEmojiCollection.query().fetch(); let freqEmojis = await freqEmojiCollection.query().fetch();
const isLandscape = width > height; const isLandscape = width > height;

View File

@ -34,20 +34,24 @@ const MessageActions = React.memo(forwardRef(({
Message_AllowPinning, Message_AllowPinning,
Message_AllowStarring, Message_AllowStarring,
Message_Read_Receipt_Store_Users, Message_Read_Receipt_Store_Users,
isMasterDetail isMasterDetail,
editMessagePermission,
deleteMessagePermission,
forceDeleteMessagePermission,
pinMessagePermission
}, ref) => { }, ref) => {
let permissions = {}; let permissions = {};
const { showActionSheet, hideActionSheet } = useActionSheet(); const { showActionSheet, hideActionSheet } = useActionSheet();
const getPermissions = async() => { const getPermissions = async() => {
try { try {
const permission = ['edit-message', 'delete-message', 'force-delete-message', 'pin-message']; const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission];
const result = await RocketChat.hasPermission(permission, room.rid); const result = await RocketChat.hasPermission(permission, room.rid);
permissions = { permissions = {
hasEditPermission: result[permission[0]], hasEditPermission: result[0],
hasDeletePermission: result[permission[1]], hasDeletePermission: result[1],
hasForceDeletePermission: result[permission[2]], hasForceDeletePermission: result[2],
hasPinPermission: result[permission[3]] hasPinPermission: result[3]
}; };
} catch { } catch {
// Do nothing // Do nothing
@ -141,7 +145,7 @@ const MessageActions = React.memo(forwardRef(({
const db = database.active; const db = database.active;
const result = await RocketChat.markAsUnread({ messageId }); const result = await RocketChat.markAsUnread({ messageId });
if (result.success) { if (result.success) {
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid); const subRecord = await subCollection.find(rid);
await db.action(async() => { await db.action(async() => {
try { try {
@ -171,7 +175,7 @@ const MessageActions = React.memo(forwardRef(({
const handleCopy = async(message) => { const handleCopy = async(message) => {
logEvent(events.ROOM_MSG_ACTION_COPY); logEvent(events.ROOM_MSG_ACTION_COPY);
await Clipboard.setString(message.msg); await Clipboard.setString(message?.attachments?.[0]?.description || message.msg);
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
}; };
@ -440,7 +444,11 @@ MessageActions.propTypes = {
Message_AllowPinning: PropTypes.bool, Message_AllowPinning: PropTypes.bool,
Message_AllowStarring: PropTypes.bool, Message_AllowStarring: PropTypes.bool,
Message_Read_Receipt_Store_Users: PropTypes.bool, Message_Read_Receipt_Store_Users: PropTypes.bool,
server: PropTypes.string server: PropTypes.string,
editMessagePermission: PropTypes.array,
deleteMessagePermission: PropTypes.array,
forceDeleteMessagePermission: PropTypes.array,
pinMessagePermission: PropTypes.array
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
@ -452,7 +460,11 @@ const mapStateToProps = state => ({
Message_AllowPinning: state.settings.Message_AllowPinning, Message_AllowPinning: state.settings.Message_AllowPinning,
Message_AllowStarring: state.settings.Message_AllowStarring, Message_AllowStarring: state.settings.Message_AllowStarring,
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users, Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users,
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail,
editMessagePermission: state.permissions['edit-message'],
deleteMessagePermission: state.permissions['delete-message'],
forceDeleteMessagePermission: state.permissions['force-delete-message'],
pinMessagePermission: state.permissions['pin-message']
}); });
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions); export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import Item from './Item'; import Item from './Item';
import styles from '../styles'; import styles from '../styles';
@ -31,7 +31,7 @@ const CommandsPreview = React.memo(({ theme, commandPreview, showCommandPreview
if (prevProps.showCommandPreview !== nextProps.showCommandPreview) { if (prevProps.showCommandPreview !== nextProps.showCommandPreview) {
return false; return false;
} }
if (!equal(prevProps.commandPreview, nextProps.commandPreview)) { if (!dequal(prevProps.commandPreview, nextProps.commandPreview)) {
return false; return false;
} }
return true; return true;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { FlatList, View } from 'react-native'; import { FlatList, View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import styles from '../styles'; import styles from '../styles';
import MentionItem from './MentionItem'; import MentionItem from './MentionItem';
@ -30,7 +30,7 @@ const Mentions = React.memo(({ mentions, trackingType, theme }) => {
if (prevProps.trackingType !== nextProps.trackingType) { if (prevProps.trackingType !== nextProps.trackingType) {
return false; return false;
} }
if (!equal(prevProps.mentions, nextProps.mentions)) { if (!dequal(prevProps.mentions, nextProps.mentions)) {
return false; return false;
} }
return true; return true;

View File

@ -3,7 +3,6 @@ import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import moment from 'moment'; import moment from 'moment';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import isEqual from 'lodash/isEqual';
import Markdown from '../markdown'; import Markdown from '../markdown';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
@ -43,7 +42,7 @@ const styles = StyleSheet.create({
}); });
const ReplyPreview = React.memo(({ const ReplyPreview = React.memo(({
message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme, useRealName
}) => { }) => {
if (!replying) { if (!replying) {
return null; return null;
@ -59,7 +58,7 @@ const ReplyPreview = React.memo(({
> >
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}> <View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
<View style={styles.header}> <View style={styles.header}>
<Text style={[styles.username, { color: themes[theme].tintColor }]}>{message.u?.username}</Text> <Text style={[styles.username, { color: themes[theme].tintColor }]}>{useRealName ? message.u?.name : message.u?.username}</Text>
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View> </View>
<Markdown <Markdown
@ -75,7 +74,7 @@ const ReplyPreview = React.memo(({
<CustomIcon name='close' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} /> <CustomIcon name='close' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} />
</View> </View>
); );
}, (prevProps, nextProps) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme && isEqual(prevProps.message, nextProps.message)); }, (prevProps, nextProps) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme && prevProps.message.id === nextProps.message.id);
ReplyPreview.propTypes = { ReplyPreview.propTypes = {
replying: PropTypes.bool, replying: PropTypes.bool,
@ -85,12 +84,14 @@ ReplyPreview.propTypes = {
baseUrl: PropTypes.string.isRequired, baseUrl: PropTypes.string.isRequired,
username: PropTypes.string.isRequired, username: PropTypes.string.isRequired,
getCustomEmoji: PropTypes.func, getCustomEmoji: PropTypes.func,
theme: PropTypes.string theme: PropTypes.string,
useRealName: PropTypes.bool
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
Message_TimeFormat: state.settings.Message_TimeFormat, Message_TimeFormat: state.settings.Message_TimeFormat,
baseUrl: state.server.server baseUrl: state.server.server,
useRealName: state.settings.UI_Use_Real_Name
}); });
export default connect(mapStateToProps)(ReplyPreview); export default connect(mapStateToProps)(ReplyPreview);

View File

@ -6,7 +6,7 @@ import {
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard'; import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker from 'react-native-image-crop-picker'; import ImagePicker from 'react-native-image-crop-picker';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import DocumentPicker from 'react-native-document-picker'; import DocumentPicker from 'react-native-document-picker';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler'; import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
@ -63,6 +63,7 @@ const imagePickerConfig = {
const libraryPickerConfig = { const libraryPickerConfig = {
multiple: true, multiple: true,
compressVideoPreset: 'Passthrough',
mediaType: 'any' mediaType: 'any'
}; };
@ -189,8 +190,8 @@ class MessageBox extends Component {
} = this.props; } = this.props;
let msg; let msg;
try { try {
const threadsCollection = db.collections.get('threads'); const threadsCollection = db.get('threads');
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.get('subscriptions');
try { try {
this.room = await subsCollection.find(rid); this.room = await subsCollection.find(rid);
} catch (error) { } catch (error) {
@ -270,7 +271,7 @@ class MessageBox extends Component {
} = this.state; } = this.state;
const { const {
roomType, replying, editing, isFocused, message, theme, children roomType, replying, editing, isFocused, message, theme
} = this.props; } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
return true; return true;
@ -299,16 +300,13 @@ class MessageBox extends Component {
if (nextState.tshow !== tshow) { if (nextState.tshow !== tshow) {
return true; return true;
} }
if (!equal(nextState.mentions, mentions)) { if (!dequal(nextState.mentions, mentions)) {
return true; return true;
} }
if (!equal(nextState.commandPreview, commandPreview)) { if (!dequal(nextState.commandPreview, commandPreview)) {
return true; return true;
} }
if (!equal(nextProps.message, message)) { if (!dequal(nextProps.message?.id, message?.id)) {
return true;
}
if (!equal(nextProps.children, children)) {
return true; return true;
} }
return false; return false;
@ -366,7 +364,7 @@ class MessageBox extends Component {
const slashCommand = text.match(/^\/([a-z0-9._-]+) (.+)/im); const slashCommand = text.match(/^\/([a-z0-9._-]+) (.+)/im);
if (slashCommand) { if (slashCommand) {
const [, name, params] = slashCommand; const [, name, params] = slashCommand;
const commandsCollection = db.collections.get('slash_commands'); const commandsCollection = db.get('slash_commands');
try { try {
const command = await commandsCollection.find(name); const command = await commandsCollection.find(name);
if (command.providesPreview) { if (command.providesPreview) {
@ -507,7 +505,7 @@ class MessageBox extends Component {
getEmojis = debounce(async(keyword) => { getEmojis = debounce(async(keyword) => {
const db = database.active; const db = database.active;
if (keyword) { if (keyword) {
const customEmojisCollection = db.collections.get('custom_emojis'); const customEmojisCollection = db.get('custom_emojis');
const likeString = sanitizeLikeString(keyword); const likeString = sanitizeLikeString(keyword);
let customEmojis = await customEmojisCollection.query( let customEmojis = await customEmojisCollection.query(
Q.where('name', Q.like(`${ likeString }%`)) Q.where('name', Q.like(`${ likeString }%`))
@ -521,7 +519,7 @@ class MessageBox extends Component {
getSlashCommands = debounce(async(keyword) => { getSlashCommands = debounce(async(keyword) => {
const db = database.active; const db = database.active;
const commandsCollection = db.collections.get('slash_commands'); const commandsCollection = db.get('slash_commands');
const likeString = sanitizeLikeString(keyword); const likeString = sanitizeLikeString(keyword);
const commands = await commandsCollection.query( const commands = await commandsCollection.query(
Q.where('id', Q.like(`${ likeString }%`)) Q.where('id', Q.like(`${ likeString }%`))
@ -753,7 +751,7 @@ class MessageBox extends Component {
// Slash command // Slash command
if (message[0] === MENTIONS_TRACKING_TYPE_COMMANDS) { if (message[0] === MENTIONS_TRACKING_TYPE_COMMANDS) {
const db = database.active; const db = database.active;
const commandsCollection = db.collections.get('slash_commands'); const commandsCollection = db.get('slash_commands');
const command = message.replace(/ .*/, '').slice(1); const command = message.replace(/ .*/, '').slice(1);
const likeString = sanitizeLikeString(command); const likeString = sanitizeLikeString(command);
const slashCommand = await commandsCollection.query( const slashCommand = await commandsCollection.query(
@ -941,7 +939,7 @@ class MessageBox extends Component {
keyboardType='twitter' keyboardType='twitter'
blurOnSubmit={false} blurOnSubmit={false}
placeholder={I18n.t('New_Message')} placeholder={I18n.t('New_Message')}
placeholderTextColor={themes[theme].auxiliaryTintColor} placeholderTextColor={themes[theme].auxiliaryText}
onChangeText={this.onChangeText} onChangeText={this.onChangeText}
onSelectionChange={this.onSelectionChange} onSelectionChange={this.onSelectionChange}
underlineColorAndroid='transparent' underlineColorAndroid='transparent'

View File

@ -19,8 +19,8 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => {
try { try {
const db = database.active; const db = database.active;
const deleteBatch = []; const deleteBatch = [];
const msgCollection = db.collections.get('messages'); const msgCollection = db.get('messages');
const threadCollection = db.collections.get('threads'); const threadCollection = db.get('threads');
// Delete the object (it can be Message or ThreadMessage instance) // Delete the object (it can be Message or ThreadMessage instance)
deleteBatch.push(message.prepareDestroyPermanently()); deleteBatch.push(message.prepareDestroyPermanently());

View File

@ -28,7 +28,7 @@ class Toast extends React.Component {
} }
componentDidMount() { componentDidMount() {
EventEmitter.addEventListener(LISTENER, this.showToast); this.listener = EventEmitter.addEventListener(LISTENER, this.showToast);
} }
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
@ -40,7 +40,7 @@ class Toast extends React.Component {
} }
componentWillUnmount() { componentWillUnmount() {
EventEmitter.removeListener(LISTENER); EventEmitter.removeListener(LISTENER, this.listener);
} }
getToastRef = toast => this.toast = toast; getToastRef = toast => this.toast = toast;

View File

@ -58,9 +58,9 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }) => {
const showTwoFactor = args => setData(args); const showTwoFactor = args => setData(args);
useEffect(() => { useEffect(() => {
EventEmitter.addEventListener(TWO_FACTOR, showTwoFactor); const listener = EventEmitter.addEventListener(TWO_FACTOR, showTwoFactor);
return () => EventEmitter.removeListener(TWO_FACTOR); return () => EventEmitter.removeListener(TWO_FACTOR, listener);
}, []); }, []);
const onCancel = () => { const onCancel = () => {

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
View, Text, TouchableWithoutFeedback, Modal, KeyboardAvoidingView, Animated, Easing View, Text, TouchableWithoutFeedback, Modal, KeyboardAvoidingView, Animated, Easing, StyleSheet
} from 'react-native'; } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
@ -172,7 +172,7 @@ export const MultiSelect = React.memo(({
> >
<TouchableWithoutFeedback onPress={onHide}> <TouchableWithoutFeedback onPress={onHide}>
<View style={styles.container}> <View style={styles.container}>
<View style={[styles.backdrop, { backgroundColor: themes[theme].backdropColor }]} /> <View style={{ ...StyleSheet.absoluteFill, opacity: themes[theme].backdropOpacity, backgroundColor: themes[theme].backdropColor }} />
<KeyboardAvoidingView style={styles.keyboardView} behavior={behavior}> <KeyboardAvoidingView style={styles.keyboardView} behavior={behavior}>
<Animated.View style={[styles.animatedContent, { transform: [{ translateY }] }]}> <Animated.View style={[styles.animatedContent, { transform: [{ translateY }] }]}>
{showContent ? renderContent() : null} {showContent ? renderContent() : null}

View File

@ -8,10 +8,6 @@ export default StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-end' justifyContent: 'flex-end'
}, },
backdrop: {
...StyleSheet.absoluteFill,
opacity: 0.3
},
modal: { modal: {
height: 300, height: 300,
width: '100%', width: '100%',

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import isEqual from 'lodash/isEqual'; import { dequal } from 'dequal';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Image from './Image'; import Image from './Image';
@ -28,7 +28,7 @@ const Attachments = React.memo(({
// eslint-disable-next-line react/no-array-index-key // eslint-disable-next-line react/no-array-index-key
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} theme={theme} />; return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} theme={theme} />;
}); });
}, (prevProps, nextProps) => isEqual(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme); }, (prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme);
Attachments.propTypes = { Attachments.propTypes = {
attachments: PropTypes.array, attachments: PropTypes.array,

View File

@ -6,7 +6,7 @@ import {
import { Audio } from 'expo-av'; import { Audio } from 'expo-av';
import Slider from '@react-native-community/slider'; import Slider from '@react-native-community/slider';
import moment from 'moment'; import moment from 'moment';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake'; import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
import Touchable from './Touchable'; import Touchable from './Touchable';
@ -150,7 +150,7 @@ class MessageAudio extends React.Component {
if (nextState.paused !== paused) { if (nextState.paused !== paused) {
return true; return true;
} }
if (!equal(nextProps.file, file)) { if (!dequal(nextProps.file, file)) {
return true; return true;
} }
if (nextState.loading !== loading) { if (nextState.loading !== loading) {

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import I18n from '../../i18n'; import I18n from '../../i18n';
import styles from './styles'; import styles from './styles';
@ -108,10 +108,10 @@ const Content = React.memo((props) => {
if (prevProps.isIgnored !== nextProps.isIgnored) { if (prevProps.isIgnored !== nextProps.isIgnored) {
return false; return false;
} }
if (!equal(prevProps.mentions, nextProps.mentions)) { if (!dequal(prevProps.mentions, nextProps.mentions)) {
return false; return false;
} }
if (!equal(prevProps.channels, nextProps.channels)) { if (!dequal(prevProps.channels, nextProps.channels)) {
return false; return false;
} }
return true; return true;

View File

@ -2,7 +2,7 @@ import React, { useContext } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import { createImageProgress } from 'react-native-image-progress'; import { createImageProgress } from 'react-native-image-progress';
import * as Progress from 'react-native-progress'; import * as Progress from 'react-native-progress';
@ -66,7 +66,7 @@ const ImageContainer = React.memo(({
<MessageImage img={img} theme={theme} /> <MessageImage img={img} theme={theme} />
</Button> </Button>
); );
}, (prevProps, nextProps) => equal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme); }, (prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme);
ImageContainer.propTypes = { ImageContainer.propTypes = {
file: PropTypes.object, file: PropTypes.object,

View File

@ -119,7 +119,7 @@ const MessageTouchable = React.memo((props) => {
<Touchable <Touchable
onLongPress={onLongPress} onLongPress={onLongPress}
onPress={onPress} onPress={onPress}
disabled={props.isInfo || props.archived || props.isTemp} disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp}
> >
<View> <View>
<Message {...props} /> <Message {...props} />
@ -132,6 +132,7 @@ MessageTouchable.displayName = 'MessageTouchable';
MessageTouchable.propTypes = { MessageTouchable.propTypes = {
hasError: PropTypes.bool, hasError: PropTypes.bool,
isInfo: PropTypes.bool, isInfo: PropTypes.bool,
isThreadReply: PropTypes.bool,
isTemp: PropTypes.bool, isTemp: PropTypes.bool,
archived: PropTypes.bool archived: PropTypes.bool
}; };

View File

@ -2,7 +2,7 @@ import React, { useContext } from 'react';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import moment from 'moment'; import moment from 'moment';
import isEqual from 'deep-equal'; import { dequal } from 'dequal';
import Touchable from './Touchable'; import Touchable from './Touchable';
import Markdown from '../markdown'; import Markdown from '../markdown';
@ -125,7 +125,7 @@ const Fields = React.memo(({ attachment, theme }) => {
))} ))}
</View> </View>
); );
}, (prevProps, nextProps) => isEqual(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme); }, (prevProps, nextProps) => dequal(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme);
const Reply = React.memo(({ const Reply = React.memo(({
attachment, timeFormat, index, getCustomEmoji, theme attachment, timeFormat, index, getCustomEmoji, theme
@ -172,7 +172,6 @@ const Reply = React.memo(({
/> />
<Description <Description
attachment={attachment} attachment={attachment}
timeFormat={timeFormat}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme} theme={theme}
/> />
@ -188,7 +187,7 @@ const Reply = React.memo(({
/> />
</> </>
); );
}, (prevProps, nextProps) => isEqual(prevProps.attachment, nextProps.attachment) && prevProps.theme === nextProps.theme); }, (prevProps, nextProps) => dequal(prevProps.attachment, nextProps.attachment) && prevProps.theme === nextProps.theme);
Reply.propTypes = { Reply.propTypes = {
attachment: PropTypes.object, attachment: PropTypes.object,

View File

@ -4,7 +4,7 @@ import {
} from 'react-native'; } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
import isEqual from 'lodash/isEqual'; import { dequal } from 'dequal';
import Touchable from './Touchable'; import Touchable from './Touchable';
import openLink from '../../utils/openLink'; import openLink from '../../utils/openLink';
@ -112,7 +112,7 @@ const Url = React.memo(({ url, index, theme }) => {
</> </>
</Touchable> </Touchable>
); );
}, (oldProps, newProps) => isEqual(oldProps.url, newProps.url) && oldProps.theme === newProps.theme); }, (oldProps, newProps) => dequal(oldProps.url, newProps.url) && oldProps.theme === newProps.theme);
const Urls = React.memo(({ urls, theme }) => { const Urls = React.memo(({ urls, theme }) => {
if (!urls || urls.length === 0) { if (!urls || urls.length === 0) {
@ -122,7 +122,7 @@ const Urls = React.memo(({ urls, theme }) => {
return urls.map((url, index) => ( return urls.map((url, index) => (
<Url url={url} key={url.url} index={index} theme={theme} /> <Url url={url} key={url.url} index={index} theme={theme} />
)); ));
}, (oldProps, newProps) => isEqual(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme); }, (oldProps, newProps) => dequal(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme);
UrlImage.propTypes = { UrlImage.propTypes = {
image: PropTypes.string image: PropTypes.string

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import isEqual from 'deep-equal'; import { dequal } from 'dequal';
import Touchable from './Touchable'; import Touchable from './Touchable';
import Markdown from '../markdown'; import Markdown from '../markdown';
@ -57,7 +57,7 @@ const Video = React.memo(({
<Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} /> <Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
</> </>
); );
}, (prevProps, nextProps) => isEqual(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme); }, (prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme);
Video.propTypes = { Video.propTypes = {
file: PropTypes.object, file: PropTypes.object,

View File

@ -37,7 +37,9 @@ export const SYSTEM_MESSAGES = [
'room_changed_privacy', 'room_changed_privacy',
'room_changed_avatar', 'room_changed_avatar',
'message_snippeted', 'message_snippeted',
'thread-created' 'thread-created',
'room_e2e_enabled',
'room_e2e_disabled'
]; ];
export const SYSTEM_MESSAGE_TYPES = { export const SYSTEM_MESSAGE_TYPES = {
@ -100,6 +102,10 @@ export const getInfoMessage = ({
return I18n.t('Room_changed_avatar', { userBy: username }); return I18n.t('Room_changed_avatar', { userBy: username });
} else if (type === 'message_snippeted') { } else if (type === 'message_snippeted') {
return I18n.t('Created_snippet'); return I18n.t('Created_snippet');
} else if (type === 'room_e2e_disabled') {
return I18n.t('This_room_encryption_has_been_disabled_by__username_', { username });
} else if (type === 'room_e2e_enabled') {
return I18n.t('This_room_encryption_has_been_enabled_by__username_', { username });
} }
return ''; return '';
}; };

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import isEqual from 'react-fast-compare'; import { dequal } from 'dequal';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import RoomItem, { ROW_HEIGHT } from '../../../presentation/RoomItem'; import RoomItem, { ROW_HEIGHT } from '../../../presentation/RoomItem';
@ -56,7 +56,7 @@ class QueueListView extends React.Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
const { queued } = this.props; const { queued } = this.props;
if (!isEqual(nextProps.queued, queued)) { if (!dequal(nextProps.queued, queued)) {
return true; return true;
} }

View File

@ -704,5 +704,7 @@ export default {
Direct_message: 'Direct message', Direct_message: 'Direct message',
Message_Ignored: 'Message ignored. Tap to display it.', Message_Ignored: 'Message ignored. Tap to display it.',
Enter_workspace_URL: 'Enter workspace URL', Enter_workspace_URL: 'Enter workspace URL',
Workspace_URL_Example: 'Ex. your-company.rocket.chat' Workspace_URL_Example: 'Ex. your-company.rocket.chat',
This_room_encryption_has_been_enabled_by__username_: 'This room\'s encryption has been enabled by {{username}}',
This_room_encryption_has_been_disabled_by__username_: 'This room\'s encryption has been disabled by {{username}}'
}; };

View File

@ -651,5 +651,7 @@ export default {
Direct_message: 'Mensagem direta', Direct_message: 'Mensagem direta',
Message_Ignored: 'Mensagem ignorada. Toque para mostrar.', Message_Ignored: 'Mensagem ignorada. Toque para mostrar.',
Enter_workspace_URL: 'Digite a URL da sua workspace', Enter_workspace_URL: 'Digite a URL da sua workspace',
Workspace_URL_Example: 'Ex. sua-empresa.rocket.chat' Workspace_URL_Example: 'Ex. sua-empresa.rocket.chat',
This_room_encryption_has_been_enabled_by__username_: 'A criptografia para essa sala foi habilitada por {{username}}',
This_room_encryption_has_been_disabled_by__username_: 'A criptografia para essa sala foi desabilitada por {{username}}'
}; };

View File

@ -678,5 +678,31 @@ export default {
No_threads: 'Тредов нет', No_threads: 'Тредов нет',
No_threads_following: 'Нет тредов, за которыми вы следите', No_threads_following: 'Нет тредов, за которыми вы следите',
No_threads_unread: 'Непрочитанных тредов нет', No_threads_unread: 'Непрочитанных тредов нет',
Messagebox_Send_to_channel: 'Отправить в чат' Messagebox_Send_to_channel: 'Отправить в чат',
Set_as_leader: 'Назначить лидером',
Set_as_moderator: 'Назначить модератором',
Set_as_owner: 'Назначить владельцем',
Remove_as_leader: 'Удалить из лидеров',
Remove_as_moderator: 'Удалить из модераторов',
Remove_as_owner: 'Удалить из владельцев',
Remove_from_room: 'Удалить из чата',
Ignore: 'Игнориновать',
Unignore: 'Прекратить игнорировать',
User_has_been_ignored: 'Пользователь теперь игнорируется',
User_has_been_unignored: 'Пользователь больше не игнорируется',
User_has_been_removed_from_s: 'Пользователь удален из {{s}}',
User__username__is_now_a_leader_of__room_name_: 'Пользователь {{username}} больше не лидер в чате {{room_name}}',
User__username__is_now_a_moderator_of__room_name_: 'Пользователь {{username}} больше не модератор в чате {{room_name}}',
User__username__is_now_a_owner_of__room_name_: 'Пользователь {{username}} больше не владелец в чате {{room_name}}',
User__username__removed_from__room_name__leaders: 'Пользователь {{username}} удален из {{room_name}} лидеров',
User__username__removed_from__room_name__moderators: 'Пользователь {{username}} удален из {{room_name}} модераторов',
User__username__removed_from__room_name__owners: 'Пользователь {{username}} удален из {{room_name}} владельцев',
The_user_will_be_removed_from_s: 'Пользователь будет удален из {{s}}',
Yes_remove_user: 'Да, удалить пользователя!',
Direct_message: 'Личное сообщение',
Message_Ignored: 'Сообщение игнорируется. Тапните по нему, чтобы отобразить его.',
Enter_workspace_URL: 'Введите URL вашего рабочего пространства',
Workspace_URL_Example: 'Например, your-company.rocket.chat',
This_room_encryption_has_been_enabled_by__username_: 'Шифрование для этого чата включено {{username}}',
This_room_encryption_has_been_disabled_by__username_: 'Шифрование для этого чата выключено {{username}}'
}; };

View File

@ -112,16 +112,25 @@ export default class Root extends React.Component {
init = async() => { init = async() => {
UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme); UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme);
const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]);
const parsedDeepLinkingURL = parseDeepLinking(deepLinking);
store.dispatch(appInitLocalSettings()); store.dispatch(appInitLocalSettings());
// Open app from push notification
const notification = await initializePushNotifications();
if (notification) { if (notification) {
onNotification(notification); onNotification(notification);
} else if (parsedDeepLinkingURL) { return;
store.dispatch(deepLinkingOpen(parsedDeepLinkingURL));
} else {
store.dispatch(appInit());
} }
// Open app from deep linking
const deepLinking = await Linking.getInitialURL();
const parsedDeepLinkingURL = parseDeepLinking(deepLinking);
if (parsedDeepLinkingURL) {
store.dispatch(deepLinkingOpen(parsedDeepLinkingURL));
return;
}
// Open app from app icon
store.dispatch(appInit());
} }
getMasterDetail = (width) => { getMasterDetail = (width) => {

View File

@ -229,9 +229,9 @@ class Encryption {
decryptPendingMessages = async(roomId) => { decryptPendingMessages = async(roomId) => {
const db = database.active; const db = database.active;
const messagesCollection = db.collections.get('messages'); const messagesCollection = db.get('messages');
const threadsCollection = db.collections.get('threads'); const threadsCollection = db.get('threads');
const threadMessagesCollection = db.collections.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');
// e2e status is null or 'pending' and message type is 'e2e' // e2e status is null or 'pending' and message type is 'e2e'
const whereClause = [ const whereClause = [
@ -286,7 +286,7 @@ class Encryption {
// after initialize the encryption client // after initialize the encryption client
decryptPendingSubscriptions = async() => { decryptPendingSubscriptions = async() => {
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
try { try {
// Find all rooms that can have a lastMessage encrypted // Find all rooms that can have a lastMessage encrypted
// If we select only encrypted rooms we can miss some room that changed their encrypted status // If we select only encrypted rooms we can miss some room that changed their encrypted status
@ -347,7 +347,7 @@ class Encryption {
const { rid } = subscription; const { rid } = subscription;
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
let subRecord; let subRecord;
try { try {
@ -400,7 +400,7 @@ class Encryption {
encryptMessage = async(message) => { encryptMessage = async(message) => {
const { rid } = message; const { rid } = message;
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
try { try {
// Find the subscription // Find the subscription

View File

@ -49,7 +49,7 @@ export default class EncryptionRoom {
} }
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
try { try {
// Find the subscription // Find the subscription
const subscription = await subCollection.find(this.roomId); const subscription = await subCollection.find(this.roomId);

View File

@ -2,7 +2,7 @@ import reduxStore from '../createStore';
import Navigation from '../Navigation'; import Navigation from '../Navigation';
import { logEvent, events } from '../../utils/log'; import { logEvent, events } from '../../utils/log';
async function jitsiURL({ rid }) { async function jitsiURL({ room }) {
const { settings } = reduxStore.getState(); const { settings } = reduxStore.getState();
const { Jitsi_Enabled } = settings; const { Jitsi_Enabled } = settings;
@ -11,31 +11,37 @@ async function jitsiURL({ rid }) {
} }
const { const {
Jitsi_Domain, Jitsi_URL_Room_Prefix, Jitsi_SSL, Jitsi_Enabled_TokenAuth, uniqueID Jitsi_Domain, Jitsi_URL_Room_Prefix, Jitsi_SSL, Jitsi_Enabled_TokenAuth, uniqueID, Jitsi_URL_Room_Hash
} = settings; } = settings;
const domain = `${ Jitsi_Domain }/`; const domain = `${ Jitsi_Domain }/`;
const prefix = Jitsi_URL_Room_Prefix; const prefix = Jitsi_URL_Room_Prefix;
const uniqueIdentifier = uniqueID || 'undefined';
const protocol = Jitsi_SSL ? 'https://' : 'http://'; const protocol = Jitsi_SSL ? 'https://' : 'http://';
let queryString = ''; let queryString = '';
if (Jitsi_Enabled_TokenAuth) { if (Jitsi_Enabled_TokenAuth) {
try { try {
const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', rid); const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', room?.rid);
queryString = `?jwt=${ accessToken }`; queryString = `?jwt=${ accessToken }`;
} catch { } catch {
logEvent(events.RA_JITSI_F); logEvent(events.RA_JITSI_F);
} }
} }
return `${ protocol }${ domain }${ prefix }${ uniqueIdentifier }${ rid }${ queryString }`; let rname;
if (Jitsi_URL_Room_Hash) {
rname = uniqueID + room?.rid;
} else {
rname = encodeURIComponent(room.t === 'd' ? room?.usernames?.join?.(' x ') : room?.name);
}
return `${ protocol }${ domain }${ prefix }${ rname }${ queryString }`;
} }
async function callJitsi(rid, onlyAudio = false) { async function callJitsi(room, onlyAudio = false) {
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO); logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
const url = await jitsiURL.call(this, { rid }); const url = await jitsiURL.call(this, { room });
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid }); Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid: room?.rid });
} }
export default callJitsi; export default callJitsi;

View File

@ -57,7 +57,7 @@ async function open({ type, rid, name }) {
export default async function canOpenRoom({ rid, path, isCall }) { export default async function canOpenRoom({ rid, path, isCall }) {
try { try {
const db = database.active; const db = database.active;
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.get('subscriptions');
if (isCall && !rid) { if (isCall && !rid) {
// Extract rid from a Jitsi URL // Extract rid from a Jitsi URL
@ -75,7 +75,8 @@ export default async function canOpenRoom({ rid, path, isCall }) {
name: room.name, name: room.name,
fname: room.fname, fname: room.fname,
prid: room.prid, prid: room.prid,
uids: room.uids uids: room.uids,
usernames: room.usernames
}; };
} catch (e) { } catch (e) {
// Do nothing // Do nothing

View File

@ -13,7 +13,7 @@ export async function setEnterpriseModules() {
try { try {
const { server: serverId } = reduxStore.getState().server; const { server: serverId } = reduxStore.getState().server;
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
let server; let server;
try { try {
server = await serversCollection.find(serverId); server = await serversCollection.find(serverId);
@ -39,7 +39,7 @@ export function getEnterpriseModules() {
const enterpriseModules = await this.methodCallWrapper('license:getModules'); const enterpriseModules = await this.methodCallWrapper('license:getModules');
if (enterpriseModules) { if (enterpriseModules) {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
const server = await serversCollection.find(serverId); const server = await serversCollection.find(serverId);
await serversDB.action(async() => { await serversDB.action(async() => {
await server.update((s) => { await server.update((s) => {

View File

@ -1,4 +1,3 @@
import { InteractionManager } from 'react-native';
import lt from 'semver/functions/lt'; import lt from 'semver/functions/lt';
import orderBy from 'lodash/orderBy'; import orderBy from 'lodash/orderBy';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
@ -21,7 +20,7 @@ const updateEmojis = async({ update = [], remove = [], allRecords }) => {
return; return;
} }
const db = database.active; const db = database.active;
const emojisCollection = db.collections.get('custom_emojis'); const emojisCollection = db.get('custom_emojis');
let emojisToCreate = []; let emojisToCreate = [];
let emojisToUpdate = []; let emojisToUpdate = [];
let emojisToDelete = []; let emojisToDelete = [];
@ -63,7 +62,7 @@ const updateEmojis = async({ update = [], remove = [], allRecords }) => {
export async function setCustomEmojis() { export async function setCustomEmojis() {
const db = database.active; const db = database.active;
const emojisCollection = db.collections.get('custom_emojis'); const emojisCollection = db.get('custom_emojis');
const allEmojis = await emojisCollection.query().fetch(); const allEmojis = await emojisCollection.query().fetch();
const parsed = allEmojis.reduce((ret, item) => { const parsed = allEmojis.reduce((ret, item) => {
ret[item.name] = { ret[item.name] = {
@ -86,7 +85,7 @@ export function getCustomEmojis() {
try { try {
const serverVersion = reduxStore.getState().server.version; const serverVersion = reduxStore.getState().server.version;
const db = database.active; const db = database.active;
const emojisCollection = db.collections.get('custom_emojis'); const emojisCollection = db.get('custom_emojis');
const allRecords = await emojisCollection.query().fetch(); const allRecords = await emojisCollection.query().fetch();
const updatedSince = await getUpdatedSince(allRecords); const updatedSince = await getUpdatedSince(allRecords);
@ -95,18 +94,16 @@ export function getCustomEmojis() {
// RC 0.61.0 // RC 0.61.0
const result = await this.sdk.get('emoji-custom'); const result = await this.sdk.get('emoji-custom');
InteractionManager.runAfterInteractions(async() => { let { emojis } = result;
let { emojis } = result; emojis = emojis.filter(emoji => !updatedSince || emoji._updatedAt > updatedSince);
emojis = emojis.filter(emoji => !updatedSince || emoji._updatedAt > updatedSince); const changedEmojis = await updateEmojis({ update: emojis, allRecords });
const changedEmojis = await updateEmojis({ update: emojis, allRecords });
// `setCustomEmojis` is fired on selectServer // `setCustomEmojis` is fired on selectServer
// We run it again only if emojis were changed // We run it again only if emojis were changed
if (changedEmojis) { if (changedEmojis) {
setCustomEmojis(); setCustomEmojis();
} }
return resolve(); return resolve();
});
} else { } else {
const params = {}; const params = {};
if (updatedSince) { if (updatedSince) {
@ -120,17 +117,15 @@ export function getCustomEmojis() {
return resolve(); return resolve();
} }
InteractionManager.runAfterInteractions(async() => { const { emojis } = result;
const { emojis } = result; const { update, remove } = emojis;
const { update, remove } = emojis; const changedEmojis = await updateEmojis({ update, remove, allRecords });
const changedEmojis = await updateEmojis({ update, remove, allRecords });
// `setCustomEmojis` is fired on selectServer // `setCustomEmojis` is fired on selectServer
// We run it again only if emojis were changed // We run it again only if emojis were changed
if (changedEmojis) { if (changedEmojis) {
setCustomEmojis(); setCustomEmojis();
} }
});
} }
} catch (e) { } catch (e) {
log(e); log(e);

View File

@ -1,12 +1,55 @@
import { InteractionManager } from 'react-native';
import lt from 'semver/functions/lt'; import lt from 'semver/functions/lt';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb';
import coerce from 'semver/functions/coerce';
import orderBy from 'lodash/orderBy'; import orderBy from 'lodash/orderBy';
import database from '../database'; import database from '../database';
import log from '../../utils/log'; import log from '../../utils/log';
import reduxStore from '../createStore'; import reduxStore from '../createStore';
import protectedFunction from './helpers/protectedFunction'; import protectedFunction from './helpers/protectedFunction';
import { setPermissions as setPermissionsAction } from '../../actions/permissions';
const PERMISSIONS = [
'add-user-to-any-c-room',
'add-user-to-any-p-room',
'add-user-to-joined-room',
'archive-room',
'auto-translate',
'create-invite-links',
'delete-c',
'delete-message',
'delete-p',
'edit-message',
'edit-room',
'force-delete-message',
'mute-user',
'pin-message',
'post-readonly',
'remove-user',
'set-leader',
'set-moderator',
'set-owner',
'set-react-when-readonly',
'set-readonly',
'toggle-room-e2e-encryption',
'transfer-livechat-guest',
'unarchive-room',
'view-broadcast-member-list',
'view-privileged-setting',
'view-room-administration',
'view-statistics',
'view-user-administration'
];
export async function setPermissions() {
const db = database.active;
const permissionsCollection = db.collections.get('permissions');
const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch();
const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {});
reduxStore.dispatch(setPermissionsAction(parsed));
}
const getUpdatedSince = (allRecords) => { const getUpdatedSince = (allRecords) => {
try { try {
@ -26,7 +69,7 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => {
return; return;
} }
const db = database.active; const db = database.active;
const permissionsCollection = db.collections.get('permissions'); const permissionsCollection = db.get('permissions');
// filter permissions // filter permissions
let permissionsToCreate = []; let permissionsToCreate = [];
@ -65,33 +108,35 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => {
await db.action(async() => { await db.action(async() => {
await db.batch(...batch); await db.batch(...batch);
}); });
return true;
} catch (e) { } catch (e) {
log(e); log(e);
} }
}; };
export default function() { export function getPermissions() {
return new Promise(async(resolve) => { return new Promise(async(resolve) => {
try { try {
const serverVersion = reduxStore.getState().server.version; const serverVersion = reduxStore.getState().server.version;
const db = database.active; const db = database.active;
const permissionsCollection = db.collections.get('permissions'); const permissionsCollection = db.get('permissions');
const allRecords = await permissionsCollection.query().fetch(); const allRecords = await permissionsCollection.query().fetch();
// if server version is lower than 0.73.0, fetches from old api // if server version is lower than 0.73.0, fetches from old api
if (serverVersion && lt(serverVersion, '0.73.0')) { if (serverVersion && lt(coerce(serverVersion), '0.73.0')) {
// RC 0.66.0 // RC 0.66.0
const result = await this.sdk.get('permissions.list'); const result = await this.sdk.get('permissions.list');
if (!result.success) { if (!result.success) {
return resolve(); return resolve();
} }
InteractionManager.runAfterInteractions(async() => { const changePermissions = await updatePermissions({ update: result.permissions, allRecords });
await updatePermissions({ update: result.permissions, allRecords }); if (changePermissions) {
return resolve(); setPermissions();
}); }
return resolve();
} else { } else {
const params = {}; const params = {};
const updatedSince = await getUpdatedSince(allRecords); const updatedSince = getUpdatedSince(allRecords);
if (updatedSince) { if (updatedSince) {
params.updatedSince = updatedSince; params.updatedSince = updatedSince;
} }
@ -102,10 +147,11 @@ export default function() {
return resolve(); return resolve();
} }
InteractionManager.runAfterInteractions(async() => { const changePermissions = await updatePermissions({ update: result.update, remove: result.delete, allRecords });
await updatePermissions({ update: result.update, remove: result.delete, allRecords }); if (changePermissions) {
return resolve(); setPermissions();
}); }
return resolve();
} }
} catch (e) { } catch (e) {
log(e); log(e);

View File

@ -1,4 +1,3 @@
import { InteractionManager } from 'react-native';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import database from '../database'; import database from '../database';
@ -19,43 +18,41 @@ export default function() {
const { roles } = result; const { roles } = result;
if (roles && roles.length) { if (roles && roles.length) {
InteractionManager.runAfterInteractions(async() => { await db.action(async() => {
await db.action(async() => { const rolesCollections = db.get('roles');
const rolesCollections = db.collections.get('roles'); const allRolesRecords = await rolesCollections.query().fetch();
const allRolesRecords = await rolesCollections.query().fetch();
// filter roles // filter roles
let rolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id)); let rolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id));
let rolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id)); let rolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id));
// Create // Create
rolesToCreate = rolesToCreate.map(role => rolesCollections.prepareCreate(protectedFunction((r) => { rolesToCreate = rolesToCreate.map(role => rolesCollections.prepareCreate(protectedFunction((r) => {
r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema); r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema);
Object.assign(r, role); Object.assign(r, role);
}))); })));
// Update // Update
rolesToUpdate = rolesToUpdate.map((role) => { rolesToUpdate = rolesToUpdate.map((role) => {
const newRole = roles.find(r => r._id === role.id); const newRole = roles.find(r => r._id === role.id);
return role.prepareUpdate(protectedFunction((r) => { return role.prepareUpdate(protectedFunction((r) => {
Object.assign(r, newRole); Object.assign(r, newRole);
})); }));
});
const allRecords = [
...rolesToCreate,
...rolesToUpdate
];
try {
await db.batch(...allRecords);
} catch (e) {
log(e);
}
return allRecords.length;
}); });
return resolve();
const allRecords = [
...rolesToCreate,
...rolesToUpdate
];
try {
await db.batch(...allRecords);
} catch (e) {
log(e);
}
return allRecords.length;
}); });
return resolve();
} }
} catch (e) { } catch (e) {
log(e); log(e);

View File

@ -44,7 +44,7 @@ const loginSettings = [
const serverInfoUpdate = async(serverInfo, iconSetting) => { const serverInfoUpdate = async(serverInfo, iconSetting) => {
const serversDB = database.servers; const serversDB = database.servers;
const serverId = reduxStore.getState().server.server; const serverId = reduxStore.getState().server.server;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
const server = await serversCollection.find(serverId); const server = await serversCollection.find(serverId);
let info = serverInfo.reduce((allSettings, setting) => { let info = serverInfo.reduce((allSettings, setting) => {
@ -118,7 +118,7 @@ export async function getLoginSettings({ server }) {
export async function setSettings() { export async function setSettings() {
const db = database.active; const db = database.active;
const settingsCollection = db.collections.get('settings'); const settingsCollection = db.get('settings');
const settingsRecords = await settingsCollection.query().fetch(); const settingsRecords = await settingsCollection.query().fetch();
const parsed = Object.values(settingsRecords).map(item => ({ const parsed = Object.values(settingsRecords).map(item => ({
_id: item.id, _id: item.id,
@ -157,7 +157,7 @@ export default async function() {
} }
await db.action(async() => { await db.action(async() => {
const settingsCollection = db.collections.get('settings'); const settingsCollection = db.get('settings');
const allSettingsRecords = await settingsCollection const allSettingsRecords = await settingsCollection
.query(Q.where('id', Q.oneOf(filteredSettingsIds))) .query(Q.where('id', Q.oneOf(filteredSettingsIds)))
.fetch(); .fetch();

View File

@ -1,4 +1,3 @@
import { InteractionManager } from 'react-native';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import database from '../database'; import database from '../database';
@ -20,47 +19,45 @@ export default function() {
const { commands } = result; const { commands } = result;
if (commands && commands.length) { if (commands && commands.length) {
InteractionManager.runAfterInteractions(async() => { await db.action(async() => {
await db.action(async() => { const slashCommandsCollection = db.get('slash_commands');
const slashCommandsCollection = db.collections.get('slash_commands'); const allSlashCommandsRecords = await slashCommandsCollection.query().fetch();
const allSlashCommandsRecords = await slashCommandsCollection.query().fetch();
// filter slash commands // filter slash commands
let slashCommandsToCreate = commands.filter(i1 => !allSlashCommandsRecords.find(i2 => i1.command === i2.id)); let slashCommandsToCreate = commands.filter(i1 => !allSlashCommandsRecords.find(i2 => i1.command === i2.id));
let slashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => commands.find(i2 => i1.id === i2.command)); let slashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => commands.find(i2 => i1.id === i2.command));
let slashCommandsToDelete = allSlashCommandsRecords let slashCommandsToDelete = allSlashCommandsRecords
.filter(i1 => !slashCommandsToCreate.find(i2 => i2.command === i1.id) && !slashCommandsToUpdate.find(i2 => i2.id === i1.id)); .filter(i1 => !slashCommandsToCreate.find(i2 => i2.command === i1.id) && !slashCommandsToUpdate.find(i2 => i2.id === i1.id));
// Create // Create
slashCommandsToCreate = slashCommandsToCreate.map(command => slashCommandsCollection.prepareCreate(protectedFunction((s) => { slashCommandsToCreate = slashCommandsToCreate.map(command => slashCommandsCollection.prepareCreate(protectedFunction((s) => {
s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema); s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema);
Object.assign(s, command); Object.assign(s, command);
}))); })));
// Update // Update
slashCommandsToUpdate = slashCommandsToUpdate.map((command) => { slashCommandsToUpdate = slashCommandsToUpdate.map((command) => {
const newCommand = commands.find(s => s.command === command.id); const newCommand = commands.find(s => s.command === command.id);
return command.prepareUpdate(protectedFunction((s) => { return command.prepareUpdate(protectedFunction((s) => {
Object.assign(s, newCommand); Object.assign(s, newCommand);
})); }));
});
// Delete
slashCommandsToDelete = slashCommandsToDelete.map(command => command.prepareDestroyPermanently());
const allRecords = [
...slashCommandsToCreate,
...slashCommandsToUpdate,
...slashCommandsToDelete
];
try {
await db.batch(...allRecords);
} catch (e) {
log(e);
}
return allRecords.length;
}); });
// Delete
slashCommandsToDelete = slashCommandsToDelete.map(command => command.prepareDestroyPermanently());
const allRecords = [
...slashCommandsToCreate,
...slashCommandsToUpdate,
...slashCommandsToDelete
];
try {
await db.batch(...allRecords);
} catch (e) {
log(e);
}
return allRecords.length;
}); });
} }
} catch (e) { } catch (e) {

View File

@ -72,7 +72,7 @@ export default async function getUsersPresence() {
ids = []; ids = [];
const db = database.active; const db = database.active;
const userCollection = db.collections.get('users'); const userCollection = db.get('users');
users.forEach(async(user) => { users.forEach(async(user) => {
try { try {
const userRecord = await userCollection.find(user._id); const userRecord = await userCollection.find(user._id);

View File

@ -5,7 +5,7 @@ import database from '../../database';
export default async(subscriptions = [], rooms = []) => { export default async(subscriptions = [], rooms = []) => {
try { try {
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
const roomIds = rooms.filter(r => !subscriptions.find(s => s.rid === r._id)).map(r => r._id); const roomIds = rooms.filter(r => !subscriptions.find(s => s.rid === r._id)).map(r => r._id);
let existingSubs = await subCollection.query(Q.where('rid', Q.oneOf(roomIds))).fetch(); let existingSubs = await subCollection.query(Q.where('rid', Q.oneOf(roomIds))).fetch();

View File

@ -1,11 +1,14 @@
import EJSON from 'ejson'; import EJSON from 'ejson';
import { lt, coerce } from 'semver';
import normalizeMessage from './normalizeMessage'; import normalizeMessage from './normalizeMessage';
import findSubscriptionsRooms from './findSubscriptionsRooms'; import findSubscriptionsRooms from './findSubscriptionsRooms';
import { Encryption } from '../../encryption'; import { Encryption } from '../../encryption';
import reduxStore from '../../createStore';
// TODO: delete and update // TODO: delete and update
export const merge = (subscription, room) => { export const merge = (subscription, room) => {
const serverVersion = reduxStore.getState().server.version;
subscription = EJSON.fromJSONValue(subscription); subscription = EJSON.fromJSONValue(subscription);
room = EJSON.fromJSONValue(room); room = EJSON.fromJSONValue(room);
@ -25,9 +28,15 @@ export const merge = (subscription, room) => {
subscription.usernames = room.usernames; subscription.usernames = room.usernames;
subscription.uids = room.uids; subscription.uids = room.uids;
} }
// https://github.com/RocketChat/Rocket.Chat/blob/develop/app/ui-sidenav/client/roomList.js#L180 if (serverVersion && lt(coerce(serverVersion), '3.7.0')) {
const lastRoomUpdate = room.lm || subscription.ts || subscription._updatedAt; const updatedAt = room?._updatedAt ? new Date(room._updatedAt) : null;
subscription.roomUpdatedAt = subscription.lr ? Math.max(new Date(subscription.lr), new Date(lastRoomUpdate)) : lastRoomUpdate; const lastMessageTs = subscription?.lastMessage?.ts ? new Date(subscription.lastMessage.ts) : null;
subscription.roomUpdatedAt = Math.max(updatedAt, lastMessageTs);
} else {
// https://github.com/RocketChat/Rocket.Chat/blob/develop/app/ui-sidenav/client/roomList.js#L180
const lastRoomUpdate = room.lm || subscription.ts || subscription._updatedAt;
subscription.roomUpdatedAt = subscription.lr ? Math.max(new Date(subscription.lr), new Date(lastRoomUpdate)) : lastRoomUpdate;
}
subscription.ro = room.ro; subscription.ro = room.ro;
subscription.broadcast = room.broadcast; subscription.broadcast = room.broadcast;
subscription.encrypted = room.encrypted; subscription.encrypted = room.encrypted;

View File

@ -5,7 +5,7 @@ import updateMessages from './updateMessages';
const getLastUpdate = async(rid) => { const getLastUpdate = async(rid) => {
try { try {
const db = database.active; const db = database.active;
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.get('subscriptions');
const sub = await subsCollection.find(rid); const sub = await subsCollection.find(rid);
return sub.lastOpen.toISOString(); return sub.lastOpen.toISOString();
} catch (e) { } catch (e) {

View File

@ -1,4 +1,3 @@
import { InteractionManager } from 'react-native';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
@ -30,44 +29,42 @@ export default function loadThreadMessages({ tmid, rid, offset = 0 }) {
let data = await load.call(this, { tmid, offset }); let data = await load.call(this, { tmid, offset });
if (data && data.length) { if (data && data.length) {
InteractionManager.runAfterInteractions(async() => { try {
try { data = data.map(m => buildMessage(m));
data = data.map(m => buildMessage(m)); data = await Encryption.decryptMessages(data);
data = await Encryption.decryptMessages(data); const db = database.active;
const db = database.active; const threadMessagesCollection = db.get('thread_messages');
const threadMessagesCollection = db.collections.get('thread_messages'); const allThreadMessagesRecords = await threadMessagesCollection.query(Q.where('rid', tmid)).fetch();
const allThreadMessagesRecords = await threadMessagesCollection.query(Q.where('rid', tmid)).fetch(); let threadMessagesToCreate = data.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id));
let threadMessagesToCreate = data.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id)); let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => data.find(i2 => i1.id === i2._id));
let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => data.find(i2 => i1.id === i2._id));
threadMessagesToCreate = threadMessagesToCreate.map(threadMessage => threadMessagesCollection.prepareCreate(protectedFunction((tm) => { threadMessagesToCreate = threadMessagesToCreate.map(threadMessage => threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema); tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema);
Object.assign(tm, threadMessage); Object.assign(tm, threadMessage);
tm.subscription.id = rid; tm.subscription.id = rid;
tm.rid = threadMessage.tmid;
delete threadMessage.tmid;
})));
threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => {
const newThreadMessage = data.find(t => t._id === threadMessage.id);
return threadMessage.prepareUpdate(protectedFunction((tm) => {
Object.assign(tm, newThreadMessage);
tm.rid = threadMessage.tmid; tm.rid = threadMessage.tmid;
delete threadMessage.tmid; delete threadMessage.tmid;
}))); }));
});
threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => { await db.action(async() => {
const newThreadMessage = data.find(t => t._id === threadMessage.id); await db.batch(
return threadMessage.prepareUpdate(protectedFunction((tm) => { ...threadMessagesToCreate,
Object.assign(tm, newThreadMessage); ...threadMessagesToUpdate
tm.rid = threadMessage.tmid; );
delete threadMessage.tmid; });
})); } catch (e) {
}); log(e);
}
await db.action(async() => { return resolve(data);
await db.batch(
...threadMessagesToCreate,
...threadMessagesToUpdate
);
});
} catch (e) {
log(e);
}
return resolve(data);
});
} else { } else {
return resolve([]); return resolve([]);
} }

View File

@ -42,12 +42,12 @@ async function removeServerData({ server }) {
const serversDB = database.servers; const serversDB = database.servers;
const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`);
const usersCollection = serversDB.collections.get('users'); const usersCollection = serversDB.get('users');
if (userId) { if (userId) {
const userRecord = await usersCollection.find(userId); const userRecord = await usersCollection.find(userId);
batch.push(userRecord.prepareDestroyPermanently()); batch.push(userRecord.prepareDestroyPermanently());
} }
const serverCollection = serversDB.collections.get('servers'); const serverCollection = serversDB.get('servers');
const serverRecord = await serverCollection.find(server); const serverRecord = await serverCollection.find(server);
batch.push(serverRecord.prepareDestroyPermanently()); batch.push(serverRecord.prepareDestroyPermanently());

View File

@ -4,7 +4,7 @@ import log from '../../utils/log';
export default async function readMessages(rid, ls, updateLastOpen = false) { export default async function readMessages(rid, ls, updateLastOpen = false) {
try { try {
const db = database.active; const db = database.active;
const subscription = await db.collections.get('subscriptions').find(rid); const subscription = await db.get('subscriptions').find(rid);
// RC 0.61.0 // RC 0.61.0
await this.sdk.post('subscriptions.read', { rid }); await this.sdk.post('subscriptions.read', { rid });

View File

@ -40,7 +40,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
fileInfo.rid = rid; fileInfo.rid = rid;
const db = database.active; const db = database.active;
const uploadsCollection = db.collections.get('uploads'); const uploadsCollection = db.get('uploads');
let uploadRecord; let uploadRecord;
try { try {
uploadRecord = await uploadsCollection.find(fileInfo.path); uploadRecord = await uploadsCollection.find(fileInfo.path);

View File

@ -9,8 +9,8 @@ import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../encryption/constants';
const changeMessageStatus = async(id, tmid, status, message) => { const changeMessageStatus = async(id, tmid, status, message) => {
const db = database.active; const db = database.active;
const msgCollection = db.collections.get('messages'); const msgCollection = db.get('messages');
const threadMessagesCollection = db.collections.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');
const successBatch = []; const successBatch = [];
const messageRecord = await msgCollection.find(id); const messageRecord = await msgCollection.find(id);
successBatch.push( successBatch.push(
@ -89,10 +89,10 @@ export async function resendMessage(message, tmid) {
export default async function(rid, msg, tmid, user, tshow) { export default async function(rid, msg, tmid, user, tshow) {
try { try {
const db = database.active; const db = database.active;
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.get('subscriptions');
const msgCollection = db.collections.get('messages'); const msgCollection = db.get('messages');
const threadCollection = db.collections.get('threads'); const threadCollection = db.get('threads');
const threadMessagesCollection = db.collections.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');
const messageId = random(17); const messageId = random(17);
const batch = []; const batch = [];
@ -151,7 +151,8 @@ export default async function(rid, msg, tmid, user, tshow) {
tm.status = messagesStatus.TEMP; tm.status = messagesStatus.TEMP;
tm.u = { tm.u = {
_id: user.id || '1', _id: user.id || '1',
username: user.username username: user.username,
name: user.name
}; };
tm.t = message.t; tm.t = message.t;
if (message.t === E2E_MESSAGE_TYPE) { if (message.t === E2E_MESSAGE_TYPE) {
@ -175,7 +176,8 @@ export default async function(rid, msg, tmid, user, tshow) {
m.status = messagesStatus.TEMP; m.status = messagesStatus.TEMP;
m.u = { m.u = {
_id: user.id || '1', _id: user.id || '1',
username: user.username username: user.username,
name: user.name
}; };
if (tmid && tMessageRecord) { if (tmid && tMessageRecord) {
m.tmid = tmid; m.tmid = tmid;

View File

@ -90,6 +90,10 @@ export default class RoomSubscription {
if (ev === 'typing') { if (ev === 'typing') {
const { user } = reduxStore.getState().login; const { user } = reduxStore.getState().login;
const { UI_Use_Real_Name } = reduxStore.getState().settings; const { UI_Use_Real_Name } = reduxStore.getState().settings;
const { rooms } = reduxStore.getState().room;
if (rooms[0] !== _rid) {
return;
}
const [name, typing] = ddpMessage.fields.args; const [name, typing] = ddpMessage.fields.args;
const key = UI_Use_Real_Name ? 'name' : 'username'; const key = UI_Use_Real_Name ? 'name' : 'username';
if (name !== user[key]) { if (name !== user[key]) {
@ -105,9 +109,9 @@ export default class RoomSubscription {
try { try {
const { _id } = ddpMessage.fields.args[0]; const { _id } = ddpMessage.fields.args[0];
const db = database.active; const db = database.active;
const msgCollection = db.collections.get('messages'); const msgCollection = db.get('messages');
const threadsCollection = db.collections.get('threads'); const threadsCollection = db.get('threads');
const threadMessagesCollection = db.collections.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');
let deleteMessage; let deleteMessage;
let deleteThread; let deleteThread;
let deleteThreadMessage; let deleteThreadMessage;
@ -159,9 +163,9 @@ export default class RoomSubscription {
} }
const db = database.active; const db = database.active;
const msgCollection = db.collections.get('messages'); const msgCollection = db.get('messages');
const threadsCollection = db.collections.get('threads'); const threadsCollection = db.get('threads');
const threadMessagesCollection = db.collections.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');
// Decrypt the message if necessary // Decrypt the message if necessary
message = await Encryption.decryptMessage(message); message = await Encryption.decryptMessage(message);

View File

@ -32,8 +32,8 @@ const WINDOW_TIME = 500;
const createOrUpdateSubscription = async(subscription, room) => { const createOrUpdateSubscription = async(subscription, room) => {
try { try {
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
const roomsCollection = db.collections.get('rooms'); const roomsCollection = db.get('rooms');
if (!subscription) { if (!subscription) {
try { try {
@ -185,7 +185,7 @@ const createOrUpdateSubscription = async(subscription, room) => {
const { rooms } = store.getState().room; const { rooms } = store.getState().room;
if (tmp.lastMessage && !rooms.includes(tmp.rid)) { if (tmp.lastMessage && !rooms.includes(tmp.rid)) {
const lastMessage = buildMessage(tmp.lastMessage); const lastMessage = buildMessage(tmp.lastMessage);
const messagesCollection = db.collections.get('messages'); const messagesCollection = db.get('messages');
let messageRecord; let messageRecord;
try { try {
messageRecord = await messagesCollection.find(lastMessage._id); messageRecord = await messagesCollection.find(lastMessage._id);
@ -281,7 +281,7 @@ export default function subscribeRooms() {
if (/subscriptions/.test(ev)) { if (/subscriptions/.test(ev)) {
if (type === 'removed') { if (type === 'removed') {
try { try {
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
const sub = await subCollection.find(data.rid); const sub = await subCollection.find(data.rid);
const messages = await sub.messages.fetch(); const messages = await sub.messages.fetch();
const threads = await sub.threads.fetch(); const threads = await sub.threads.fetch();
@ -335,7 +335,7 @@ export default function subscribeRooms() {
} }
}; };
try { try {
const msgCollection = db.collections.get('messages'); const msgCollection = db.get('messages');
await db.action(async() => { await db.action(async() => {
await msgCollection.create(protectedFunction((m) => { await msgCollection.create(protectedFunction((m) => {
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
@ -407,7 +407,7 @@ export default function subscribeRooms() {
}; };
connectedListener = this.sdk.onStreamData('connected', handleConnection); connectedListener = this.sdk.onStreamData('connected', handleConnection);
disconnectedListener = this.sdk.onStreamData('close', handleConnection); // disconnectedListener = this.sdk.onStreamData('close', handleConnection);
streamListener = this.sdk.onStreamData('stream-notify-user', handleStreamMessageReceived); streamListener = this.sdk.onStreamData('stream-notify-user', handleStreamMessageReceived);
try { try {

View File

@ -16,7 +16,7 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
return db.action(async() => { return db.action(async() => {
// Decrypt these messages // Decrypt these messages
update = await Encryption.decryptMessages(update); update = await Encryption.decryptMessages(update);
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
let sub; let sub;
try { try {
sub = await subCollection.find(rid); sub = await subCollection.find(rid);
@ -26,9 +26,9 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
} }
const messagesIds = [...update.map(m => m._id), ...remove.map(m => m._id)]; const messagesIds = [...update.map(m => m._id), ...remove.map(m => m._id)];
const msgCollection = db.collections.get('messages'); const msgCollection = db.get('messages');
const threadCollection = db.collections.get('threads'); const threadCollection = db.get('threads');
const threadMessagesCollection = db.collections.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');
const allMessagesRecords = await msgCollection const allMessagesRecords = await msgCollection
.query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds))) .query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds)))
.fetch(); .fetch();

View File

@ -32,7 +32,7 @@ import readMessages from './methods/readMessages';
import getSettings, { getLoginSettings, setSettings } from './methods/getSettings'; import getSettings, { getLoginSettings, setSettings } from './methods/getSettings';
import getRooms from './methods/getRooms'; import getRooms from './methods/getRooms';
import getPermissions from './methods/getPermissions'; import { setPermissions, getPermissions } from './methods/getPermissions';
import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis'; import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
import { import {
getEnterpriseModules, setEnterpriseModules, hasLicense, isOmnichannelModuleAvailable getEnterpriseModules, setEnterpriseModules, hasLicense, isOmnichannelModuleAvailable
@ -70,7 +70,6 @@ const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY';
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY'; export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY'; export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY'; export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
const returnAnArray = obj => obj || [];
const MIN_ROCKETCHAT_VERSION = '0.70.0'; const MIN_ROCKETCHAT_VERSION = '0.70.0';
const STATUSES = ['offline', 'online', 'away', 'busy']; const STATUSES = ['offline', 'online', 'away', 'busy'];
@ -178,9 +177,16 @@ const RocketChat = {
} }
this.controller = new AbortController(); this.controller = new AbortController();
}, },
checkAndReopen() {
return this?.sdk?.checkAndReopen();
},
connect({ server, user, logoutOnError = false }) { connect({ server, user, logoutOnError = false }) {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!this.sdk || this.sdk.client.host !== server) { if (this?.sdk?.client?.host === server) {
return resolve();
} else {
this.sdk?.disconnect?.();
this.sdk = null;
database.setActiveDB(server); database.setActiveDB(server);
} }
reduxStore.dispatch(connectRequest()); reduxStore.dispatch(connectRequest());
@ -209,11 +215,6 @@ const RocketChat = {
EventEmitter.emit('INQUIRY_UNSUBSCRIBE'); EventEmitter.emit('INQUIRY_UNSUBSCRIBE');
if (this.sdk) {
this.sdk.disconnect();
this.sdk = null;
}
if (this.code) { if (this.code) {
this.code = null; this.code = null;
} }
@ -241,6 +242,10 @@ const RocketChat = {
sdkConnect(); sdkConnect();
this.connectedListener = this.sdk.onStreamData('connecting', () => {
reduxStore.dispatch(connectRequest());
});
this.connectedListener = this.sdk.onStreamData('connected', () => { this.connectedListener = this.sdk.onStreamData('connected', () => {
reduxStore.dispatch(connectSuccess()); reduxStore.dispatch(connectSuccess());
}); });
@ -276,7 +281,7 @@ const RocketChat = {
} else if (/updateAvatar/.test(eventName)) { } else if (/updateAvatar/.test(eventName)) {
const { username, etag } = ddpMessage.fields.args[0]; const { username, etag } = ddpMessage.fields.args[0];
const db = database.active; const db = database.active;
const userCollection = db.collections.get('users'); const userCollection = db.get('users');
try { try {
const [userRecord] = await userCollection.query(Q.where('username', Q.eq(username))).fetch(); const [userRecord] = await userCollection.query(Q.where('username', Q.eq(username))).fetch();
await db.action(async() => { await db.action(async() => {
@ -290,7 +295,7 @@ const RocketChat = {
} else if (/Users:NameChanged/.test(eventName)) { } else if (/Users:NameChanged/.test(eventName)) {
const userNameChanged = ddpMessage.fields.args[0]; const userNameChanged = ddpMessage.fields.args[0];
const db = database.active; const db = database.active;
const userCollection = db.collections.get('users'); const userCollection = db.get('users');
try { try {
const userRecord = await userCollection.find(userNameChanged._id); const userRecord = await userCollection.find(userNameChanged._id);
await db.action(async() => { await db.action(async() => {
@ -334,7 +339,7 @@ const RocketChat = {
// set Server // set Server
const currentServer = { server }; const currentServer = { server };
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
try { try {
const serverRecord = await serversCollection.find(server); const serverRecord = await serversCollection.find(server);
currentServer.version = serverRecord.version; currentServer.version = serverRecord.version;
@ -349,7 +354,7 @@ const RocketChat = {
// set Settings // set Settings
const settings = ['Accounts_AvatarBlockUnauthenticatedAccess']; const settings = ['Accounts_AvatarBlockUnauthenticatedAccess'];
const db = database.active; const db = database.active;
const settingsCollection = db.collections.get('settings'); const settingsCollection = db.get('settings');
const settingsRecords = await settingsCollection.query(Q.where('id', Q.oneOf(settings))).fetch(); const settingsRecords = await settingsCollection.query(Q.where('id', Q.oneOf(settings))).fetch();
const parsed = Object.values(settingsRecords).map(item => ({ const parsed = Object.values(settingsRecords).map(item => ({
_id: item.id, _id: item.id,
@ -363,7 +368,7 @@ const RocketChat = {
// set User info // set User info
const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`);
const userCollections = serversDB.collections.get('users'); const userCollections = serversDB.get('users');
let user = null; let user = null;
if (userId) { if (userId) {
const userRecord = await userCollections.find(userId); const userRecord = await userCollections.find(userId);
@ -545,7 +550,7 @@ const RocketChat = {
try { try {
const serversDB = database.servers; const serversDB = database.servers;
await serversDB.action(async() => { await serversDB.action(async() => {
const serverCollection = serversDB.collections.get('servers'); const serverCollection = serversDB.get('servers');
const serverRecord = await serverCollection.find(server); const serverRecord = await serverCollection.find(server);
await serverRecord.update((s) => { await serverRecord.update((s) => {
s.roomsUpdatedAt = null; s.roomsUpdatedAt = null;
@ -605,7 +610,7 @@ const RocketChat = {
} }
const db = database.active; const db = database.active;
const likeString = sanitizeLikeString(searchText); const likeString = sanitizeLikeString(searchText);
let data = await db.collections.get('subscriptions').query( let data = await db.get('subscriptions').query(
Q.or( Q.or(
Q.where('name', Q.like(`%${ likeString }%`)), Q.where('name', Q.like(`%${ likeString }%`)),
Q.where('fname', Q.like(`%${ likeString }%`)) Q.where('fname', Q.like(`%${ likeString }%`))
@ -621,19 +626,15 @@ const RocketChat = {
data = data.slice(0, 7); data = data.slice(0, 7);
data = data.map((sub) => { data = data.map(sub => ({
if (sub.t !== 'd') { rid: sub.rid,
return { name: sub.name,
rid: sub.rid, fname: sub.fname,
name: sub.name, avatarETag: sub.avatarETag,
fname: sub.fname, t: sub.t,
avatarETag: sub.avatarETag, encrypted: sub.encrypted,
t: sub.t, lastMessage: sub.lastMessage
encrypted: sub.encrypted }));
};
}
return sub;
});
return data; return data;
}, },
@ -740,6 +741,7 @@ const RocketChat = {
getLoginSettings, getLoginSettings,
setSettings, setSettings,
getPermissions, getPermissions,
setPermissions,
getCustomEmojis, getCustomEmojis,
setCustomEmojis, setCustomEmojis,
getEnterpriseModules, getEnterpriseModules,
@ -796,7 +798,7 @@ const RocketChat = {
async getRoom(rid) { async getRoom(rid) {
try { try {
const db = database.active; const db = database.active;
const room = await db.collections.get('subscriptions').find(rid); const room = await db.get('subscriptions').find(rid);
return Promise.resolve(room); return Promise.resolve(room);
} catch (error) { } catch (error) {
return Promise.reject(new Error('Room not found')); return Promise.reject(new Error('Room not found'));
@ -1172,10 +1174,13 @@ const RocketChat = {
// RC 0.65.0 // RC 0.65.0
return this.sdk.get(`${ this.roomTypeToApiType(type) }.roles`, { roomId }); return this.sdk.get(`${ this.roomTypeToApiType(type) }.roles`, { roomId });
}, },
/**
* Permissions: array of permissions' roles from redux. Example: [['owner', 'admin'], ['leader']]
* Returns an array of boolean for each permission from permissions arg
*/
async hasPermission(permissions, rid) { async hasPermission(permissions, rid) {
const db = database.active; const db = database.active;
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.get('subscriptions');
const permissionsCollection = db.collections.get('permissions');
let roomRoles = []; let roomRoles = [];
try { try {
// get the room from database // get the room from database
@ -1184,31 +1189,16 @@ const RocketChat = {
roomRoles = room.roles || []; roomRoles = room.roles || [];
} catch (error) { } catch (error) {
console.log('hasPermission -> Room not found'); console.log('hasPermission -> Room not found');
return permissions.reduce((result, permission) => { return permissions.map(() => false);
result[permission] = false;
return result;
}, {});
} }
// get permissions from database
try { try {
const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch();
const shareUser = reduxStore.getState().share.user; const shareUser = reduxStore.getState().share.user;
const loginUser = reduxStore.getState().login.user; const loginUser = reduxStore.getState().login.user;
// get user roles on the server from redux // get user roles on the server from redux
const userRoles = (shareUser?.roles || loginUser?.roles) || []; const userRoles = (shareUser?.roles || loginUser?.roles) || [];
// merge both roles
const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; const mergedRoles = [...new Set([...roomRoles, ...userRoles])];
return permissions.map(permission => permission?.some(r => mergedRoles.includes(r) ?? false));
// return permissions in object format
// e.g. { 'edit-room': true, 'set-readonly': false }
return permissions.reduce((result, permission) => {
result[permission] = false;
const permissionFound = permissionsFiltered.find(p => p.id === permission);
if (permissionFound) {
result[permission] = returnAnArray(permissionFound.roles).some(r => mergedRoles.includes(r));
}
return result;
}, {});
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -1438,17 +1428,15 @@ const RocketChat = {
query, count, offset, sort query, count, offset, sort
}); });
}, },
async canAutoTranslate() { canAutoTranslate() {
const db = database.active;
try { try {
const AutoTranslate_Enabled = reduxStore.getState().settings && reduxStore.getState().settings.AutoTranslate_Enabled; const { AutoTranslate_Enabled } = reduxStore.getState().settings;
if (!AutoTranslate_Enabled) { if (!AutoTranslate_Enabled) {
return false; return false;
} }
const permissionsCollection = db.collections.get('permissions'); const autoTranslatePermission = reduxStore.getState().permissions['auto-translate'];
const autoTranslatePermission = await permissionsCollection.find('auto-translate'); const userRoles = (reduxStore.getState().login?.user?.roles) ?? [];
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || []; return autoTranslatePermission?.some(role => userRoles.includes(role));
return autoTranslatePermission.roles.some(role => userRoles.includes(role));
} catch (e) { } catch (e) {
log(e); log(e);
return false; return false;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual'; import { dequal } from 'dequal';
import I18n from '../../i18n'; import I18n from '../../i18n';
import styles from './styles'; import styles from './styles';
@ -45,7 +45,7 @@ const formatMsg = ({
return `${ prefix }${ lastMessage.msg }`; return `${ prefix }${ lastMessage.msg }`;
}; };
const arePropsEqual = (oldProps, newProps) => isEqual(oldProps, newProps); const arePropsEqual = (oldProps, newProps) => dequal(oldProps, newProps);
const LastMessage = React.memo(({ const LastMessage = React.memo(({
lastMessage, type, showLastMessage, username, alert, useRealName, theme lastMessage, type, showLastMessage, username, alert, useRealName, theme

View File

@ -18,6 +18,7 @@ import inviteLinks from './inviteLinks';
import createDiscussion from './createDiscussion'; import createDiscussion from './createDiscussion';
import enterpriseModules from './enterpriseModules'; import enterpriseModules from './enterpriseModules';
import encryption from './encryption'; import encryption from './encryption';
import permissions from './permissions';
import inquiry from '../ee/omnichannel/reducers/inquiry'; import inquiry from '../ee/omnichannel/reducers/inquiry';
@ -41,5 +42,6 @@ export default combineReducers({
createDiscussion, createDiscussion,
inquiry, inquiry,
enterpriseModules, enterpriseModules,
encryption encryption,
permissions
}); });

View File

@ -0,0 +1,14 @@
import { PERMISSIONS } from '../actions/actionsTypes';
const initialState = {
permissions: {}
};
export default function permissions(state = initialState, action) {
switch (action.type) {
case PERMISSIONS.SET:
return action.permissions;
default:
return state;
}
}

View File

@ -42,7 +42,7 @@ const handleRequest = function* handleRequest({ data }) {
broadcast, broadcast,
encrypted encrypted
} = data; } = data;
logEvent(events.CREATE_CHANNEL_CREATE, { logEvent(events.CR_CREATE, {
type: type ? 'private' : 'public', type: type ? 'private' : 'public',
readOnly, readOnly,
broadcast, broadcast,
@ -53,7 +53,7 @@ const handleRequest = function* handleRequest({ data }) {
try { try {
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
yield db.action(async() => { yield db.action(async() => {
await subCollection.create((s) => { await subCollection.create((s) => {
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema);
@ -66,7 +66,7 @@ const handleRequest = function* handleRequest({ data }) {
yield put(createChannelSuccess(sub)); yield put(createChannelSuccess(sub));
} catch (err) { } catch (err) {
logEvent(events[data.group ? 'SELECTED_USERS_CREATE_GROUP_F' : 'CREATE_CHANNEL_CREATE_F']); logEvent(events[data.group ? 'SELECTED_USERS_CREATE_GROUP_F' : 'CR_CREATE_F']);
yield put(createChannelFailure(err)); yield put(createChannelFailure(err));
} }
}; };

View File

@ -14,7 +14,7 @@ const create = function* create(data) {
}; };
const handleRequest = function* handleRequest({ data }) { const handleRequest = function* handleRequest({ data }) {
logEvent(events.CREATE_DISCUSSION_CREATE); logEvent(events.CD_CREATE);
try { try {
const auth = yield select(state => state.login.isAuthenticated); const auth = yield select(state => state.login.isAuthenticated);
if (!auth) { if (!auth) {
@ -27,7 +27,7 @@ const handleRequest = function* handleRequest({ data }) {
try { try {
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
yield db.action(async() => { yield db.action(async() => {
await subCollection.create((s) => { await subCollection.create((s) => {
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema);
@ -39,11 +39,11 @@ const handleRequest = function* handleRequest({ data }) {
} }
yield put(createDiscussionSuccess(sub)); yield put(createDiscussionSuccess(sub));
} else { } else {
logEvent(events.CREATE_DISCUSSION_CREATE_F); logEvent(events.CD_CREATE_F);
yield put(createDiscussionFailure(result)); yield put(createDiscussionFailure(result));
} }
} catch (err) { } catch (err) {
logEvent(events.CREATE_DISCUSSION_CREATE_F); logEvent(events.CD_CREATE_F);
yield put(createDiscussionFailure(err)); yield put(createDiscussionFailure(err));
} }
}; };

View File

@ -31,6 +31,14 @@ const handleInviteLink = function* handleInviteLink({ params, requireLogin = fal
} }
}; };
const popToRoot = function popToRoot({ isMasterDetail }) {
if (isMasterDetail) {
Navigation.navigate('DrawerNavigator');
} else {
Navigation.navigate('RoomsListView');
}
};
const navigate = function* navigate({ params }) { const navigate = function* navigate({ params }) {
yield put(appStart({ root: ROOT_INSIDE })); yield put(appStart({ root: ROOT_INSIDE }));
if (params.path) { if (params.path) {
@ -38,22 +46,31 @@ const navigate = function* navigate({ params }) {
if (type !== 'invite') { if (type !== 'invite') {
const room = yield RocketChat.canOpenRoom(params); const room = yield RocketChat.canOpenRoom(params);
if (room) { if (room) {
const isMasterDetail = yield select(state => state.app.isMasterDetail);
if (isMasterDetail) {
Navigation.navigate('DrawerNavigator');
} else {
Navigation.navigate('RoomsListView');
}
const item = { const item = {
name, name,
t: roomTypes[type], t: roomTypes[type],
roomUserId: RocketChat.getUidDirectMessage(room), roomUserId: RocketChat.getUidDirectMessage(room),
...room ...room
}; };
yield goRoom({ item, isMasterDetail });
const isMasterDetail = yield select(state => state.app.isMasterDetail);
const focusedRooms = yield select(state => state.room.rooms);
if (focusedRooms.includes(room.rid)) {
// if there's one room on the list or last room is the one
if (focusedRooms.length === 1 || focusedRooms[0] === room.rid) {
yield goRoom({ item, isMasterDetail });
} else {
popToRoot({ isMasterDetail });
yield goRoom({ item, isMasterDetail });
}
} else {
popToRoot({ isMasterDetail });
yield goRoom({ item, isMasterDetail });
}
if (params.isCall) { if (params.isCall) {
RocketChat.callJitsi(item.rid); RocketChat.callJitsi(item);
} }
} }
} else { } else {
@ -72,7 +89,7 @@ const fallbackNavigation = function* fallbackNavigation() {
const handleOpen = function* handleOpen({ params }) { const handleOpen = function* handleOpen({ params }) {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
let { host } = params; let { host } = params;
if (params.isCall && !host) { if (params.isCall && !host) {
@ -121,10 +138,10 @@ const handleOpen = function* handleOpen({ params }) {
} else { } else {
// search if deep link's server already exists // search if deep link's server already exists
try { try {
const servers = yield serversCollection.find(host); const hostServerRecord = yield serversCollection.find(host);
if (servers && user) { if (hostServerRecord && user) {
yield localAuthenticate(host); yield localAuthenticate(host);
yield put(selectServerRequest(host)); yield put(selectServerRequest(host, hostServerRecord.version, true, true));
yield take(types.LOGIN.SUCCESS); yield take(types.LOGIN.SUCCESS);
yield navigate({ params }); yield navigate({ params });
return; return;

View File

@ -30,7 +30,7 @@ const handleEncryptionInit = function* handleEncryptionInit() {
// Fetch server info to check E2E enable // Fetch server info to check E2E enable
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
let serverInfo; let serverInfo;
try { try {
serverInfo = yield serversCollection.find(server); serverInfo = yield serversCollection.find(server);

View File

@ -42,7 +42,7 @@ const restore = function* restore() {
// yield put(appStart({ root: ROOT_OUTSIDE })); // yield put(appStart({ root: ROOT_OUTSIDE }));
} else { } else {
const serversDB = database.servers; const serversDB = database.servers;
const serverCollections = serversDB.collections.get('servers'); const serverCollections = serversDB.get('servers');
let serverObj; let serverObj;
try { try {

View File

@ -60,7 +60,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
// Saves username on server history // Saves username on server history
const serversDB = database.servers; const serversDB = database.servers;
const serversHistoryCollection = serversDB.collections.get('servers_history'); const serversHistoryCollection = serversDB.get('servers_history');
yield serversDB.action(async() => { yield serversDB.action(async() => {
try { try {
const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch(); const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch();
@ -148,7 +148,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
moment.locale(toMomentLocale(user.language)); moment.locale(toMomentLocale(user.language));
const serversDB = database.servers; const serversDB = database.servers;
const usersCollection = serversDB.collections.get('users'); const usersCollection = serversDB.get('users');
const u = { const u = {
token: user.token, token: user.token,
username: user.username, username: user.username,
@ -224,10 +224,9 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
// EventEmitter.emit('NewServer', { server }); // EventEmitter.emit('NewServer', { server });
yield put(serverRequest(appConfig.server)); yield put(serverRequest(appConfig.server));
} else { } else {
yield put(serverRequest(appConfig.server));
// const serversDB = database.servers; // const serversDB = database.servers;
// // all servers // // all servers
// const serversCollection = serversDB.collections.get('servers'); // const serversCollection = serversDB.get('servers');
// const servers = yield serversCollection.query().fetch(); // const servers = yield serversCollection.query().fetch();
// // see if there're other logged in servers and selects first one // // see if there're other logged in servers and selects first one
@ -241,8 +240,8 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
// } // }
// } // }
// } // }
// // if there's no servers, go outside // if there's no servers, go outside
// yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: ROOT_OUTSIDE }));
} }
} catch (e) { } catch (e) {
yield put(appStart({ root: ROOT_OUTSIDE })); yield put(appStart({ root: ROOT_OUTSIDE }));

View File

@ -12,7 +12,7 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
try { try {
const db = database.active; const db = database.active;
const { username } = message.u; const { username } = message.u;
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.get('subscriptions');
const subscriptions = yield subsCollection.query(Q.where('name', username)).fetch(); const subscriptions = yield subsCollection.query(Q.where('name', username)).fetch();
const isMasterDetail = yield select(state => state.app.isMasterDetail); const isMasterDetail = yield select(state => state.app.isMasterDetail);

View File

@ -15,7 +15,7 @@ import protectedFunction from '../lib/methods/helpers/protectedFunction';
const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) { const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
try { try {
const serverRecord = yield serversCollection.find(server); const serverRecord = yield serversCollection.find(server);
@ -39,7 +39,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
if (params.allData) { if (params.allData) {
yield put(roomsRefresh()); yield put(roomsRefresh());
} else { } else {
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
try { try {
const serverRecord = yield serversCollection.find(server); const serverRecord = yield serversCollection.find(server);
({ roomsUpdatedAt } = serverRecord); ({ roomsUpdatedAt } = serverRecord);
@ -51,8 +51,8 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
const { subscriptions } = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult); const { subscriptions } = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
const db = database.active; const db = database.active;
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
const messagesCollection = db.collections.get('messages'); const messagesCollection = db.get('messages');
const subsIds = subscriptions.map(sub => sub.rid).concat(roomsResult.remove.map(room => room._id)); const subsIds = subscriptions.map(sub => sub.rid).concat(roomsResult.remove.map(room => room._id));
if (subsIds.length) { if (subsIds.length) {

View File

@ -46,7 +46,7 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
} }
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
yield serversDB.action(async() => { yield serversDB.action(async() => {
try { try {
const serverRecord = await serversCollection.find(server); const serverRecord = await serversCollection.find(server);
@ -78,7 +78,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
const serversDB = database.servers; const serversDB = database.servers;
yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server); yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server);
const userId = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); const userId = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`);
const userCollections = serversDB.collections.get('users'); const userCollections = serversDB.get('users');
let user = null; let user = null;
if (userId) { if (userId) {
try { try {
@ -124,6 +124,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
// and block the selectServerSuccess raising multiples errors // and block the selectServerSuccess raising multiples errors
RocketChat.setSettings(); RocketChat.setSettings();
RocketChat.setCustomEmojis(); RocketChat.setCustomEmojis();
RocketChat.setPermissions();
RocketChat.setEnterpriseModules(); RocketChat.setEnterpriseModules();
let serverInfo; let serverInfo;
@ -151,7 +152,7 @@ const handleServerRequest = function* handleServerRequest({ server, username, fr
const serverInfo = yield getServerInfo({ server }); const serverInfo = yield getServerInfo({ server });
const serversDB = database.servers; const serversDB = database.servers;
const serversHistoryCollection = serversDB.collections.get('servers_history'); const serversHistoryCollection = serversDB.get('servers_history');
if (serverInfo) { if (serverInfo) {
yield RocketChat.getLoginServices(server); yield RocketChat.getLoginServices(server);

View File

@ -12,13 +12,14 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
if (appRoot === ROOT_OUTSIDE) { if (appRoot === ROOT_OUTSIDE) {
return; return;
} }
const auth = yield select(state => state.login.isAuthenticated); const login = yield select(state => state.login);
if (!auth) { const server = yield select(state => state.server);
if (!login.isAuthenticated || login.isFetching || server.connecting || server.loading || server.changingServer) {
return; return;
} }
try { try {
const server = yield select(state => state.server.server); yield localAuthenticate(server.server);
yield localAuthenticate(server); RocketChat.checkAndReopen();
setBadgeCount(); setBadgeCount();
return yield RocketChat.setUserPresenceOnline(); return yield RocketChat.setUserPresenceOnline();
} catch (e) { } catch (e) {
@ -31,14 +32,6 @@ const appHasComeBackToBackground = function* appHasComeBackToBackground() {
if (appRoot === ROOT_OUTSIDE) { if (appRoot === ROOT_OUTSIDE) {
return; return;
} }
const auth = yield select(state => state.login.isAuthenticated);
if (!auth) {
return;
}
const localAuthenticated = yield select(state => state.login.isLocalAuthenticated);
if (!localAuthenticated) {
return;
}
try { try {
const server = yield select(state => state.server.server); const server = yield select(state => state.server.server);
yield saveLastLocalAuthenticationSession(server); yield saveLastLocalAuthenticationSession(server);

View File

@ -31,6 +31,7 @@ import PickerView from '../views/PickerView';
import ThreadMessagesView from '../views/ThreadMessagesView'; import ThreadMessagesView from '../views/ThreadMessagesView';
import MarkdownTableView from '../views/MarkdownTableView'; import MarkdownTableView from '../views/MarkdownTableView';
import ReadReceiptsView from '../views/ReadReceiptView'; import ReadReceiptsView from '../views/ReadReceiptView';
import { themes } from '../constants/colors';
// Profile Stack // Profile Stack
import ProfileView from '../views/ProfileView'; import ProfileView from '../views/ProfileView';
@ -280,19 +281,24 @@ const AdminPanelStackNavigator = () => {
// DrawerNavigator // DrawerNavigator
const Drawer = createDrawerNavigator(); const Drawer = createDrawerNavigator();
const DrawerNavigator = () => ( const DrawerNavigator = () => {
<Drawer.Navigator const { theme } = React.useContext(ThemeContext);
drawerContent={({ navigation, state }) => <Sidebar navigation={navigation} state={state} />}
drawerPosition={I18nManager.isRTL ? 'right' : 'left'} return (
screenOptions={{ swipeEnabled: false }} <Drawer.Navigator
drawerType='back' drawerContent={({ navigation, state }) => <Sidebar navigation={navigation} state={state} />}
> drawerPosition={I18nManager.isRTL ? 'right' : 'left'}
<Drawer.Screen name='ChatsStackNavigator' component={ChatsStackNavigator} /> screenOptions={{ swipeEnabled: false }}
<Drawer.Screen name='ProfileStackNavigator' component={ProfileStackNavigator} /> drawerType='back'
<Drawer.Screen name='SettingsStackNavigator' component={SettingsStackNavigator} /> overlayColor={`rgba(0,0,0,${ themes[theme].backdropOpacity })`}
<Drawer.Screen name='AdminPanelStackNavigator' component={AdminPanelStackNavigator} /> >
</Drawer.Navigator> <Drawer.Screen name='ChatsStackNavigator' component={ChatsStackNavigator} />
); <Drawer.Screen name='ProfileStackNavigator' component={ProfileStackNavigator} />
<Drawer.Screen name='SettingsStackNavigator' component={SettingsStackNavigator} />
<Drawer.Screen name='AdminPanelStackNavigator' component={AdminPanelStackNavigator} />
</Drawer.Navigator>
);
};
// NewMessageStackNavigator // NewMessageStackNavigator
const NewMessageStack = createStackNavigator(); const NewMessageStack = createStackNavigator();

View File

@ -1,13 +1,11 @@
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import reduxStore from '../lib/createStore';
const canPost = async({ rid }) => { const canPostReadOnly = async({ rid }) => {
try { // TODO: this is not reactive. If this permission changes, the component won't be updated
const permission = await RocketChat.hasPermission(['post-readonly'], rid); const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly'];
return permission && permission['post-readonly']; const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid);
} catch { return permission[0];
// do nothing
}
return false;
}; };
const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username); const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username);
@ -20,7 +18,7 @@ export const isReadOnly = async(room, user) => {
return true; return true;
} }
if (room?.ro) { if (room?.ro) {
const allowPost = await canPost(room); const allowPost = await canPostReadOnly(room);
if (allowPost) { if (allowPost) {
return false; return false;
} }

View File

@ -17,7 +17,7 @@ import { setLocalAuthenticated } from '../actions/login';
export const saveLastLocalAuthenticationSession = async(server, serverRecord) => { export const saveLastLocalAuthenticationSession = async(server, serverRecord) => {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
await serversDB.action(async() => { await serversDB.action(async() => {
try { try {
if (!serverRecord) { if (!serverRecord) {
@ -91,7 +91,7 @@ export const checkHasPasscode = async({ force = true, serverRecord }) => {
export const localAuthenticate = async(server) => { export const localAuthenticate = async(server) => {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.get('servers');
let serverRecord; let serverRecord;
try { try {
@ -102,9 +102,6 @@ export const localAuthenticate = async(server) => {
// if screen lock is enabled // if screen lock is enabled
if (serverRecord?.autoLock) { if (serverRecord?.autoLock) {
// set isLocalAuthenticated to false
store.dispatch(setLocalAuthenticated(false));
// Make sure splash screen has been hidden // Make sure splash screen has been hidden
RNBootSplash.hide(); RNBootSplash.hide();
@ -118,6 +115,9 @@ export const localAuthenticate = async(server) => {
// if last authenticated session is older than configured auto lock time, authentication is required // if last authenticated session is older than configured auto lock time, authentication is required
if (diffToLastSession >= serverRecord?.autoLockTime) { if (diffToLastSession >= serverRecord?.autoLockTime) {
// set isLocalAuthenticated to false
store.dispatch(setLocalAuthenticated(false));
let hasBiometry = false; let hasBiometry = false;
// if biometry is enabled on the app // if biometry is enabled on the app

View File

@ -5,9 +5,8 @@ export default {
ONBOARD_CREATE_NEW_WORKSPACE_F: 'onboard_create_new_workspace_f', ONBOARD_CREATE_NEW_WORKSPACE_F: 'onboard_create_new_workspace_f',
// NEW SERVER VIEW // NEW SERVER VIEW
NEWSERVER_CONNECT_TO_WORKSPACE: 'newserver_connect_to_workspace', NS_CONNECT_TO_WORKSPACE: 'ns_connect_to_workspace',
NEWSERVER_CONNECT_TO_WORKSPACE_F: 'newserver_connect_to_workspace_f', NS_JOIN_OPEN_WORKSPACE: 'ns_join_open_workspace',
NEWSERVER_JOIN_OPEN_WORKSPACE: 'newserver_join_open_workspace',
// LOGIN VIEW // LOGIN VIEW
LOGIN_DEFAULT_LOGIN: 'login_default_login', LOGIN_DEFAULT_LOGIN: 'login_default_login',
@ -99,20 +98,20 @@ export default {
SELECTED_USERS_CREATE_GROUP_F: 'selected_users_create_group_f', SELECTED_USERS_CREATE_GROUP_F: 'selected_users_create_group_f',
// CREATE CHANNEL VIEW // CREATE CHANNEL VIEW
CREATE_CHANNEL_CREATE: 'create_channel_create', CR_CREATE: 'cr_create',
CREATE_CHANNEL_CREATE_F: 'create_channel_create_f', CR_CREATE_F: 'cr_create_f',
CREATE_CHANNEL_TOGGLE_TYPE: 'create_channel_toggle_type', CR_TOGGLE_TYPE: 'cr_toggle_type',
CREATE_CHANNEL_TOGGLE_READ_ONLY: 'create_channel_toggle_read_only', CR_TOGGLE_READ_ONLY: 'cr_toggle_read_only',
CREATE_CHANNEL_TOGGLE_BROADCAST: 'create_channel_toggle_broadcast', CR_TOGGLE_BROADCAST: 'cr_toggle_broadcast',
CREATE_CHANNEL_TOGGLE_ENCRYPTED: 'create_channel_toggle_encrypted', CR_TOGGLE_ENCRYPTED: 'cr_toggle_encrypted',
CREATE_CHANNEL_REMOVE_USER: 'create_channel_remove_user', CR_REMOVE_USER: 'cr_remove_user',
// CREATE DISCUSSION VIEW // CREATE DISCUSSION VIEW
CREATE_DISCUSSION_CREATE: 'create_discussion_create', CD_CREATE: 'cd_create',
CREATE_DISCUSSION_CREATE_F: 'create_discussion_create_f', CD_CREATE_F: 'cd_create_f',
CREATE_DISCUSSION_SELECT_CHANNEL: 'create_discussion_select_channel', CD_SELECT_CHANNEL: 'cd_select_channel',
CREATE_DISCUSSION_SELECT_USERS: 'create_discussion_select_users', CD_SELECT_USERS: 'cd_select_users',
CREATE_DISCUSSION_TOGGLE_ENCRY: 'create_discussion_toggle_encry', CD_TOGGLE_ENCRY: 'cd_toggle_encry',
// PROFILE VIEW // PROFILE VIEW
PROFILE_PICK_AVATAR: 'profile_pick_avatar', PROFILE_PICK_AVATAR: 'profile_pick_avatar',
@ -122,8 +121,9 @@ export default {
PROFILE_SAVE_AVATAR_F: 'profile_save_avatar_f', PROFILE_SAVE_AVATAR_F: 'profile_save_avatar_f',
PROFILE_SAVE_CHANGES: 'profile_save_changes', PROFILE_SAVE_CHANGES: 'profile_save_changes',
PROFILE_SAVE_CHANGES_F: 'profile_save_changes_f', PROFILE_SAVE_CHANGES_F: 'profile_save_changes_f',
PROFILE_LOGOUT_OTHER_LOCATIONS: 'profile_logout_other_locations', // PROFILE LOGOUT
PROFILE_LOGOUT_OTHER_LOCATIONS_F: 'profile_logout_other_locations_f', PL_OTHER_LOCATIONS: 'pl_other_locations',
PL_OTHER_LOCATIONS_F: 'pl_other_locations_f',
// SETTINGS VIEW // SETTINGS VIEW
SE_CONTACT_US: 'se_contact_us', SE_CONTACT_US: 'se_contact_us',
@ -297,8 +297,6 @@ export default {
NP_AUDIONOTIFICATIONS_F: 'np_audio_notifications_f', NP_AUDIONOTIFICATIONS_F: 'np_audio_notifications_f',
NP_AUDIONOTIFICATIONVALUE: 'np_audio_notification_value', NP_AUDIONOTIFICATIONVALUE: 'np_audio_notification_value',
NP_AUDIONOTIFICATIONVALUE_F: 'np_audio_notification_value_f', NP_AUDIONOTIFICATIONVALUE_F: 'np_audio_notification_value_f',
NP_DESKTOPNOTIFICATIONDURATION: 'np_desktopnotificationduration',
NP_DESKTOPNOTIFICATIONDURATION_F: 'np_desktopnotificationduration_f',
NP_EMAILNOTIFICATIONS: 'np_email_notifications', NP_EMAILNOTIFICATIONS: 'np_email_notifications',
NP_EMAILNOTIFICATIONS_F: 'np_email_notifications_f', NP_EMAILNOTIFICATIONS_F: 'np_email_notifications_f',

View File

@ -121,8 +121,9 @@ class AttachmentView extends React.Component {
const extension = image_url ? `.${ mime.extension(image_type) || 'jpg' }` : `.${ mime.extension(video_type) || 'mp4' }`; const extension = image_url ? `.${ mime.extension(image_type) || 'jpg' }` : `.${ mime.extension(video_type) || 'mp4' }`;
const documentDir = `${ RNFetchBlob.fs.dirs.DocumentDir }/`; const documentDir = `${ RNFetchBlob.fs.dirs.DocumentDir }/`;
const path = `${ documentDir + SHA256(url) + extension }`; const path = `${ documentDir + SHA256(url) + extension }`;
const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment).then(res => res.path()); const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment);
await CameraRoll.save(file, { album: 'Rocket.Chat' }); await CameraRoll.save(path, { album: 'Rocket.Chat' });
await file.flush();
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') }); EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
} catch (e) { } catch (e) {
EventEmitter.emit(LISTENER, { message: I18n.t(image_url ? 'error-save-image' : 'error-save-video') }); EventEmitter.emit(LISTENER, { message: I18n.t(image_url ? 'error-save-image' : 'error-save-video') });

View File

@ -63,12 +63,12 @@ const ChangePasscodeView = React.memo(({ theme }) => {
if (!isTablet) { if (!isTablet) {
Orientation.lockToPortrait(); Orientation.lockToPortrait();
} }
EventEmitter.addEventListener(CHANGE_PASSCODE_EMITTER, showChangePasscode); const listener = EventEmitter.addEventListener(CHANGE_PASSCODE_EMITTER, showChangePasscode);
return (() => { return (() => {
if (!isTablet) { if (!isTablet) {
Orientation.unlockAllOrientations(); Orientation.unlockAllOrientations();
} }
EventEmitter.removeListener(CHANGE_PASSCODE_EMITTER); EventEmitter.removeListener(CHANGE_PASSCODE_EMITTER, listener);
}); });
}, []); }, []);

View File

@ -4,7 +4,8 @@ import PropTypes from 'prop-types';
import { import {
View, Text, Switch, ScrollView, StyleSheet, FlatList View, Text, Switch, ScrollView, StyleSheet, FlatList
} from 'react-native'; } from 'react-native';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import * as List from '../containers/List';
import TextInput from '../presentation/TextInput'; import TextInput from '../presentation/TextInput';
import Loading from '../containers/Loading'; import Loading from '../containers/Loading';
@ -31,12 +32,6 @@ const styles = StyleSheet.create({
list: { list: {
width: '100%' width: '100%'
}, },
separator: {
marginLeft: 60
},
formSeparator: {
marginLeft: 15
},
input: { input: {
height: 54, height: 54,
paddingHorizontal: 18, paddingHorizontal: 18,
@ -133,7 +128,7 @@ class CreateChannelView extends React.Component {
if (nextProps.encryptionEnabled !== encryptionEnabled) { if (nextProps.encryptionEnabled !== encryptionEnabled) {
return true; return true;
} }
if (!equal(nextProps.users, users)) { if (!dequal(nextProps.users, users)) {
return true; return true;
} }
return false; return false;
@ -177,7 +172,7 @@ class CreateChannelView extends React.Component {
} }
removeUser = (user) => { removeUser = (user) => {
logEvent(events.CREATE_CHANNEL_REMOVE_USER); logEvent(events.CR_REMOVE_USER);
const { removeUser } = this.props; const { removeUser } = this.props;
removeUser(user); removeUser(user);
} }
@ -207,7 +202,7 @@ class CreateChannelView extends React.Component {
value: type, value: type,
label: 'Private_Channel', label: 'Private_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CREATE_CHANNEL_TOGGLE_TYPE); logEvent(events.CR_TOGGLE_TYPE);
// If we set the channel as public, encrypted status should be false // If we set the channel as public, encrypted status should be false
this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted })); this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted }));
} }
@ -221,7 +216,7 @@ class CreateChannelView extends React.Component {
value: readOnly, value: readOnly,
label: 'Read_Only_Channel', label: 'Read_Only_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CREATE_CHANNEL_TOGGLE_READ_ONLY); logEvent(events.CR_TOGGLE_READ_ONLY);
this.setState({ readOnly: value }); this.setState({ readOnly: value });
}, },
disabled: broadcast disabled: broadcast
@ -241,7 +236,7 @@ class CreateChannelView extends React.Component {
value: encrypted, value: encrypted,
label: 'Encrypted', label: 'Encrypted',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CREATE_CHANNEL_TOGGLE_ENCRYPTED); logEvent(events.CR_TOGGLE_ENCRYPTED);
this.setState({ encrypted: value }); this.setState({ encrypted: value });
}, },
disabled: !type disabled: !type
@ -255,7 +250,7 @@ class CreateChannelView extends React.Component {
value: broadcast, value: broadcast,
label: 'Broadcast_Channel', label: 'Broadcast_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CREATE_CHANNEL_TOGGLE_BROADCAST); logEvent(events.CR_TOGGLE_BROADCAST);
this.setState({ this.setState({
broadcast: value, broadcast: value,
readOnly: value ? true : readOnly readOnly: value ? true : readOnly
@ -264,13 +259,6 @@ class CreateChannelView extends React.Component {
}); });
} }
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />
renderFormSeparator = () => {
const { theme } = this.props;
return <View style={[sharedStyles.separator, styles.formSeparator, { backgroundColor: themes[theme].separatorColor }]} />;
}
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { baseUrl, user, theme } = this.props; const { baseUrl, user, theme } = this.props;
@ -305,7 +293,7 @@ class CreateChannelView extends React.Component {
} }
]} ]}
renderItem={this.renderItem} renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
enableEmptySections enableEmptySections
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />
@ -341,13 +329,13 @@ class CreateChannelView extends React.Component {
theme={theme} theme={theme}
underlineColorAndroid='transparent' underlineColorAndroid='transparent'
/> />
{this.renderFormSeparator()} <List.Separator />
{this.renderType()} {this.renderType()}
{this.renderFormSeparator()} <List.Separator />
{this.renderReadOnly()} {this.renderReadOnly()}
{this.renderFormSeparator()} <List.Separator />
{this.renderEncrypted()} {this.renderEncrypted()}
{this.renderFormSeparator()} <List.Separator />
{this.renderBroadcast()} {this.renderBroadcast()}
</View> </View>
<View style={styles.invitedHeader}> <View style={styles.invitedHeader}>

View File

@ -22,7 +22,7 @@ const SelectUsers = ({
const getUsers = debounce(async(keyword = '') => { const getUsers = debounce(async(keyword = '') => {
try { try {
const db = database.active; const db = database.active;
const usersCollection = db.collections.get('users'); const usersCollection = db.get('users');
const res = await RocketChat.search({ text: keyword, filterRooms: false }); const res = await RocketChat.search({ text: keyword, filterRooms: false });
let items = [...users.filter(u => selected.includes(u.name)), ...res.filter(r => !users.find(u => u.name === r.name))]; let items = [...users.filter(u => selected.includes(u.name)), ...res.filter(r => !users.find(u => u.name === r.name))];
const records = await usersCollection.query(Q.where('username', Q.oneOf(items.map(u => u.name)))).fetch(); const records = await usersCollection.query(Q.where('username', Q.oneOf(items.map(u => u.name)))).fetch();

View File

@ -2,7 +2,6 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ScrollView, Text, Switch } from 'react-native'; import { ScrollView, Text, Switch } from 'react-native';
import isEqual from 'lodash/isEqual';
import Loading from '../../containers/Loading'; import Loading from '../../containers/Loading';
import KeyboardView from '../../presentation/KeyboardView'; import KeyboardView from '../../presentation/KeyboardView';
@ -63,11 +62,12 @@ class CreateChannelView extends React.Component {
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
const { channel, name } = this.state;
const { const {
loading, failure, error, result, isMasterDetail loading, failure, error, result, isMasterDetail
} = this.props; } = this.props;
if (!isEqual(this.state, prevState)) { if (channel?.rid !== prevState.channel?.rid || name !== prevState.name) {
this.setHeader(); this.setHeader();
} }
@ -136,17 +136,17 @@ class CreateChannelView extends React.Component {
}; };
selectChannel = ({ value }) => { selectChannel = ({ value }) => {
logEvent(events.CREATE_DISCUSSION_SELECT_CHANNEL); logEvent(events.CD_SELECT_CHANNEL);
this.setState({ channel: value, encrypted: value?.encrypted }); this.setState({ channel: value, encrypted: value?.encrypted });
} }
selectUsers = ({ value }) => { selectUsers = ({ value }) => {
logEvent(events.CREATE_DISCUSSION_SELECT_USERS); logEvent(events.CD_SELECT_USERS);
this.setState({ users: value }); this.setState({ users: value });
} }
onEncryptedChange = (value) => { onEncryptedChange = (value) => {
logEvent(events.CREATE_DISCUSSION_TOGGLE_ENCRY); logEvent(events.CD_TOGGLE_ENCRY);
this.setState({ encrypted: value }); this.setState({ encrypted: value });
} }
@ -222,7 +222,7 @@ const mapStateToProps = state => ({
loading: state.createDiscussion.isFetching, loading: state.createDiscussion.isFetching,
result: state.createDiscussion.result, result: state.createDiscussion.result,
blockUnauthenticatedAccess: state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true, blockUnauthenticatedAccess: state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true,
serverVersion: state.share.server.version || state.server.version, serverVersion: state.server.version,
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
encryptionEnabled: state.encryption.enabled encryptionEnabled: state.encryption.enabled
}); });

View File

@ -84,13 +84,13 @@ export default class DirectoryOptions extends PureComponent {
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [-326, 0] outputRange: [-326, 0]
}); });
const backdropOpacity = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.3]
});
const { const {
globalUsers, toggleWorkspace, isFederationEnabled, theme globalUsers, toggleWorkspace, isFederationEnabled, theme
} = this.props; } = this.props;
const backdropOpacity = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity]
});
return ( return (
<> <>
<TouchableWithoutFeedback onPress={this.close}> <TouchableWithoutFeedback onPress={this.close}>

View File

@ -4,6 +4,7 @@ import {
View, FlatList, Text View, FlatList, Text
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as List from '../../containers/List';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
@ -182,11 +183,6 @@ class DirectoryView extends React.Component {
); );
} }
renderSeparator = () => {
const { theme } = this.props;
return <View style={[sharedStyles.separator, styles.separator, { backgroundColor: themes[theme].separatorColor }]} />;
}
renderItem = ({ item, index }) => { renderItem = ({ item, index }) => {
const { data, type } = this.state; const { data, type } = this.state;
const { baseUrl, user, theme } = this.props; const { baseUrl, user, theme } = this.props;
@ -251,7 +247,7 @@ class DirectoryView extends React.Component {
keyExtractor={item => item._id} keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader} ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem} renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null} ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null}
onEndReached={() => this.load({})} onEndReached={() => this.load({})}

View File

@ -94,7 +94,7 @@ class LanguageView extends React.Component {
setUser({ language: params.language }); setUser({ language: params.language });
const serversDB = database.servers; const serversDB = database.servers;
const usersCollection = serversDB.collections.get('users'); const usersCollection = serversDB.get('users');
await serversDB.action(async() => { await serversDB.action(async() => {
try { try {
const userRecord = await usersCollection.find(user.id); const userRecord = await usersCollection.find(user.id);

View File

@ -4,7 +4,7 @@ import {
Text, View, StyleSheet, Keyboard, Alert Text, View, StyleSheet, Keyboard, Alert
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
import Button from '../containers/Button'; import Button from '../containers/Button';
@ -82,7 +82,7 @@ class LoginView extends React.Component {
UNSAFE_componentWillReceiveProps(nextProps) { UNSAFE_componentWillReceiveProps(nextProps) {
const { error } = this.props; const { error } = this.props;
if (nextProps.failure && !equal(error, nextProps.error)) { if (nextProps.failure && !dequal(error, nextProps.error)) {
Alert.alert(I18n.t('Oops'), I18n.t('Login_error')); Alert.alert(I18n.t('Oops'), I18n.t('Login_error'));
} }
} }

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native'; import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import styles from './styles'; import styles from './styles';
import Message from '../../containers/message'; import Message from '../../containers/message';
@ -57,7 +57,7 @@ class MessagesView extends React.Component {
if (nextState.loading !== loading) { if (nextState.loading !== loading) {
return true; return true;
} }
if (!equal(nextState.messages, messages)) { if (!dequal(nextState.messages, messages)) {
return true; return true;
} }
if (fileLoading !== nextState.fileLoading) { if (fileLoading !== nextState.fileLoading) {

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { KeyboardAwareScrollView } from '@codler/react-native-keyboard-aware-scroll-view'; import { KeyboardAwareScrollView } from '@codler/react-native-keyboard-aware-scroll-view';
@ -94,17 +93,6 @@ class ModalBlockView extends React.Component {
EventEmitter.addEventListener(viewId, this.handleUpdate); EventEmitter.addEventListener(viewId, this.handleUpdate);
} }
shouldComponentUpdate(nextProps, nextState) {
if (!isEqual(nextProps, this.props)) {
return true;
}
if (!isEqual(nextState, this.state)) {
return true;
}
return false;
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { navigation, route } = this.props; const { navigation, route } = this.props;
const oldData = prevProps.route.params?.data ?? {}; const oldData = prevProps.route.params?.data ?? {};

View File

@ -4,9 +4,8 @@ import {
View, StyleSheet, FlatList, Text View, StyleSheet, FlatList, Text
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import equal from 'deep-equal';
import orderBy from 'lodash/orderBy';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import * as List from '../containers/List';
import Touch from '../utils/touch'; import Touch from '../utils/touch';
import database from '../lib/database'; import database from '../lib/database';
@ -27,10 +26,9 @@ import { createChannelRequest } from '../actions/createChannel';
import { goRoom } from '../utils/goRoom'; import { goRoom } from '../utils/goRoom';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
const QUERY_SIZE = 50;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
separator: {
marginLeft: 60
},
button: { button: {
height: 46, height: 46,
flexDirection: 'row', flexDirection: 'row',
@ -77,40 +75,20 @@ class NewMessageView extends React.Component {
}; };
} }
shouldComponentUpdate(nextProps, nextState) {
const { search, chats } = this.state;
const { theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (!equal(nextState.search, search)) {
return true;
}
if (!equal(nextState.chats, chats)) {
return true;
}
return false;
}
componentWillUnmount() {
if (this.querySubscription && this.querySubscription.unsubscribe) {
this.querySubscription.unsubscribe();
}
}
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
init = async() => { init = async() => {
try { try {
const db = database.active; const db = database.active;
const observable = await db.collections const chats = await db.collections
.get('subscriptions') .get('subscriptions')
.query(Q.where('t', 'd')) .query(
.observeWithColumns(['room_updated_at']); Q.where('t', 'd'),
Q.experimentalTake(QUERY_SIZE),
Q.experimentalSortBy('room_updated_at', Q.desc)
)
.fetch();
this.querySubscription = observable.subscribe((data) => { this.setState({ chats });
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']);
this.setState({ chats });
});
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -211,10 +189,6 @@ class NewMessageView extends React.Component {
); );
} }
renderSeparator = () => {
const { theme } = this.props;
return <View style={[sharedStyles.separator, styles.separator, { backgroundColor: themes[theme].separatorColor }]} />;
}
renderItem = ({ item, index }) => { renderItem = ({ item, index }) => {
const { search, chats } = this.state; const { search, chats } = this.state;
@ -254,7 +228,7 @@ class NewMessageView extends React.Component {
keyExtractor={item => item._id} keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader} ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem} renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }} contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />

View File

@ -132,7 +132,7 @@ class NewServerView extends React.Component {
queryServerHistory = async(text) => { queryServerHistory = async(text) => {
const db = database.servers; const db = database.servers;
try { try {
const serversHistoryCollection = db.collections.get('servers_history'); const serversHistoryCollection = db.get('servers_history');
let whereClause = [ let whereClause = [
Q.where('username', Q.notEq(null)), Q.where('username', Q.notEq(null)),
Q.experimentalSortBy('updated_at', Q.desc), Q.experimentalSortBy('updated_at', Q.desc),
@ -174,7 +174,7 @@ class NewServerView extends React.Component {
} }
submit = async({ fromServerHistory = false, username }) => { submit = async({ fromServerHistory = false, username }) => {
logEvent(events.NEWSERVER_CONNECT_TO_WORKSPACE); logEvent(events.NS_CONNECT_TO_WORKSPACE);
const { text, certificate } = this.state; const { text, certificate } = this.state;
const { connectServer } = this.props; const { connectServer } = this.props;
@ -199,7 +199,7 @@ class NewServerView extends React.Component {
} }
connectOpen = () => { connectOpen = () => {
logEvent(events.NEWSERVER_JOIN_OPEN_WORKSPACE); logEvent(events.NS_JOIN_OPEN_WORKSPACE);
this.setState({ connectingOpen: true }); this.setState({ connectingOpen: true });
const { connectServer } = this.props; const { connectServer } = this.props;
connectServer('https://open.rocket.chat'); connectServer('https://open.rocket.chat');

View File

@ -6,7 +6,7 @@ import prompt from 'react-native-prompt-android';
import SHA256 from 'js-sha256'; import SHA256 from 'js-sha256';
import ImagePicker from 'react-native-image-crop-picker'; import ImagePicker from 'react-native-image-crop-picker';
import RNPickerSelect from 'react-native-picker-select'; import RNPickerSelect from 'react-native-picker-select';
import isEqual from 'lodash/isEqual'; import { dequal } from 'dequal';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
@ -91,21 +91,11 @@ class ProfileView extends React.Component {
* it's resetting the avatar right after * it's resetting the avatar right after
* select some image from gallery. * select some image from gallery.
*/ */
if (!isEqual(omit(user, ['status']), omit(nextProps.user, ['status']))) { if (!dequal(omit(user, ['status']), omit(nextProps.user, ['status']))) {
this.init(nextProps.user); this.init(nextProps.user);
} }
} }
shouldComponentUpdate(nextProps, nextState) {
if (!isEqual(nextState, this.state)) {
return true;
}
if (!isEqual(nextProps, this.props)) {
return true;
}
return false;
}
setAvatar = (avatar) => { setAvatar = (avatar) => {
const { Accounts_AllowUserAvatarChange } = this.props; const { Accounts_AllowUserAvatarChange } = this.props;
@ -434,7 +424,7 @@ class ProfileView extends React.Component {
} }
logoutOtherLocations = () => { logoutOtherLocations = () => {
logEvent(events.PROFILE_LOGOUT_OTHER_LOCATIONS); logEvent(events.PL_OTHER_LOCATIONS);
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('You_will_be_logged_out_from_other_locations'), message: I18n.t('You_will_be_logged_out_from_other_locations'),
confirmationText: I18n.t('Logout'), confirmationText: I18n.t('Logout'),
@ -443,7 +433,7 @@ class ProfileView extends React.Component {
await RocketChat.logoutOtherLocations(); await RocketChat.logoutOtherLocations();
EventEmitter.emit(LISTENER, { message: I18n.t('Logged_out_of_other_clients_successfully') }); EventEmitter.emit(LISTENER, { message: I18n.t('Logged_out_of_other_clients_successfully') });
} catch { } catch {
logEvent(events.PROFILE_LOGOUT_OTHER_LOCATIONS_F); logEvent(events.PL_OTHER_LOCATIONS_F);
EventEmitter.emit(LISTENER, { message: I18n.t('Logout_failed') }); EventEmitter.emit(LISTENER, { message: I18n.t('Logout_failed') });
} }
} }

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native'; import { FlatList, View, Text } from 'react-native';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import moment from 'moment'; import moment from 'moment';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as List from '../../containers/List';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import styles from './styles'; import styles from './styles';
@ -55,7 +56,7 @@ class ReadReceiptView extends React.Component {
if (nextState.loading !== loading) { if (nextState.loading !== loading) {
return true; return true;
} }
if (!equal(nextState.receipts, receipts)) { if (!dequal(nextState.receipts, receipts)) {
return true; return true;
} }
return false; return false;
@ -121,11 +122,6 @@ class ReadReceiptView extends React.Component {
); );
} }
renderSeparator = () => {
const { theme } = this.props;
return <View style={[styles.separator, { backgroundColor: themes[theme].separatorColor }]} />;
}
render() { render() {
const { receipts, loading } = this.state; const { receipts, loading } = this.state;
const { theme } = this.props; const { theme } = this.props;
@ -143,7 +139,7 @@ class ReadReceiptView extends React.Component {
<FlatList <FlatList
data={receipts} data={receipts}
renderItem={this.renderItem} renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
style={[ style={[
styles.list, styles.list,
{ {

View File

@ -53,7 +53,15 @@ class RoomActionsView extends React.Component {
closeRoom: PropTypes.func, closeRoom: PropTypes.func,
theme: PropTypes.string, theme: PropTypes.string,
fontScale: PropTypes.number, fontScale: PropTypes.number,
serverVersion: PropTypes.string serverVersion: PropTypes.string,
addUserToJoinedRoomPermission: PropTypes.array,
addUserToAnyCRoomPermission: PropTypes.array,
addUserToAnyPRoomPermission: PropTypes.array,
createInviteLinksPermission: PropTypes.array,
editRoomPermission: PropTypes.array,
toggleRoomE2EEncryptionPermission: PropTypes.array,
viewBroadcastMemberListPermission: PropTypes.array,
transferLivechatGuestPermission: PropTypes.array
} }
constructor(props) { constructor(props) {
@ -118,7 +126,7 @@ class RoomActionsView extends React.Component {
this.updateRoomMember(); this.updateRoomMember();
} }
const canAutoTranslate = await RocketChat.canAutoTranslate(); const canAutoTranslate = RocketChat.canAutoTranslate();
this.setState({ canAutoTranslate }); this.setState({ canAutoTranslate });
this.canAddUser(); this.canAddUser();
@ -159,60 +167,62 @@ class RoomActionsView extends React.Component {
canAddUser = async() => { canAddUser = async() => {
const { room, joined } = this.state; const { room, joined } = this.state;
const { addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission } = this.props;
const { rid, t } = room; const { rid, t } = room;
let canAdd = false; let canAddUser = false;
const userInRoom = joined; const userInRoom = joined;
const permissions = await RocketChat.hasPermission(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'], rid); const permissions = await RocketChat.hasPermission([addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission], rid);
if (permissions) { if (userInRoom && permissions[0]) {
if (userInRoom && permissions['add-user-to-joined-room']) { canAddUser = true;
canAdd = true;
}
if (t === 'c' && permissions['add-user-to-any-c-room']) {
canAdd = true;
}
if (t === 'p' && permissions['add-user-to-any-p-room']) {
canAdd = true;
}
} }
this.setState({ canAddUser: canAdd }); if (t === 'c' && permissions[1]) {
canAddUser = true;
}
if (t === 'p' && permissions[2]) {
canAddUser = true;
}
this.setState({ canAddUser });
} }
canInviteUser = async() => { canInviteUser = async() => {
const { room } = this.state; const { room } = this.state;
const { createInviteLinksPermission } = this.props;
const { rid } = room; const { rid } = room;
const permissions = await RocketChat.hasPermission(['create-invite-links'], rid); const permissions = await RocketChat.hasPermission([createInviteLinksPermission], rid);
const canInviteUser = permissions && permissions['create-invite-links']; const canInviteUser = permissions[0];
this.setState({ canInviteUser }); this.setState({ canInviteUser });
} }
canEdit = async() => { canEdit = async() => {
const { room } = this.state; const { room } = this.state;
const { editRoomPermission } = this.props;
const { rid } = room; const { rid } = room;
const permissions = await RocketChat.hasPermission(['edit-room'], rid); const permissions = await RocketChat.hasPermission([editRoomPermission], rid);
const canEdit = permissions && permissions['edit-room']; const canEdit = permissions[0];
this.setState({ canEdit }); this.setState({ canEdit });
} }
canToggleEncryption = async() => { canToggleEncryption = async() => {
const { room } = this.state; const { room } = this.state;
const { toggleRoomE2EEncryptionPermission } = this.props;
const { rid } = room; const { rid } = room;
const permissions = await RocketChat.hasPermission(['toggle-room-e2e-encryption'], rid); const permissions = await RocketChat.hasPermission([toggleRoomE2EEncryptionPermission], rid);
const canToggleEncryption = permissions && permissions['toggle-room-e2e-encryption']; const canToggleEncryption = permissions[0];
this.setState({ canToggleEncryption }); this.setState({ canToggleEncryption });
} }
canViewMembers = async() => { canViewMembers = async() => {
const { room } = this.state; const { room } = this.state;
const { viewBroadcastMemberListPermission } = this.props;
const { rid, t, broadcast } = room; const { rid, t, broadcast } = room;
if (broadcast) { if (broadcast) {
const viewBroadcastMemberListPermission = 'view-broadcast-member-list';
const permissions = await RocketChat.hasPermission([viewBroadcastMemberListPermission], rid); const permissions = await RocketChat.hasPermission([viewBroadcastMemberListPermission], rid);
if (!permissions[viewBroadcastMemberListPermission]) { if (!permissions[0]) {
return false; return false;
} }
} }
@ -226,16 +236,10 @@ class RoomActionsView extends React.Component {
canForwardGuest = async() => { canForwardGuest = async() => {
const { room } = this.state; const { room } = this.state;
const { transferLivechatGuestPermission } = this.props;
const { rid } = room; const { rid } = room;
let result = true; const permissions = await RocketChat.hasPermission([transferLivechatGuestPermission], rid);
this.setState({ canForwardGuest: permissions[0] });
const transferLivechatGuest = 'transfer-livechat-guest';
const permissions = await RocketChat.hasPermission([transferLivechatGuest], rid);
if (!permissions[transferLivechatGuest]) {
result = false;
}
this.setState({ canForwardGuest: result });
} }
canReturnQueue = async() => { canReturnQueue = async() => {
@ -482,7 +486,7 @@ class RoomActionsView extends React.Component {
<List.Separator /> <List.Separator />
<List.Item <List.Item
title='Voice_call' title='Voice_call'
onPress={() => RocketChat.callJitsi(room?.rid, true)} onPress={() => RocketChat.callJitsi(room, true)}
testID='room-actions-voice' testID='room-actions-voice'
left={() => <List.Icon name='phone' />} left={() => <List.Icon name='phone' />}
showActionIndicator showActionIndicator
@ -490,7 +494,7 @@ class RoomActionsView extends React.Component {
<List.Separator /> <List.Separator />
<List.Item <List.Item
title='Video_call' title='Video_call'
onPress={() => RocketChat.callJitsi(room?.rid)} onPress={() => RocketChat.callJitsi(room)}
testID='room-actions-video' testID='room-actions-video'
left={() => <List.Icon name='camera' />} left={() => <List.Icon name='camera' />}
showActionIndicator showActionIndicator
@ -866,7 +870,15 @@ class RoomActionsView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
jitsiEnabled: state.settings.Jitsi_Enabled || false, jitsiEnabled: state.settings.Jitsi_Enabled || false,
encryptionEnabled: state.encryption.enabled, encryptionEnabled: state.encryption.enabled,
serverVersion: state.server.version serverVersion: state.server.version,
addUserToJoinedRoomPermission: state.permissions['add-user-to-joined-room'],
addUserToAnyCRoomPermission: state.permissions['add-user-to-any-c-room'],
addUserToAnyPRoomPermission: state.permissions['add-user-to-any-p-room'],
createInviteLinksPermission: state.permissions['create-invite-links'],
editRoomPermission: state.permissions['edit-room'],
toggleRoomE2EEncryptionPermission: state.permissions['toggle-room-e2e-encryption'],
viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'],
transferLivechatGuestPermission: state.permissions['transfer-livechat-guest']
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -4,15 +4,13 @@ import {
Text, View, ScrollView, TouchableOpacity, Keyboard, Alert Text, View, ScrollView, TouchableOpacity, Keyboard, Alert
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import equal from 'deep-equal';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import ImagePicker from 'react-native-image-crop-picker'; import ImagePicker from 'react-native-image-crop-picker';
import isEqual from 'lodash/isEqual'; import { dequal } from 'dequal';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import lt from 'semver/functions/lt'; import lt from 'semver/functions/lt';
import coerce from 'semver/functions/coerce'; import coerce from 'semver/functions/coerce';
import database from '../../lib/database'; import database from '../../lib/database';
import { deleteRoom as deleteRoomAction } from '../../actions/room'; import { deleteRoom as deleteRoomAction } from '../../actions/room';
import KeyboardView from '../../presentation/KeyboardView'; import KeyboardView from '../../presentation/KeyboardView';
@ -44,14 +42,6 @@ const PERMISSION_ARCHIVE = 'archive-room';
const PERMISSION_UNARCHIVE = 'unarchive-room'; const PERMISSION_UNARCHIVE = 'unarchive-room';
const PERMISSION_DELETE_C = 'delete-c'; const PERMISSION_DELETE_C = 'delete-c';
const PERMISSION_DELETE_P = 'delete-p'; const PERMISSION_DELETE_P = 'delete-p';
const PERMISSIONS_ARRAY = [
PERMISSION_SET_READONLY,
PERMISSION_SET_REACT_WHEN_READONLY,
PERMISSION_ARCHIVE,
PERMISSION_UNARCHIVE,
PERMISSION_DELETE_C,
PERMISSION_DELETE_P
];
class RoomInfoEditView extends React.Component { class RoomInfoEditView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -63,7 +53,13 @@ class RoomInfoEditView extends React.Component {
deleteRoom: PropTypes.func, deleteRoom: PropTypes.func,
serverVersion: PropTypes.string, serverVersion: PropTypes.string,
encryptionEnabled: PropTypes.bool, encryptionEnabled: PropTypes.bool,
theme: PropTypes.string theme: PropTypes.string,
setReadOnlyPermission: PropTypes.array,
setReactWhenReadOnlyPermission: PropTypes.array,
archiveRoomPermission: PropTypes.array,
unarchiveRoomPermission: PropTypes.array,
deleteCPermission: PropTypes.array,
deletePPermission: PropTypes.array
}; };
constructor(props) { constructor(props) {
@ -90,16 +86,6 @@ class RoomInfoEditView extends React.Component {
this.loadRoom(); this.loadRoom();
} }
shouldComponentUpdate(nextProps, nextState) {
if (!equal(nextState, this.state)) {
return true;
}
if (!equal(nextProps, this.props)) {
return true;
}
return false;
}
componentWillUnmount() { componentWillUnmount() {
if (this.querySubscription && this.querySubscription.unsubscribe) { if (this.querySubscription && this.querySubscription.unsubscribe) {
this.querySubscription.unsubscribe(); this.querySubscription.unsubscribe();
@ -108,14 +94,22 @@ class RoomInfoEditView extends React.Component {
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
loadRoom = async() => { loadRoom = async() => {
const { route } = this.props; const {
route,
setReadOnlyPermission,
setReactWhenReadOnlyPermission,
archiveRoomPermission,
unarchiveRoomPermission,
deleteCPermission,
deletePPermission
} = this.props;
const rid = route.params?.rid; const rid = route.params?.rid;
if (!rid) { if (!rid) {
return; return;
} }
try { try {
const db = database.active; const db = database.active;
const sub = await db.collections.get('subscriptions').find(rid); const sub = await db.get('subscriptions').find(rid);
const observable = sub.observe(); const observable = sub.observe();
this.querySubscription = observable.subscribe((data) => { this.querySubscription = observable.subscribe((data) => {
@ -123,8 +117,25 @@ class RoomInfoEditView extends React.Component {
this.init(this.room); this.init(this.room);
}); });
const permissions = await RocketChat.hasPermission(PERMISSIONS_ARRAY, rid); const result = await RocketChat.hasPermission([
this.setState({ permissions }); setReadOnlyPermission,
setReactWhenReadOnlyPermission,
archiveRoomPermission,
unarchiveRoomPermission,
deleteCPermission,
deletePPermission
], rid);
this.setState({
permissions: {
[PERMISSION_SET_READONLY]: result[0],
[PERMISSION_SET_REACT_WHEN_READONLY]: result[1],
[PERMISSION_ARCHIVE]: result[2],
[PERMISSION_UNARCHIVE]: result[3],
[PERMISSION_DELETE_C]: result[4],
[PERMISSION_DELETE_P]: result[5]
}
});
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -179,7 +190,7 @@ class RoomInfoEditView extends React.Component {
&& room.t === 'p' === t && room.t === 'p' === t
&& room.ro === ro && room.ro === ro
&& room.reactWhenReadOnly === reactWhenReadOnly && room.reactWhenReadOnly === reactWhenReadOnly
&& isEqual(room.sysMes, systemMessages) && dequal(room.sysMes, systemMessages)
&& enableSysMes === (room.sysMes && room.sysMes.length > 0) && enableSysMes === (room.sysMes && room.sysMes.length > 0)
&& room.encrypted === encrypted && room.encrypted === encrypted
&& isEmpty(avatar) && isEmpty(avatar)
@ -239,7 +250,7 @@ class RoomInfoEditView extends React.Component {
params.reactWhenReadOnly = reactWhenReadOnly; params.reactWhenReadOnly = reactWhenReadOnly;
} }
if (!isEqual(room.sysMes, systemMessages)) { if (!dequal(room.sysMes, systemMessages)) {
params.systemMessages = systemMessages; params.systemMessages = systemMessages;
} }
@ -666,8 +677,14 @@ class RoomInfoEditView extends React.Component {
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
serverVersion: state.share.server.version || state.server.version, serverVersion: state.server.version,
encryptionEnabled: state.encryption.enabled encryptionEnabled: state.encryption.enabled,
setReadOnlyPermission: state.permissions[PERMISSION_SET_READONLY],
setReactWhenReadOnlyPermission: state.permissions[PERMISSION_SET_REACT_WHEN_READONLY],
archiveRoomPermission: state.permissions[PERMISSION_ARCHIVE],
unarchiveRoomPermission: state.permissions[PERMISSION_UNARCHIVE],
deleteCPermission: state.permissions[PERMISSION_DELETE_C],
deletePPermission: state.permissions[PERMISSION_DELETE_P]
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -31,7 +31,6 @@ import SafeAreaView from '../../containers/SafeAreaView';
import { goRoom } from '../../utils/goRoom'; import { goRoom } from '../../utils/goRoom';
import Navigation from '../../lib/Navigation'; import Navigation from '../../lib/Navigation';
const PERMISSION_EDIT_ROOM = 'edit-room';
const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd' const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd'
? ( ? (
<> <>
@ -55,7 +54,8 @@ class RoomInfoView extends React.Component {
rooms: PropTypes.array, rooms: PropTypes.array,
theme: PropTypes.string, theme: PropTypes.string,
isMasterDetail: PropTypes.bool, isMasterDetail: PropTypes.bool,
jitsiEnabled: PropTypes.bool jitsiEnabled: PropTypes.bool,
editRoomPermission: PropTypes.array
} }
constructor(props) { constructor(props) {
@ -136,7 +136,7 @@ class RoomInfoView extends React.Component {
getRoleDescription = async(id) => { getRoleDescription = async(id) => {
const db = database.active; const db = database.active;
try { try {
const rolesCollection = db.collections.get('roles'); const rolesCollection = db.get('roles');
const role = await rolesCollection.find(id); const role = await rolesCollection.find(id);
if (role) { if (role) {
return role.description; return role.description;
@ -193,7 +193,7 @@ class RoomInfoView extends React.Component {
loadRoom = async() => { loadRoom = async() => {
const { room: roomState } = this.state; const { room: roomState } = this.state;
const { route } = this.props; const { route, editRoomPermission } = this.props;
let room = route.params?.room; let room = route.params?.room;
if (room && room.observe) { if (room && room.observe) {
this.roomObservable = room.observe(); this.roomObservable = room.observe();
@ -213,8 +213,8 @@ class RoomInfoView extends React.Component {
} }
} }
const permissions = await RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid); const permissions = await RocketChat.hasPermission([editRoomPermission], room.rid);
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) { if (permissions[0] && !room.prid) {
this.setState({ showEdit: true }, () => this.setHeader()); this.setState({ showEdit: true }, () => this.setHeader());
} }
} }
@ -276,7 +276,7 @@ class RoomInfoView extends React.Component {
videoCall = () => { videoCall = () => {
const { room } = this.state; const { room } = this.state;
RocketChat.callJitsi(room.rid); RocketChat.callJitsi(room);
} }
renderAvatar = (room, roomUser) => { renderAvatar = (room, roomUser) => {
@ -369,7 +369,8 @@ class RoomInfoView extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
rooms: state.room.rooms, rooms: state.room.rooms,
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
jitsiEnabled: state.settings.Jitsi_Enabled || false jitsiEnabled: state.settings.Jitsi_Enabled || false,
editRoomPermission: state.permissions['edit-room']
}); });
export default connect(mapStateToProps)(withTheme(RoomInfoView)); export default connect(mapStateToProps)(withTheme(RoomInfoView));

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FlatList, View } from 'react-native'; import { FlatList } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import * as List from '../../containers/List';
import styles from './styles'; import styles from './styles';
import UserItem from '../../presentation/UserItem'; import UserItem from '../../presentation/UserItem';
@ -28,6 +29,12 @@ import { goRoom } from '../../utils/goRoom';
const PAGE_SIZE = 25; const PAGE_SIZE = 25;
const PERMISSION_MUTE_USER = 'mute-user';
const PERMISSION_SET_LEADER = 'set-leader';
const PERMISSION_SET_OWNER = 'set-owner';
const PERMISSION_SET_MODERATOR = 'set-moderator';
const PERMISSION_REMOVE_USER = 'remove-user';
class RoomMembersView extends React.Component { class RoomMembersView extends React.Component {
static propTypes = { static propTypes = {
navigation: PropTypes.object, navigation: PropTypes.object,
@ -43,7 +50,12 @@ class RoomMembersView extends React.Component {
showActionSheet: PropTypes.func, showActionSheet: PropTypes.func,
theme: PropTypes.string, theme: PropTypes.string,
isMasterDetail: PropTypes.bool, isMasterDetail: PropTypes.bool,
useRealName: PropTypes.bool useRealName: PropTypes.bool,
muteUserPermission: PropTypes.array,
setLeaderPermission: PropTypes.array,
setOwnerPermission: PropTypes.array,
setModeratorPermission: PropTypes.array,
removeUserPermission: PropTypes.array
} }
constructor(props) { constructor(props) {
@ -81,7 +93,20 @@ class RoomMembersView extends React.Component {
this.fetchMembers(); this.fetchMembers();
const { room } = this.state; const { room } = this.state;
this.permissions = await RocketChat.hasPermission(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user'], room.rid); const {
muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission
} = this.props;
const result = await RocketChat.hasPermission([
muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission
], room.rid);
this.permissions = {
[PERMISSION_MUTE_USER]: result[0],
[PERMISSION_SET_LEADER]: result[1],
[PERMISSION_SET_OWNER]: result[2],
[PERMISSION_SET_MODERATOR]: result[3],
[PERMISSION_REMOVE_USER]: result[4]
};
const hasSinglePermission = Object.values(this.permissions).some(p => !!p); const hasSinglePermission = Object.values(this.permissions).some(p => !!p);
if (hasSinglePermission) { if (hasSinglePermission) {
@ -122,7 +147,7 @@ class RoomMembersView extends React.Component {
navToDirectMessage = async(item) => { navToDirectMessage = async(item) => {
try { try {
const db = database.active; const db = database.active;
const subsCollection = db.collections.get('subscriptions'); const subsCollection = db.get('subscriptions');
const query = await subsCollection.query(Q.where('name', item.username)).fetch(); const query = await subsCollection.query(Q.where('name', item.username)).fetch();
if (query.length) { if (query.length) {
const [room] = query; const [room] = query;
@ -395,11 +420,6 @@ class RoomMembersView extends React.Component {
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='room-members-view-search' /> <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='room-members-view-search' />
) )
renderSeparator = () => {
const { theme } = this.props;
return <View style={[styles.separator, { backgroundColor: themes[theme].separatorColor }]} />;
}
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { baseUrl, user, theme } = this.props; const { baseUrl, user, theme } = this.props;
@ -429,7 +449,7 @@ class RoomMembersView extends React.Component {
renderItem={this.renderItem} renderItem={this.renderItem}
style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]}
keyExtractor={item => item._id} keyExtractor={item => item._id}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
ListHeaderComponent={this.renderSearchBar} ListHeaderComponent={this.renderSearchBar}
ListFooterComponent={() => { ListFooterComponent={() => {
if (isLoading) { if (isLoading) {
@ -452,7 +472,12 @@ const mapStateToProps = state => ({
baseUrl: state.server.server, baseUrl: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
useRealName: state.settings.UI_Use_Real_Name useRealName: state.settings.UI_Use_Real_Name,
muteUserPermission: state.permissions[PERMISSION_MUTE_USER],
setLeaderPermission: state.permissions[PERMISSION_SET_LEADER],
setOwnerPermission: state.permissions[PERMISSION_SET_OWNER],
setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR],
removeUserPermission: state.permissions[PERMISSION_REMOVE_USER]
}); });
export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView))); export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView)));

View File

@ -168,7 +168,7 @@ const Header = React.memo(({
theme={theme} theme={theme}
/> />
</View> </View>
<SubTitle usersTyping={usersTyping} subtitle={subtitle} theme={theme} renderFunc={renderFunc} /> <SubTitle usersTyping={tmid ? [] : usersTyping} subtitle={subtitle} theme={theme} renderFunc={renderFunc} />
</TouchableOpacity> </TouchableOpacity>
); );
}); });

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import isEqual from 'react-fast-compare'; import { dequal } from 'dequal';
import * as HeaderButton from '../../../containers/HeaderButton'; import * as HeaderButton from '../../../containers/HeaderButton';
import database from '../../../lib/database'; import database from '../../../lib/database';
@ -35,7 +35,7 @@ class RightButtonsContainer extends Component {
const db = database.active; const db = database.active;
if (tmid) { if (tmid) {
try { try {
const threadRecord = await db.collections.get('messages').find(tmid); const threadRecord = await db.get('messages').find(tmid);
this.observeThread(threadRecord); this.observeThread(threadRecord);
} catch (e) { } catch (e) {
console.log('Can\'t find message to observe.'); console.log('Can\'t find message to observe.');
@ -43,7 +43,7 @@ class RightButtonsContainer extends Component {
} }
if (rid) { if (rid) {
try { try {
const subCollection = db.collections.get('subscriptions'); const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid); const subRecord = await subCollection.find(rid);
this.observeSubscription(subRecord); this.observeSubscription(subRecord);
} catch (e) { } catch (e) {
@ -59,15 +59,16 @@ class RightButtonsContainer extends Component {
if (nextState.isFollowingThread !== isFollowingThread) { if (nextState.isFollowingThread !== isFollowingThread) {
return true; return true;
} }
if (!isEqual(nextState.tunread, tunread)) { if (!dequal(nextState.tunread, tunread)) {
return true; return true;
} }
if (!isEqual(nextState.tunreadUser, tunreadUser)) { if (!dequal(nextState.tunreadUser, tunreadUser)) {
return true; return true;
} }
if (!isEqual(nextState.tunreadGroup, tunreadGroup)) { if (!dequal(nextState.tunreadGroup, tunreadGroup)) {
return true; return true;
} }
return false;
} }
componentWillUnmount() { componentWillUnmount() {

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import equal from 'deep-equal'; import { dequal } from 'dequal';
import Header from './Header'; import Header from './Header';
import LeftButtons from './LeftButtons'; import LeftButtons from './LeftButtons';
@ -65,7 +65,7 @@ class RoomHeaderView extends Component {
if (nextProps.height !== height) { if (nextProps.height !== height) {
return true; return true;
} }
if (!equal(nextProps.usersTyping, usersTyping)) { if (!dequal(nextProps.usersTyping, usersTyping)) {
return true; return true;
} }
if (nextProps.goRoomActionsView !== goRoomActionsView) { if (nextProps.goRoomActionsView !== goRoomActionsView) {

Some files were not shown because too many files have changed in this diff Show More