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
/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" ./ShareRocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./NotificationService/Info.plist
else
/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" ./ShareRocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist
fi
if [[ $APP_STORE_CONNECT_API_KEY ]]; then
@ -416,9 +420,13 @@ workflows:
- lint-testunit
# iOS Experimental
- ios-build-experimental:
- ios-hold-build-experimental:
type: approval
requires:
- lint-testunit
- ios-build-experimental:
requires:
- ios-hold-build-experimental
- ios-hold-testflight-experimental:
type: approval
requires:
@ -444,9 +452,13 @@ workflows:
- ios-hold-testflight-official
# Android Experimental
- android-build-experimental:
- android-hold-build-experimental:
type: approval
requires:
- lint-testunit
- android-build-experimental:
requires:
- android-hold-build-experimental
- android-hold-google-play-beta-experimental:
type: approval
requires:

View File

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

View File

@ -44404,6 +44404,309 @@ exports[`Storyshots Message list message 1`] = `
</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
style={
Array [

View File

@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.14.1"
versionName "4.15.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
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 ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
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',
previewBackground: '#1F2329',
previewTintColor: '#ffffff',
backdropOpacity: 0.3,
...mentions
},
dark: {
@ -109,6 +110,7 @@ export const themes = {
passcodeDotFull: '#6C727A',
previewBackground: '#030b1b',
previewTintColor: '#ffffff',
backdropOpacity: 0.9,
...mentions
},
black: {
@ -155,6 +157,7 @@ export const themes = {
passcodeDotFull: '#6C727A',
previewBackground: '#000000',
previewTintColor: '#ffffff',
backdropOpacity: 0.9,
...mentions
}
};

View File

@ -1,6 +1,6 @@
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 FDROID_MARKET_LINK = 'https://f-droid.org/en/packages/chat.rocket.android';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,20 +34,24 @@ const MessageActions = React.memo(forwardRef(({
Message_AllowPinning,
Message_AllowStarring,
Message_Read_Receipt_Store_Users,
isMasterDetail
isMasterDetail,
editMessagePermission,
deleteMessagePermission,
forceDeleteMessagePermission,
pinMessagePermission
}, ref) => {
let permissions = {};
const { showActionSheet, hideActionSheet } = useActionSheet();
const getPermissions = async() => {
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);
permissions = {
hasEditPermission: result[permission[0]],
hasDeletePermission: result[permission[1]],
hasForceDeletePermission: result[permission[2]],
hasPinPermission: result[permission[3]]
hasEditPermission: result[0],
hasDeletePermission: result[1],
hasForceDeletePermission: result[2],
hasPinPermission: result[3]
};
} catch {
// Do nothing
@ -141,7 +145,7 @@ const MessageActions = React.memo(forwardRef(({
const db = database.active;
const result = await RocketChat.markAsUnread({ messageId });
if (result.success) {
const subCollection = db.collections.get('subscriptions');
const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid);
await db.action(async() => {
try {
@ -171,7 +175,7 @@ const MessageActions = React.memo(forwardRef(({
const handleCopy = async(message) => {
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') });
};
@ -440,7 +444,11 @@ MessageActions.propTypes = {
Message_AllowPinning: PropTypes.bool,
Message_AllowStarring: 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 => ({
@ -452,7 +460,11 @@ const mapStateToProps = state => ({
Message_AllowPinning: state.settings.Message_AllowPinning,
Message_AllowStarring: state.settings.Message_AllowStarring,
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);

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import moment from 'moment';
import { connect } from 'react-redux';
import isEqual from 'lodash/isEqual';
import Markdown from '../markdown';
import { CustomIcon } from '../../lib/Icons';
@ -43,7 +42,7 @@ const styles = StyleSheet.create({
});
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) {
return null;
@ -59,7 +58,7 @@ const ReplyPreview = React.memo(({
>
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
<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>
</View>
<Markdown
@ -75,7 +74,7 @@ const ReplyPreview = React.memo(({
<CustomIcon name='close' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} />
</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 = {
replying: PropTypes.bool,
@ -85,12 +84,14 @@ ReplyPreview.propTypes = {
baseUrl: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
getCustomEmoji: PropTypes.func,
theme: PropTypes.string
theme: PropTypes.string,
useRealName: PropTypes.bool
};
const mapStateToProps = state => ({
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);

View File

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

View File

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

View File

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

View File

@ -58,9 +58,9 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }) => {
const showTwoFactor = args => setData(args);
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 = () => {

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import React from 'react';
import isEqual from 'lodash/isEqual';
import { dequal } from 'dequal';
import PropTypes from 'prop-types';
import Image from './Image';
@ -28,7 +28,7 @@ const Attachments = React.memo(({
// eslint-disable-next-line react/no-array-index-key
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.array,

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import React, { useContext } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
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 * as Progress from 'react-native-progress';
@ -66,7 +66,7 @@ const ImageContainer = React.memo(({
<MessageImage img={img} theme={theme} />
</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 = {
file: PropTypes.object,

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { StyleSheet } from 'react-native';
import isEqual from 'deep-equal';
import { dequal } from 'dequal';
import Touchable from './Touchable';
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} />
</>
);
}, (prevProps, nextProps) => isEqual(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme);
}, (prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme);
Video.propTypes = {
file: PropTypes.object,

View File

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

View File

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

View File

@ -704,5 +704,7 @@ export default {
Direct_message: 'Direct message',
Message_Ignored: 'Message ignored. Tap to display it.',
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',
Message_Ignored: 'Mensagem ignorada. Toque para mostrar.',
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_following: 'Нет тредов, за которыми вы следите',
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() => {
UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme);
const [notification, deepLinking] = await Promise.all([initializePushNotifications(), Linking.getInitialURL()]);
const parsedDeepLinkingURL = parseDeepLinking(deepLinking);
store.dispatch(appInitLocalSettings());
// Open app from push notification
const notification = await initializePushNotifications();
if (notification) {
onNotification(notification);
} else if (parsedDeepLinkingURL) {
store.dispatch(deepLinkingOpen(parsedDeepLinkingURL));
} else {
store.dispatch(appInit());
return;
}
// 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) => {

View File

@ -229,9 +229,9 @@ class Encryption {
decryptPendingMessages = async(roomId) => {
const db = database.active;
const messagesCollection = db.collections.get('messages');
const threadsCollection = db.collections.get('threads');
const threadMessagesCollection = db.collections.get('thread_messages');
const messagesCollection = db.get('messages');
const threadsCollection = db.get('threads');
const threadMessagesCollection = db.get('thread_messages');
// e2e status is null or 'pending' and message type is 'e2e'
const whereClause = [
@ -286,7 +286,7 @@ class Encryption {
// after initialize the encryption client
decryptPendingSubscriptions = async() => {
const db = database.active;
const subCollection = db.collections.get('subscriptions');
const subCollection = db.get('subscriptions');
try {
// 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
@ -347,7 +347,7 @@ class Encryption {
const { rid } = subscription;
const db = database.active;
const subCollection = db.collections.get('subscriptions');
const subCollection = db.get('subscriptions');
let subRecord;
try {
@ -400,7 +400,7 @@ class Encryption {
encryptMessage = async(message) => {
const { rid } = message;
const db = database.active;
const subCollection = db.collections.get('subscriptions');
const subCollection = db.get('subscriptions');
try {
// Find the subscription

View File

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

View File

@ -2,7 +2,7 @@ import reduxStore from '../createStore';
import Navigation from '../Navigation';
import { logEvent, events } from '../../utils/log';
async function jitsiURL({ rid }) {
async function jitsiURL({ room }) {
const { settings } = reduxStore.getState();
const { Jitsi_Enabled } = settings;
@ -11,31 +11,37 @@ async function jitsiURL({ rid }) {
}
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;
const domain = `${ Jitsi_Domain }/`;
const prefix = Jitsi_URL_Room_Prefix;
const uniqueIdentifier = uniqueID || 'undefined';
const protocol = Jitsi_SSL ? 'https://' : 'http://';
let queryString = '';
if (Jitsi_Enabled_TokenAuth) {
try {
const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', rid);
const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', room?.rid);
queryString = `?jwt=${ accessToken }`;
} catch {
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);
const url = await jitsiURL.call(this, { rid });
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid });
const url = await jitsiURL.call(this, { room });
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid: room?.rid });
}
export default callJitsi;

View File

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

View File

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

View File

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

View File

@ -1,12 +1,55 @@
import { InteractionManager } from 'react-native';
import lt from 'semver/functions/lt';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb';
import coerce from 'semver/functions/coerce';
import orderBy from 'lodash/orderBy';
import database from '../database';
import log from '../../utils/log';
import reduxStore from '../createStore';
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) => {
try {
@ -26,7 +69,7 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => {
return;
}
const db = database.active;
const permissionsCollection = db.collections.get('permissions');
const permissionsCollection = db.get('permissions');
// filter permissions
let permissionsToCreate = [];
@ -65,33 +108,35 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => {
await db.action(async() => {
await db.batch(...batch);
});
return true;
} catch (e) {
log(e);
}
};
export default function() {
export function getPermissions() {
return new Promise(async(resolve) => {
try {
const serverVersion = reduxStore.getState().server.version;
const db = database.active;
const permissionsCollection = db.collections.get('permissions');
const permissionsCollection = db.get('permissions');
const allRecords = await permissionsCollection.query().fetch();
// 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
const result = await this.sdk.get('permissions.list');
if (!result.success) {
return resolve();
}
InteractionManager.runAfterInteractions(async() => {
await updatePermissions({ update: result.permissions, allRecords });
const changePermissions = await updatePermissions({ update: result.permissions, allRecords });
if (changePermissions) {
setPermissions();
}
return resolve();
});
} else {
const params = {};
const updatedSince = await getUpdatedSince(allRecords);
const updatedSince = getUpdatedSince(allRecords);
if (updatedSince) {
params.updatedSince = updatedSince;
}
@ -102,10 +147,11 @@ export default function() {
return resolve();
}
InteractionManager.runAfterInteractions(async() => {
await updatePermissions({ update: result.update, remove: result.delete, allRecords });
const changePermissions = await updatePermissions({ update: result.update, remove: result.delete, allRecords });
if (changePermissions) {
setPermissions();
}
return resolve();
});
}
} catch (e) {
log(e);

View File

@ -1,4 +1,3 @@
import { InteractionManager } from 'react-native';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import database from '../database';
@ -19,9 +18,8 @@ export default function() {
const { roles } = result;
if (roles && roles.length) {
InteractionManager.runAfterInteractions(async() => {
await db.action(async() => {
const rolesCollections = db.collections.get('roles');
const rolesCollections = db.get('roles');
const allRolesRecords = await rolesCollections.query().fetch();
// filter roles
@ -55,7 +53,6 @@ export default function() {
return allRecords.length;
});
return resolve();
});
}
} catch (e) {
log(e);

View File

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

View File

@ -1,4 +1,3 @@
import { InteractionManager } from 'react-native';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import database from '../database';
@ -20,9 +19,8 @@ export default function() {
const { commands } = result;
if (commands && commands.length) {
InteractionManager.runAfterInteractions(async() => {
await db.action(async() => {
const slashCommandsCollection = db.collections.get('slash_commands');
const slashCommandsCollection = db.get('slash_commands');
const allSlashCommandsRecords = await slashCommandsCollection.query().fetch();
// filter slash commands
@ -61,7 +59,6 @@ export default function() {
}
return allRecords.length;
});
});
}
} catch (e) {
log(e);

View File

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

View File

@ -5,7 +5,7 @@ import database from '../../database';
export default async(subscriptions = [], rooms = []) => {
try {
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);
let existingSubs = await subCollection.query(Q.where('rid', Q.oneOf(roomIds))).fetch();

View File

@ -1,11 +1,14 @@
import EJSON from 'ejson';
import { lt, coerce } from 'semver';
import normalizeMessage from './normalizeMessage';
import findSubscriptionsRooms from './findSubscriptionsRooms';
import { Encryption } from '../../encryption';
import reduxStore from '../../createStore';
// TODO: delete and update
export const merge = (subscription, room) => {
const serverVersion = reduxStore.getState().server.version;
subscription = EJSON.fromJSONValue(subscription);
room = EJSON.fromJSONValue(room);
@ -25,9 +28,15 @@ export const merge = (subscription, room) => {
subscription.usernames = room.usernames;
subscription.uids = room.uids;
}
if (serverVersion && lt(coerce(serverVersion), '3.7.0')) {
const updatedAt = room?._updatedAt ? new Date(room._updatedAt) : null;
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.broadcast = room.broadcast;
subscription.encrypted = room.encrypted;

View File

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

View File

@ -1,4 +1,3 @@
import { InteractionManager } from 'react-native';
import { Q } from '@nozbe/watermelondb';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
@ -30,12 +29,11 @@ export default function loadThreadMessages({ tmid, rid, offset = 0 }) {
let data = await load.call(this, { tmid, offset });
if (data && data.length) {
InteractionManager.runAfterInteractions(async() => {
try {
data = data.map(m => buildMessage(m));
data = await Encryption.decryptMessages(data);
const db = database.active;
const threadMessagesCollection = db.collections.get('thread_messages');
const threadMessagesCollection = db.get('thread_messages');
const allThreadMessagesRecords = await threadMessagesCollection.query(Q.where('rid', tmid)).fetch();
let threadMessagesToCreate = data.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id));
let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => data.find(i2 => i1.id === i2._id));
@ -67,7 +65,6 @@ export default function loadThreadMessages({ tmid, rid, offset = 0 }) {
log(e);
}
return resolve(data);
});
} else {
return resolve([]);
}

View File

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

View File

@ -4,7 +4,7 @@ import log from '../../utils/log';
export default async function readMessages(rid, ls, updateLastOpen = false) {
try {
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
await this.sdk.post('subscriptions.read', { rid });

View File

@ -40,7 +40,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
fileInfo.rid = rid;
const db = database.active;
const uploadsCollection = db.collections.get('uploads');
const uploadsCollection = db.get('uploads');
let uploadRecord;
try {
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 db = database.active;
const msgCollection = db.collections.get('messages');
const threadMessagesCollection = db.collections.get('thread_messages');
const msgCollection = db.get('messages');
const threadMessagesCollection = db.get('thread_messages');
const successBatch = [];
const messageRecord = await msgCollection.find(id);
successBatch.push(
@ -89,10 +89,10 @@ export async function resendMessage(message, tmid) {
export default async function(rid, msg, tmid, user, tshow) {
try {
const db = database.active;
const subsCollection = db.collections.get('subscriptions');
const msgCollection = db.collections.get('messages');
const threadCollection = db.collections.get('threads');
const threadMessagesCollection = db.collections.get('thread_messages');
const subsCollection = db.get('subscriptions');
const msgCollection = db.get('messages');
const threadCollection = db.get('threads');
const threadMessagesCollection = db.get('thread_messages');
const messageId = random(17);
const batch = [];
@ -151,7 +151,8 @@ export default async function(rid, msg, tmid, user, tshow) {
tm.status = messagesStatus.TEMP;
tm.u = {
_id: user.id || '1',
username: user.username
username: user.username,
name: user.name
};
tm.t = message.t;
if (message.t === E2E_MESSAGE_TYPE) {
@ -175,7 +176,8 @@ export default async function(rid, msg, tmid, user, tshow) {
m.status = messagesStatus.TEMP;
m.u = {
_id: user.id || '1',
username: user.username
username: user.username,
name: user.name
};
if (tmid && tMessageRecord) {
m.tmid = tmid;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ import inviteLinks from './inviteLinks';
import createDiscussion from './createDiscussion';
import enterpriseModules from './enterpriseModules';
import encryption from './encryption';
import permissions from './permissions';
import inquiry from '../ee/omnichannel/reducers/inquiry';
@ -41,5 +42,6 @@ export default combineReducers({
createDiscussion,
inquiry,
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,
encrypted
} = data;
logEvent(events.CREATE_CHANNEL_CREATE, {
logEvent(events.CR_CREATE, {
type: type ? 'private' : 'public',
readOnly,
broadcast,
@ -53,7 +53,7 @@ const handleRequest = function* handleRequest({ data }) {
try {
const db = database.active;
const subCollection = db.collections.get('subscriptions');
const subCollection = db.get('subscriptions');
yield db.action(async() => {
await subCollection.create((s) => {
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema);
@ -66,7 +66,7 @@ const handleRequest = function* handleRequest({ data }) {
yield put(createChannelSuccess(sub));
} 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));
}
};

View File

@ -14,7 +14,7 @@ const create = function* create(data) {
};
const handleRequest = function* handleRequest({ data }) {
logEvent(events.CREATE_DISCUSSION_CREATE);
logEvent(events.CD_CREATE);
try {
const auth = yield select(state => state.login.isAuthenticated);
if (!auth) {
@ -27,7 +27,7 @@ const handleRequest = function* handleRequest({ data }) {
try {
const db = database.active;
const subCollection = db.collections.get('subscriptions');
const subCollection = db.get('subscriptions');
yield db.action(async() => {
await subCollection.create((s) => {
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema);
@ -39,11 +39,11 @@ const handleRequest = function* handleRequest({ data }) {
}
yield put(createDiscussionSuccess(sub));
} else {
logEvent(events.CREATE_DISCUSSION_CREATE_F);
logEvent(events.CD_CREATE_F);
yield put(createDiscussionFailure(result));
}
} catch (err) {
logEvent(events.CREATE_DISCUSSION_CREATE_F);
logEvent(events.CD_CREATE_F);
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 }) {
yield put(appStart({ root: ROOT_INSIDE }));
if (params.path) {
@ -38,22 +46,31 @@ const navigate = function* navigate({ params }) {
if (type !== 'invite') {
const room = yield RocketChat.canOpenRoom(params);
if (room) {
const isMasterDetail = yield select(state => state.app.isMasterDetail);
if (isMasterDetail) {
Navigation.navigate('DrawerNavigator');
} else {
Navigation.navigate('RoomsListView');
}
const item = {
name,
t: roomTypes[type],
roomUserId: RocketChat.getUidDirectMessage(room),
...room
};
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) {
RocketChat.callJitsi(item.rid);
RocketChat.callJitsi(item);
}
}
} else {
@ -72,7 +89,7 @@ const fallbackNavigation = function* fallbackNavigation() {
const handleOpen = function* handleOpen({ params }) {
const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers');
const serversCollection = serversDB.get('servers');
let { host } = params;
if (params.isCall && !host) {
@ -121,10 +138,10 @@ const handleOpen = function* handleOpen({ params }) {
} else {
// search if deep link's server already exists
try {
const servers = yield serversCollection.find(host);
if (servers && user) {
const hostServerRecord = yield serversCollection.find(host);
if (hostServerRecord && user) {
yield localAuthenticate(host);
yield put(selectServerRequest(host));
yield put(selectServerRequest(host, hostServerRecord.version, true, true));
yield take(types.LOGIN.SUCCESS);
yield navigate({ params });
return;

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
try {
const db = database.active;
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 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 serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers');
const serversCollection = serversDB.get('servers');
try {
const serverRecord = yield serversCollection.find(server);
@ -39,7 +39,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
if (params.allData) {
yield put(roomsRefresh());
} else {
const serversCollection = serversDB.collections.get('servers');
const serversCollection = serversDB.get('servers');
try {
const serverRecord = yield serversCollection.find(server);
({ roomsUpdatedAt } = serverRecord);
@ -51,8 +51,8 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
const { subscriptions } = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
const db = database.active;
const subCollection = db.collections.get('subscriptions');
const messagesCollection = db.collections.get('messages');
const subCollection = db.get('subscriptions');
const messagesCollection = db.get('messages');
const subsIds = subscriptions.map(sub => sub.rid).concat(roomsResult.remove.map(room => room._id));
if (subsIds.length) {

View File

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

View File

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

View File

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

View File

@ -1,13 +1,11 @@
import RocketChat from '../lib/rocketchat';
import reduxStore from '../lib/createStore';
const canPost = async({ rid }) => {
try {
const permission = await RocketChat.hasPermission(['post-readonly'], rid);
return permission && permission['post-readonly'];
} catch {
// do nothing
}
return false;
const canPostReadOnly = async({ rid }) => {
// TODO: this is not reactive. If this permission changes, the component won't be updated
const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly'];
const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid);
return permission[0];
};
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;
}
if (room?.ro) {
const allowPost = await canPost(room);
const allowPost = await canPostReadOnly(room);
if (allowPost) {
return false;
}

View File

@ -17,7 +17,7 @@ import { setLocalAuthenticated } from '../actions/login';
export const saveLastLocalAuthenticationSession = async(server, serverRecord) => {
const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers');
const serversCollection = serversDB.get('servers');
await serversDB.action(async() => {
try {
if (!serverRecord) {
@ -91,7 +91,7 @@ export const checkHasPasscode = async({ force = true, serverRecord }) => {
export const localAuthenticate = async(server) => {
const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers');
const serversCollection = serversDB.get('servers');
let serverRecord;
try {
@ -102,9 +102,6 @@ export const localAuthenticate = async(server) => {
// if screen lock is enabled
if (serverRecord?.autoLock) {
// set isLocalAuthenticated to false
store.dispatch(setLocalAuthenticated(false));
// Make sure splash screen has been hidden
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 (diffToLastSession >= serverRecord?.autoLockTime) {
// set isLocalAuthenticated to false
store.dispatch(setLocalAuthenticated(false));
let hasBiometry = false;
// 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',
// NEW SERVER VIEW
NEWSERVER_CONNECT_TO_WORKSPACE: 'newserver_connect_to_workspace',
NEWSERVER_CONNECT_TO_WORKSPACE_F: 'newserver_connect_to_workspace_f',
NEWSERVER_JOIN_OPEN_WORKSPACE: 'newserver_join_open_workspace',
NS_CONNECT_TO_WORKSPACE: 'ns_connect_to_workspace',
NS_JOIN_OPEN_WORKSPACE: 'ns_join_open_workspace',
// LOGIN VIEW
LOGIN_DEFAULT_LOGIN: 'login_default_login',
@ -99,20 +98,20 @@ export default {
SELECTED_USERS_CREATE_GROUP_F: 'selected_users_create_group_f',
// CREATE CHANNEL VIEW
CREATE_CHANNEL_CREATE: 'create_channel_create',
CREATE_CHANNEL_CREATE_F: 'create_channel_create_f',
CREATE_CHANNEL_TOGGLE_TYPE: 'create_channel_toggle_type',
CREATE_CHANNEL_TOGGLE_READ_ONLY: 'create_channel_toggle_read_only',
CREATE_CHANNEL_TOGGLE_BROADCAST: 'create_channel_toggle_broadcast',
CREATE_CHANNEL_TOGGLE_ENCRYPTED: 'create_channel_toggle_encrypted',
CREATE_CHANNEL_REMOVE_USER: 'create_channel_remove_user',
CR_CREATE: 'cr_create',
CR_CREATE_F: 'cr_create_f',
CR_TOGGLE_TYPE: 'cr_toggle_type',
CR_TOGGLE_READ_ONLY: 'cr_toggle_read_only',
CR_TOGGLE_BROADCAST: 'cr_toggle_broadcast',
CR_TOGGLE_ENCRYPTED: 'cr_toggle_encrypted',
CR_REMOVE_USER: 'cr_remove_user',
// CREATE DISCUSSION VIEW
CREATE_DISCUSSION_CREATE: 'create_discussion_create',
CREATE_DISCUSSION_CREATE_F: 'create_discussion_create_f',
CREATE_DISCUSSION_SELECT_CHANNEL: 'create_discussion_select_channel',
CREATE_DISCUSSION_SELECT_USERS: 'create_discussion_select_users',
CREATE_DISCUSSION_TOGGLE_ENCRY: 'create_discussion_toggle_encry',
CD_CREATE: 'cd_create',
CD_CREATE_F: 'cd_create_f',
CD_SELECT_CHANNEL: 'cd_select_channel',
CD_SELECT_USERS: 'cd_select_users',
CD_TOGGLE_ENCRY: 'cd_toggle_encry',
// PROFILE VIEW
PROFILE_PICK_AVATAR: 'profile_pick_avatar',
@ -122,8 +121,9 @@ export default {
PROFILE_SAVE_AVATAR_F: 'profile_save_avatar_f',
PROFILE_SAVE_CHANGES: 'profile_save_changes',
PROFILE_SAVE_CHANGES_F: 'profile_save_changes_f',
PROFILE_LOGOUT_OTHER_LOCATIONS: 'profile_logout_other_locations',
PROFILE_LOGOUT_OTHER_LOCATIONS_F: 'profile_logout_other_locations_f',
// PROFILE LOGOUT
PL_OTHER_LOCATIONS: 'pl_other_locations',
PL_OTHER_LOCATIONS_F: 'pl_other_locations_f',
// SETTINGS VIEW
SE_CONTACT_US: 'se_contact_us',
@ -297,8 +297,6 @@ export default {
NP_AUDIONOTIFICATIONS_F: 'np_audio_notifications_f',
NP_AUDIONOTIFICATIONVALUE: 'np_audio_notification_value',
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_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 documentDir = `${ RNFetchBlob.fs.dirs.DocumentDir }/`;
const path = `${ documentDir + SHA256(url) + extension }`;
const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment).then(res => res.path());
await CameraRoll.save(file, { album: 'Rocket.Chat' });
const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment);
await CameraRoll.save(path, { album: 'Rocket.Chat' });
await file.flush();
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
} catch (e) {
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) {
Orientation.lockToPortrait();
}
EventEmitter.addEventListener(CHANGE_PASSCODE_EMITTER, showChangePasscode);
const listener = EventEmitter.addEventListener(CHANGE_PASSCODE_EMITTER, showChangePasscode);
return (() => {
if (!isTablet) {
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 {
View, Text, Switch, ScrollView, StyleSheet, FlatList
} from 'react-native';
import equal from 'deep-equal';
import { dequal } from 'dequal';
import * as List from '../containers/List';
import TextInput from '../presentation/TextInput';
import Loading from '../containers/Loading';
@ -31,12 +32,6 @@ const styles = StyleSheet.create({
list: {
width: '100%'
},
separator: {
marginLeft: 60
},
formSeparator: {
marginLeft: 15
},
input: {
height: 54,
paddingHorizontal: 18,
@ -133,7 +128,7 @@ class CreateChannelView extends React.Component {
if (nextProps.encryptionEnabled !== encryptionEnabled) {
return true;
}
if (!equal(nextProps.users, users)) {
if (!dequal(nextProps.users, users)) {
return true;
}
return false;
@ -177,7 +172,7 @@ class CreateChannelView extends React.Component {
}
removeUser = (user) => {
logEvent(events.CREATE_CHANNEL_REMOVE_USER);
logEvent(events.CR_REMOVE_USER);
const { removeUser } = this.props;
removeUser(user);
}
@ -207,7 +202,7 @@ class CreateChannelView extends React.Component {
value: type,
label: 'Private_Channel',
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
this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted }));
}
@ -221,7 +216,7 @@ class CreateChannelView extends React.Component {
value: readOnly,
label: 'Read_Only_Channel',
onValueChange: (value) => {
logEvent(events.CREATE_CHANNEL_TOGGLE_READ_ONLY);
logEvent(events.CR_TOGGLE_READ_ONLY);
this.setState({ readOnly: value });
},
disabled: broadcast
@ -241,7 +236,7 @@ class CreateChannelView extends React.Component {
value: encrypted,
label: 'Encrypted',
onValueChange: (value) => {
logEvent(events.CREATE_CHANNEL_TOGGLE_ENCRYPTED);
logEvent(events.CR_TOGGLE_ENCRYPTED);
this.setState({ encrypted: value });
},
disabled: !type
@ -255,7 +250,7 @@ class CreateChannelView extends React.Component {
value: broadcast,
label: 'Broadcast_Channel',
onValueChange: (value) => {
logEvent(events.CREATE_CHANNEL_TOGGLE_BROADCAST);
logEvent(events.CR_TOGGLE_BROADCAST);
this.setState({
broadcast: value,
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 }) => {
const { baseUrl, user, theme } = this.props;
@ -305,7 +293,7 @@ class CreateChannelView extends React.Component {
}
]}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}
ItemSeparatorComponent={List.Separator}
enableEmptySections
keyboardShouldPersistTaps='always'
/>
@ -341,13 +329,13 @@ class CreateChannelView extends React.Component {
theme={theme}
underlineColorAndroid='transparent'
/>
{this.renderFormSeparator()}
<List.Separator />
{this.renderType()}
{this.renderFormSeparator()}
<List.Separator />
{this.renderReadOnly()}
{this.renderFormSeparator()}
<List.Separator />
{this.renderEncrypted()}
{this.renderFormSeparator()}
<List.Separator />
{this.renderBroadcast()}
</View>
<View style={styles.invitedHeader}>

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import {
View, FlatList, Text
} from 'react-native';
import { connect } from 'react-redux';
import * as List from '../../containers/List';
import Touch from '../../utils/touch';
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 }) => {
const { data, type } = this.state;
const { baseUrl, user, theme } = this.props;
@ -251,7 +247,7 @@ class DirectoryView extends React.Component {
keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}
ItemSeparatorComponent={List.Separator}
keyboardShouldPersistTaps='always'
ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null}
onEndReached={() => this.load({})}

View File

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

View File

@ -4,7 +4,7 @@ import {
Text, View, StyleSheet, Keyboard, Alert
} from 'react-native';
import { connect } from 'react-redux';
import equal from 'deep-equal';
import { dequal } from 'dequal';
import sharedStyles from './Styles';
import Button from '../containers/Button';
@ -82,7 +82,7 @@ class LoginView extends React.Component {
UNSAFE_componentWillReceiveProps(nextProps) {
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'));
}
}

View File

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

View File

@ -1,7 +1,6 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import { connect } from 'react-redux';
import { KeyboardAwareScrollView } from '@codler/react-native-keyboard-aware-scroll-view';
@ -94,17 +93,6 @@ class ModalBlockView extends React.Component {
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) {
const { navigation, route } = this.props;
const oldData = prevProps.route.params?.data ?? {};

View File

@ -4,9 +4,8 @@ import {
View, StyleSheet, FlatList, Text
} from 'react-native';
import { connect } from 'react-redux';
import equal from 'deep-equal';
import orderBy from 'lodash/orderBy';
import { Q } from '@nozbe/watermelondb';
import * as List from '../containers/List';
import Touch from '../utils/touch';
import database from '../lib/database';
@ -27,10 +26,9 @@ import { createChannelRequest } from '../actions/createChannel';
import { goRoom } from '../utils/goRoom';
import SafeAreaView from '../containers/SafeAreaView';
const QUERY_SIZE = 50;
const styles = StyleSheet.create({
separator: {
marginLeft: 60
},
button: {
height: 46,
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
init = async() => {
try {
const db = database.active;
const observable = await db.collections
const chats = await db.collections
.get('subscriptions')
.query(Q.where('t', 'd'))
.observeWithColumns(['room_updated_at']);
.query(
Q.where('t', 'd'),
Q.experimentalTake(QUERY_SIZE),
Q.experimentalSortBy('room_updated_at', Q.desc)
)
.fetch();
this.querySubscription = observable.subscribe((data) => {
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']);
this.setState({ chats });
});
} catch (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 }) => {
const { search, chats } = this.state;
@ -254,7 +228,7 @@ class NewMessageView extends React.Component {
keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator}
ItemSeparatorComponent={List.Separator}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, View } from 'react-native';
import { FlatList } from 'react-native';
import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb';
import * as List from '../../containers/List';
import styles from './styles';
import UserItem from '../../presentation/UserItem';
@ -28,6 +29,12 @@ import { goRoom } from '../../utils/goRoom';
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 {
static propTypes = {
navigation: PropTypes.object,
@ -43,7 +50,12 @@ class RoomMembersView extends React.Component {
showActionSheet: PropTypes.func,
theme: PropTypes.string,
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) {
@ -81,7 +93,20 @@ class RoomMembersView extends React.Component {
this.fetchMembers();
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);
if (hasSinglePermission) {
@ -122,7 +147,7 @@ class RoomMembersView extends React.Component {
navToDirectMessage = async(item) => {
try {
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();
if (query.length) {
const [room] = query;
@ -395,11 +420,6 @@ class RoomMembersView extends React.Component {
<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 }) => {
const { baseUrl, user, theme } = this.props;
@ -429,7 +449,7 @@ class RoomMembersView extends React.Component {
renderItem={this.renderItem}
style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]}
keyExtractor={item => item._id}
ItemSeparatorComponent={this.renderSeparator}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={this.renderSearchBar}
ListFooterComponent={() => {
if (isLoading) {
@ -452,7 +472,12 @@ const mapStateToProps = state => ({
baseUrl: state.server.server,
user: getUserSelector(state),
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)));

View File

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

View File

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

View File

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

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