Compare commits
18 Commits
develop
...
fix.link-p
Author | SHA1 | Date |
---|---|---|
Reinaldo Neto | 87f88c5b52 | |
Reinaldo Neto | 251223a0c9 | |
Reinaldo Neto | 0c14716815 | |
Reinaldo Neto | 249a056b7d | |
Reinaldo Neto | 34c2c14b03 | |
Reinaldo Neto | 75ca62c3c4 | |
Reinaldo Neto | 6a7da14fdd | |
Reinaldo Neto | d1c288a702 | |
Reinaldo Neto | 986acfd0fc | |
Reinaldo Neto | 2c1a412fce | |
Reinaldo Neto | ae0a218a7a | |
Reinaldo Neto | f891eea9b9 | |
Reinaldo Neto | 8843b4aa6a | |
Reinaldo Neto | f829807092 | |
Reinaldo Neto | 257489d85c | |
Reinaldo Neto | b3e028ce24 | |
Reinaldo Neto | 87d0b6173c | |
Reinaldo Neto | c7d2f306c5 |
|
@ -1,9 +1,6 @@
|
||||||
defaults: &defaults
|
defaults: &defaults
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
|
|
||||||
orbs:
|
|
||||||
android: circleci/android@2.1.2
|
|
||||||
|
|
||||||
macos: &macos
|
macos: &macos
|
||||||
macos:
|
macos:
|
||||||
xcode: "14.2.0"
|
xcode: "14.2.0"
|
||||||
|
@ -330,14 +327,6 @@ commands:
|
||||||
working_directory: ios
|
working_directory: ios
|
||||||
- save_cache: *save-gems-cache
|
- save_cache: *save-gems-cache
|
||||||
|
|
||||||
create-e2e-account-file:
|
|
||||||
description: "Create e2e account file"
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
command: |
|
|
||||||
echo $E2E_ACCOUNT | base64 --decode > ./e2e_account.ts
|
|
||||||
working_directory: e2e
|
|
||||||
|
|
||||||
version: 2.1
|
version: 2.1
|
||||||
|
|
||||||
# EXECUTORS
|
# EXECUTORS
|
||||||
|
@ -449,94 +438,6 @@ jobs:
|
||||||
- upload-to-google-play-beta:
|
- upload-to-google-play-beta:
|
||||||
official: true
|
official: true
|
||||||
|
|
||||||
e2e-build-android:
|
|
||||||
<<: *defaults
|
|
||||||
executor:
|
|
||||||
name: android/android-machine
|
|
||||||
resource-class: xlarge
|
|
||||||
tag: 2022.12.1
|
|
||||||
environment:
|
|
||||||
<<: *android-env
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- restore_cache: *restore-npm-cache-linux
|
|
||||||
- run: *install-npm-modules
|
|
||||||
- save_cache: *save-npm-cache-linux
|
|
||||||
- restore_cache: *restore-gradle-cache
|
|
||||||
- run:
|
|
||||||
name: Configure Gradle
|
|
||||||
command: |
|
|
||||||
echo -e "" > ./gradle.properties
|
|
||||||
# echo -e "android.enableAapt2=false" >> ./gradle.properties
|
|
||||||
echo -e "android.useAndroidX=true" >> ./gradle.properties
|
|
||||||
echo -e "android.enableJetifier=true" >> ./gradle.properties
|
|
||||||
echo -e "newArchEnabled=false" >> ./gradle.properties
|
|
||||||
echo -e "FLIPPER_VERSION=0.125.0" >> ./gradle.properties
|
|
||||||
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
|
|
||||||
echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
|
|
||||||
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
|
|
||||||
echo $KEYSTORE_EXPERIMENTAL_BASE64 | base64 --decode > ./app/$KEYSTORE_EXPERIMENTAL
|
|
||||||
echo -e "KEYSTORE=$KEYSTORE_EXPERIMENTAL" >> ./gradle.properties
|
|
||||||
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
|
|
||||||
echo -e "KEY_ALIAS=$KEYSTORE_EXPERIMENTAL_ALIAS" >> ./gradle.properties
|
|
||||||
echo -e "KEY_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
|
|
||||||
working_directory: android
|
|
||||||
- run:
|
|
||||||
name: Build Android
|
|
||||||
command: |
|
|
||||||
echo "RUNNING_E2E_TESTS=true" > ./.env
|
|
||||||
yarn e2e:android-build
|
|
||||||
- save_cache: *save-gradle-cache
|
|
||||||
- store_artifacts:
|
|
||||||
path: android/app/build/outputs/apk/experimentalPlay/release/app-experimental-play-release.apk
|
|
||||||
- store_artifacts:
|
|
||||||
path: android/app/build/outputs/apk/androidTest/experimentalPlay/release/app-experimental-play-release-androidTest.apk
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: /home/circleci/repo
|
|
||||||
paths:
|
|
||||||
- android/app/build/outputs/apk/
|
|
||||||
|
|
||||||
e2e-test-android:
|
|
||||||
<<: *defaults
|
|
||||||
executor:
|
|
||||||
name: android/android-machine
|
|
||||||
resource-class: xlarge
|
|
||||||
tag: 2022.12.1
|
|
||||||
parallelism: 4
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- attach_workspace:
|
|
||||||
at: /home/circleci/repo
|
|
||||||
- restore_cache: *restore-npm-cache-linux
|
|
||||||
- run: *install-npm-modules
|
|
||||||
- save_cache: *save-npm-cache-linux
|
|
||||||
- run: mkdir ~/junit
|
|
||||||
- create-e2e-account-file
|
|
||||||
- android/create-avd:
|
|
||||||
avd-name: Pixel_API_31_AOSP
|
|
||||||
install: true
|
|
||||||
system-image: system-images;android-31;default;x86_64
|
|
||||||
- run:
|
|
||||||
name: Setup emulator
|
|
||||||
command: |
|
|
||||||
echo "hw.lcd.density = 440" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
|
|
||||||
echo "hw.lcd.height = 2280" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
|
|
||||||
echo "hw.lcd.width = 1080" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
|
|
||||||
- run:
|
|
||||||
name: Run Detox Tests
|
|
||||||
command: |
|
|
||||||
TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
|
|
||||||
yarn e2e:android-test $TEST
|
|
||||||
- store_artifacts:
|
|
||||||
path: artifacts
|
|
||||||
- run:
|
|
||||||
command: cp junit.xml ~/junit/
|
|
||||||
when: always
|
|
||||||
- store_test_results:
|
|
||||||
path: ~/junit
|
|
||||||
- store_artifacts:
|
|
||||||
path: ~/junit
|
|
||||||
|
|
||||||
# iOS builds
|
# iOS builds
|
||||||
ios-build-experimental:
|
ios-build-experimental:
|
||||||
executor: mac-env
|
executor: mac-env
|
||||||
|
@ -560,89 +461,11 @@ jobs:
|
||||||
- upload-to-testflight:
|
- upload-to-testflight:
|
||||||
official: true
|
official: true
|
||||||
|
|
||||||
e2e-build-ios:
|
|
||||||
executor: mac-env
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- restore_cache: *restore-gems-cache
|
|
||||||
- restore_cache: *restore-npm-cache-mac
|
|
||||||
- run: *install-npm-modules
|
|
||||||
- run: *update-fastlane-ios
|
|
||||||
- save_cache: *save-npm-cache-mac
|
|
||||||
- save_cache: *save-gems-cache
|
|
||||||
- manage-pods
|
|
||||||
- run:
|
|
||||||
name: Configure Detox
|
|
||||||
command: |
|
|
||||||
brew tap wix/brew
|
|
||||||
brew install applesimutils
|
|
||||||
- run:
|
|
||||||
name: Build
|
|
||||||
command: |
|
|
||||||
/usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/RocketChatRN/Info.plist
|
|
||||||
/usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/ShareRocketChatRN/Info.plist
|
|
||||||
yarn detox clean-framework-cache && yarn detox build-framework-cache
|
|
||||||
echo "RUNNING_E2E_TESTS=true" > ./.env
|
|
||||||
yarn e2e:ios-build
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: /Users/distiller/project
|
|
||||||
paths:
|
|
||||||
- ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app
|
|
||||||
|
|
||||||
e2e-test-ios:
|
|
||||||
executor: mac-env
|
|
||||||
parallelism: 5
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- attach_workspace:
|
|
||||||
at: /Users/distiller/project
|
|
||||||
- restore_cache: *restore-npm-cache-mac
|
|
||||||
- run: *install-npm-modules
|
|
||||||
- save_cache: *save-npm-cache-mac
|
|
||||||
- run: mkdir ~/junit
|
|
||||||
- run:
|
|
||||||
name: Configure Detox
|
|
||||||
command: |
|
|
||||||
brew tap wix/brew
|
|
||||||
brew install applesimutils
|
|
||||||
yarn detox clean-framework-cache && yarn detox build-framework-cache
|
|
||||||
- create-e2e-account-file
|
|
||||||
- run:
|
|
||||||
name: Run tests
|
|
||||||
command: |
|
|
||||||
TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
|
|
||||||
yarn e2e:ios-test $TEST
|
|
||||||
- store_artifacts:
|
|
||||||
path: artifacts
|
|
||||||
- run:
|
|
||||||
command: cp junit.xml ~/junit/
|
|
||||||
when: always
|
|
||||||
- store_test_results:
|
|
||||||
path: ~/junit
|
|
||||||
- store_artifacts:
|
|
||||||
path: ~/junit
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
jobs:
|
jobs:
|
||||||
- lint-testunit
|
- lint-testunit
|
||||||
|
|
||||||
# E2E tests
|
|
||||||
- e2e-hold:
|
|
||||||
type: approval
|
|
||||||
- e2e-build-ios:
|
|
||||||
requires:
|
|
||||||
- e2e-hold
|
|
||||||
- e2e-test-ios:
|
|
||||||
requires:
|
|
||||||
- e2e-build-ios
|
|
||||||
- e2e-build-android:
|
|
||||||
requires:
|
|
||||||
- e2e-hold
|
|
||||||
- e2e-test-android:
|
|
||||||
requires:
|
|
||||||
- e2e-build-android
|
|
||||||
|
|
||||||
# iOS Experimental
|
# iOS Experimental
|
||||||
- ios-hold-build-experimental:
|
- ios-hold-build-experimental:
|
||||||
type: approval
|
type: approval
|
||||||
|
|
91
.detoxrc.js
91
.detoxrc.js
|
@ -1,91 +0,0 @@
|
||||||
/** @type {Detox.DetoxConfig} */
|
|
||||||
module.exports = {
|
|
||||||
testRunner: {
|
|
||||||
args: {
|
|
||||||
$0: 'jest',
|
|
||||||
config: 'e2e/jest.config.js'
|
|
||||||
},
|
|
||||||
retries: process.env.CI ? 3 : 0
|
|
||||||
},
|
|
||||||
artifacts: {
|
|
||||||
plugins: {
|
|
||||||
screenshot: 'failing',
|
|
||||||
video: 'failing',
|
|
||||||
uiHierarchy: 'enabled'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
apps: {
|
|
||||||
'ios.debug': {
|
|
||||||
type: 'ios.app',
|
|
||||||
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/Rocket.Chat Experimental.app',
|
|
||||||
build:
|
|
||||||
'xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
|
|
||||||
},
|
|
||||||
'ios.release': {
|
|
||||||
type: 'ios.app',
|
|
||||||
binaryPath: 'ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app',
|
|
||||||
build:
|
|
||||||
'xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -sdk iphonesimulator -derivedDataPath ios/build'
|
|
||||||
},
|
|
||||||
'android.debug': {
|
|
||||||
type: 'android.apk',
|
|
||||||
binaryPath: 'android/app/build/outputs/apk/experimentalPlay/debug/app-experimental-play-debug.apk',
|
|
||||||
build:
|
|
||||||
'cd android ; ./gradlew assembleExperimentalPlayDebug assembleExperimentalPlayDebugAndroidTest -DtestBuildType=debug ; cd -',
|
|
||||||
reversePorts: [8081]
|
|
||||||
},
|
|
||||||
'android.release': {
|
|
||||||
type: 'android.apk',
|
|
||||||
binaryPath: 'android/app/build/outputs/apk/experimentalPlay/release/app-experimental-play-release.apk',
|
|
||||||
build:
|
|
||||||
'cd android ; ./gradlew assembleExperimentalPlayRelease assembleExperimentalPlayReleaseAndroidTest -DtestBuildType=release ; cd -'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
devices: {
|
|
||||||
simulator: {
|
|
||||||
type: 'ios.simulator',
|
|
||||||
device: {
|
|
||||||
type: 'iPhone 14'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
attached: {
|
|
||||||
type: 'android.attached',
|
|
||||||
device: {
|
|
||||||
adbName: '.*'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emulator: {
|
|
||||||
type: 'android.emulator',
|
|
||||||
device: {
|
|
||||||
avdName: 'Pixel_API_31_AOSP'
|
|
||||||
},
|
|
||||||
headless: process.env.CI ? true : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
configurations: {
|
|
||||||
'ios.sim.debug': {
|
|
||||||
device: 'simulator',
|
|
||||||
app: 'ios.debug'
|
|
||||||
},
|
|
||||||
'ios.sim.release': {
|
|
||||||
device: 'simulator',
|
|
||||||
app: 'ios.release'
|
|
||||||
},
|
|
||||||
'android.att.debug': {
|
|
||||||
device: 'attached',
|
|
||||||
app: 'android.debug'
|
|
||||||
},
|
|
||||||
'android.att.release': {
|
|
||||||
device: 'attached',
|
|
||||||
app: 'android.release'
|
|
||||||
},
|
|
||||||
'android.emu.debug': {
|
|
||||||
device: 'emulator',
|
|
||||||
app: 'android.debug'
|
|
||||||
},
|
|
||||||
'android.emu.release': {
|
|
||||||
device: 'emulator',
|
|
||||||
app: 'android.release'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
13
.eslintrc.js
13
.eslintrc.js
|
@ -240,8 +240,19 @@ module.exports = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['e2e/**'],
|
files: ['e2e/**'],
|
||||||
|
globals: {
|
||||||
|
by: true,
|
||||||
|
detox: true,
|
||||||
|
device: true,
|
||||||
|
element: true,
|
||||||
|
waitFor: true
|
||||||
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-await-in-loop': 0
|
'import/no-extraneous-dependencies': 0,
|
||||||
|
'no-await-in-loop': 0,
|
||||||
|
'no-restricted-syntax': 0,
|
||||||
|
// TODO: remove this rule when update Detox to 20 and test if the namespace Detox is available
|
||||||
|
'no-undef': 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -67,6 +67,5 @@ e2e/docker/rc_test_env/docker-compose.yml
|
||||||
e2e/docker/data/db
|
e2e/docker/data/db
|
||||||
e2e/e2e_account.js
|
e2e/e2e_account.js
|
||||||
e2e/e2e_account.ts
|
e2e/e2e_account.ts
|
||||||
junit.xml
|
|
||||||
|
|
||||||
*.p8
|
*.p8
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "4.37.0"
|
versionName "4.36.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
if (!isFoss) {
|
if (!isFoss) {
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
|
@ -250,7 +250,6 @@ android {
|
||||||
release {
|
release {
|
||||||
minifyEnabled enableProguardInReleaseBuilds
|
minifyEnabled enableProguardInReleaseBuilds
|
||||||
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
|
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
|
||||||
proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
|
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
if (!isFoss) {
|
if (!isFoss) {
|
||||||
firebaseCrashlytics {
|
firebaseCrashlytics {
|
||||||
|
@ -269,11 +268,6 @@ android {
|
||||||
// pickFirst '**/x86_64/libc++_shared.so'
|
// pickFirst '**/x86_64/libc++_shared.so'
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// FIXME: Remove when we update RN
|
|
||||||
packagingOptions {
|
|
||||||
pickFirst '**/*.so'
|
|
||||||
}
|
|
||||||
|
|
||||||
// applicationVariants are e.g. debug, release
|
// applicationVariants are e.g. debug, release
|
||||||
|
|
||||||
flavorDimensions "app", "type"
|
flavorDimensions "app", "type"
|
||||||
|
@ -286,6 +280,10 @@ android {
|
||||||
dimension = "app"
|
dimension = "app"
|
||||||
buildConfigField "boolean", "IS_OFFICIAL", "false"
|
buildConfigField "boolean", "IS_OFFICIAL", "false"
|
||||||
}
|
}
|
||||||
|
e2e {
|
||||||
|
dimension = "app"
|
||||||
|
buildConfigField "boolean", "IS_OFFICIAL", "false"
|
||||||
|
}
|
||||||
foss {
|
foss {
|
||||||
dimension = "type"
|
dimension = "type"
|
||||||
buildConfigField "boolean", "FDROID_BUILD", "true"
|
buildConfigField "boolean", "FDROID_BUILD", "true"
|
||||||
|
@ -313,6 +311,16 @@ android {
|
||||||
java.srcDirs = ['src/main/java', 'src/play/java']
|
java.srcDirs = ['src/main/java', 'src/play/java']
|
||||||
manifest.srcFile 'src/play/AndroidManifest.xml'
|
manifest.srcFile 'src/play/AndroidManifest.xml'
|
||||||
}
|
}
|
||||||
|
e2ePlayDebug {
|
||||||
|
java.srcDirs = ['src/main/java', 'src/play/java']
|
||||||
|
res.srcDirs = ['src/experimental/res']
|
||||||
|
manifest.srcFile 'src/play/AndroidManifest.xml'
|
||||||
|
}
|
||||||
|
e2ePlayRelease {
|
||||||
|
java.srcDirs = ['src/main/java', 'src/play/java']
|
||||||
|
res.srcDirs = ['src/experimental/res']
|
||||||
|
manifest.srcFile 'src/play/AndroidManifest.xml'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
|
@ -377,9 +385,8 @@ dependencies {
|
||||||
implementation "com.github.bumptech.glide:glide:4.9.0"
|
implementation "com.github.bumptech.glide:glide:4.9.0"
|
||||||
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
|
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
|
||||||
implementation "com.tencent:mmkv-static:1.2.10"
|
implementation "com.tencent:mmkv-static:1.2.10"
|
||||||
androidTestImplementation('com.wix:detox:+')
|
androidTestImplementation('com.wix:detox:+') { transitive = true }
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
androidTestImplementation 'junit:junit:4.12'
|
||||||
implementation 'com.facebook.soloader:soloader:0.10.4'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNewArchitectureEnabled()) {
|
if (isNewArchitectureEnabled()) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ public class DetoxTest {
|
||||||
@Rule
|
@Rule
|
||||||
// Replace 'MainActivity' with the value of android:name entry in
|
// Replace 'MainActivity' with the value of android:name entry in
|
||||||
// <activity> in AndroidManifest.xml
|
// <activity> in AndroidManifest.xml
|
||||||
public ActivityTestRule<chat.rocket.reactnative.MainActivity> mActivityRule = new ActivityTestRule<>(chat.rocket.reactnative.MainActivity.class, false, false);
|
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runDetoxTests() {
|
public void runDetoxTests() {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<base-config cleartextTrafficPermitted="true">
|
||||||
|
<trust-anchors>
|
||||||
|
<certificates src="system" />
|
||||||
|
<certificates src="user"
|
||||||
|
tools:ignore="AcceptsUserCertificates" />
|
||||||
|
</trust-anchors>
|
||||||
|
</base-config>
|
||||||
|
</network-security-config>
|
|
@ -12,8 +12,14 @@
|
||||||
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
|
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
|
||||||
|
|
||||||
<!-- permissions related to jitsi call -->
|
<!-- permissions related to jitsi call -->
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" android:usesPermissionFlags="neverForLocation" tools:targetApi="s" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28"/>
|
||||||
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" tools:targetApi="Q"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="chat.rocket.reactnative.MainApplication"
|
android:name="chat.rocket.reactnative.MainApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
|
Binary file not shown.
|
@ -7,8 +7,4 @@
|
||||||
tools:ignore="AcceptsUserCertificates" />
|
tools:ignore="AcceptsUserCertificates" />
|
||||||
</trust-anchors>
|
</trust-anchors>
|
||||||
</base-config>
|
</base-config>
|
||||||
<domain-config cleartextTrafficPermitted="true">
|
|
||||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
|
||||||
<domain includeSubdomains="true">localhost</domain>
|
|
||||||
</domain-config>
|
|
||||||
</network-security-config>
|
</network-security-config>
|
|
@ -1,5 +1,9 @@
|
||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
import org.apache.tools.ant.taskdefs.condition.Os
|
||||||
|
|
||||||
|
def safeExtGet(prop, fallback) {
|
||||||
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||||
|
}
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase()
|
def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase()
|
||||||
|
@ -71,38 +75,5 @@ allprojects {
|
||||||
google()
|
google()
|
||||||
maven { url 'https://maven.google.com' }
|
maven { url 'https://maven.google.com' }
|
||||||
maven { url 'https://www.jitpack.io' }
|
maven { url 'https://www.jitpack.io' }
|
||||||
|
|
||||||
// https://stackoverflow.com/a/74333788/5447468
|
|
||||||
// TODO: remove once we update RN
|
|
||||||
exclusiveContent {
|
|
||||||
// We get React Native's Android binaries exclusively through npm,
|
|
||||||
// from a local Maven repo inside node_modules/react-native/.
|
|
||||||
// (The use of exclusiveContent prevents looking elsewhere like Maven Central
|
|
||||||
// and potentially getting a wrong version.)
|
|
||||||
filter {
|
|
||||||
includeGroup "com.facebook.react"
|
|
||||||
}
|
|
||||||
forRepository {
|
|
||||||
maven {
|
|
||||||
// NOTE: if you are in a monorepo, you may have "$rootDir/../../../node_modules/react-native/android"
|
|
||||||
url "$rootDir/../node_modules/react-native/android"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subprojects { subproject ->
|
|
||||||
afterEvaluate {
|
|
||||||
if (!project.name.equalsIgnoreCase("app") && project.hasProperty("android")) {
|
|
||||||
android {
|
|
||||||
compileSdkVersion 31
|
|
||||||
buildToolsVersion "31.0.0"
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion 23
|
|
||||||
targetSdkVersion 31
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -40,14 +40,7 @@ export const INQUIRY = createRequestTypes('INQUIRY', [
|
||||||
'QUEUE_UPDATE',
|
'QUEUE_UPDATE',
|
||||||
'QUEUE_REMOVE'
|
'QUEUE_REMOVE'
|
||||||
]);
|
]);
|
||||||
export const APP = createRequestTypes('APP', [
|
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS', 'SET_MASTER_DETAIL']);
|
||||||
'START',
|
|
||||||
'READY',
|
|
||||||
'INIT',
|
|
||||||
'INIT_LOCAL_SETTINGS',
|
|
||||||
'SET_MASTER_DETAIL',
|
|
||||||
'SET_NOTIFICATION_PRESENCE_CAP'
|
|
||||||
]);
|
|
||||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||||
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);
|
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);
|
||||||
|
|
|
@ -12,11 +12,7 @@ interface ISetMasterDetail extends Action {
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISetNotificationPresenceCap extends Action {
|
export type TActionApp = IAppStart & ISetMasterDetail;
|
||||||
show: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TActionApp = IAppStart & ISetMasterDetail & ISetNotificationPresenceCap;
|
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
root: RootEnum;
|
root: RootEnum;
|
||||||
|
@ -55,10 +51,3 @@ export function setMasterDetail(isMasterDetail: boolean): ISetMasterDetail {
|
||||||
isMasterDetail
|
isMasterDetail
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setNotificationPresenceCap(show: boolean): ISetNotificationPresenceCap {
|
|
||||||
return {
|
|
||||||
type: APP.SET_NOTIFICATION_PRESENCE_CAP,
|
|
||||||
show
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
export const mappedIcons = {
|
export const mappedIcons = {
|
||||||
'status-disabled': 59837,
|
|
||||||
'lamp-bulb': 59836,
|
'lamp-bulb': 59836,
|
||||||
'phone-in': 59835,
|
'phone-in': 59835,
|
||||||
'basketball': 59776,
|
'basketball': 59776,
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,31 +1,34 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useWindowDimensions } from 'react-native';
|
||||||
import { FlatList } from 'react-native-gesture-handler';
|
import { FlatList } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import { IEmoji } from '../../definitions/IEmoji';
|
|
||||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
|
||||||
import { PressableEmoji } from './PressableEmoji';
|
|
||||||
import { EMOJI_BUTTON_SIZE } from './styles';
|
import { EMOJI_BUTTON_SIZE } from './styles';
|
||||||
|
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||||
|
import { IEmoji } from '../../definitions/IEmoji';
|
||||||
|
import { PressableEmoji } from './PressableEmoji';
|
||||||
|
|
||||||
interface IEmojiCategoryProps {
|
interface IEmojiCategoryProps {
|
||||||
emojis: IEmoji[];
|
emojis: IEmoji[];
|
||||||
onEmojiSelected: (emoji: IEmoji) => void;
|
onEmojiSelected: (emoji: IEmoji) => void;
|
||||||
tabLabel?: string; // needed for react-native-scrollable-tab-view only
|
tabLabel?: string; // needed for react-native-scrollable-tab-view only
|
||||||
parentWidth: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmojiCategory = ({ onEmojiSelected, emojis, parentWidth }: IEmojiCategoryProps): React.ReactElement | null => {
|
const EmojiCategory = ({ onEmojiSelected, emojis }: IEmojiCategoryProps): React.ReactElement | null => {
|
||||||
if (!parentWidth) {
|
const { width } = useWindowDimensions();
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const numColumns = Math.trunc(parentWidth / EMOJI_BUTTON_SIZE);
|
const numColumns = Math.trunc(width / EMOJI_BUTTON_SIZE);
|
||||||
const marginHorizontal = (parentWidth % EMOJI_BUTTON_SIZE) / 2;
|
const marginHorizontal = (width % EMOJI_BUTTON_SIZE) / 2;
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={onEmojiSelected} />;
|
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={onEmojiSelected} />;
|
||||||
|
|
||||||
|
if (!width) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
key={`emoji-category-${parentWidth}`}
|
// needed to update the numColumns when the width changes
|
||||||
|
key={`emoji-category-${width}`}
|
||||||
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||||
data={emojis}
|
data={emojis}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||||
|
|
||||||
|
@ -20,8 +20,6 @@ const EmojiPicker = ({
|
||||||
searchedEmojis = []
|
searchedEmojis = []
|
||||||
}: IEmojiPickerProps): React.ReactElement | null => {
|
}: IEmojiPickerProps): React.ReactElement | null => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const [parentWidth, setParentWidth] = useState(0);
|
|
||||||
|
|
||||||
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
|
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
|
||||||
|
|
||||||
const allCustomEmojis: ICustomEmojis = useAppSelector(
|
const allCustomEmojis: ICustomEmojis = useAppSelector(
|
||||||
|
@ -52,14 +50,7 @@ const EmojiPicker = ({
|
||||||
if (!emojis.length) {
|
if (!emojis.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return <EmojiCategory emojis={emojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} tabLabel={label} />;
|
||||||
<EmojiCategory
|
|
||||||
parentWidth={parentWidth}
|
|
||||||
emojis={emojis}
|
|
||||||
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
|
|
||||||
tabLabel={label}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
|
@ -67,13 +58,9 @@ const EmojiPicker = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.emojiPickerContainer} onLayout={e => setParentWidth(e.nativeEvent.layout.width)}>
|
<View style={styles.emojiPickerContainer}>
|
||||||
{searching ? (
|
{searching ? (
|
||||||
<EmojiCategory
|
<EmojiCategory emojis={searchedEmojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} />
|
||||||
emojis={searchedEmojis}
|
|
||||||
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
|
|
||||||
parentWidth={parentWidth}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<ScrollableTabView
|
<ScrollableTabView
|
||||||
renderTabBar={() => <TabBar />}
|
renderTabBar={() => <TabBar />}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import { STATUS_COLORS } from '../../lib/constants';
|
|
||||||
import UnreadBadge from '../UnreadBadge';
|
import UnreadBadge from '../UnreadBadge';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -16,8 +15,6 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BadgeUnread = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />;
|
export const Badge = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />;
|
||||||
|
|
||||||
export const BadgeWarn = (): React.ReactElement => (
|
export default Badge;
|
||||||
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: STATUS_COLORS.disabled }]} />
|
|
||||||
);
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
|
||||||
import { Header, HeaderBackground } from '@react-navigation/elements';
|
import { Header, HeaderBackground } from '@react-navigation/elements';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
|
@ -104,10 +103,9 @@ export const Badge = () => (
|
||||||
<HeaderExample
|
<HeaderExample
|
||||||
left={() => (
|
left={() => (
|
||||||
<HeaderButton.Container left>
|
<HeaderButton.Container left>
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} />
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadUser={[1]} />} />
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} tunreadUser={[1]} />} />
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadGroup={[1]} />} />
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} tunreadGroup={[1]} />} />
|
||||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
|
||||||
</HeaderButton.Container>
|
</HeaderButton.Container>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -116,23 +114,20 @@ export const Badge = () => (
|
||||||
|
|
||||||
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
||||||
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
|
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
|
||||||
<View style={{ flexDirection: 'column' }}>
|
<HeaderExample
|
||||||
<HeaderExample
|
left={() => (
|
||||||
left={() => (
|
<HeaderButton.Container left>
|
||||||
<HeaderButton.Container left>
|
<HeaderButton.Item iconName='threads' />
|
||||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
</HeaderButton.Container>
|
||||||
<HeaderButton.Item iconName='threads' />
|
)}
|
||||||
</HeaderButton.Container>
|
right={() => (
|
||||||
)}
|
<HeaderButton.Container>
|
||||||
right={() => (
|
<HeaderButton.Item title='Threads' />
|
||||||
<HeaderButton.Container>
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} />
|
||||||
<HeaderButton.Item title='Threads' />
|
</HeaderButton.Container>
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
|
)}
|
||||||
</HeaderButton.Container>
|
colors={colors[theme]}
|
||||||
)}
|
/>
|
||||||
colors={colors[theme]}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</ThemeContext.Provider>
|
</ThemeContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export { default as Container } from './HeaderButtonContainer';
|
export { default as Container } from './HeaderButtonContainer';
|
||||||
export { default as Item } from './HeaderButtonItem';
|
export { default as Item } from './HeaderButtonItem';
|
||||||
export * from './HeaderButtonItemBadge';
|
export { default as Badge } from './HeaderButtonItemBadge';
|
||||||
export * from './Common';
|
export * from './Common';
|
||||||
|
|
|
@ -1062,7 +1062,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
formatReplyMessage = async (replyingMessage: IMessage, message = '') => {
|
formatReplyMessage = async (replyingMessage: IMessage, message = '') => {
|
||||||
const { user, roomType, replyWithMention, serverVersion } = this.props;
|
const { user, roomType, replyWithMention } = this.props;
|
||||||
const permalink = await this.getPermalink(replyingMessage);
|
const permalink = await this.getPermalink(replyingMessage);
|
||||||
let msg = `[ ](${permalink}) `;
|
let msg = `[ ](${permalink}) `;
|
||||||
|
|
||||||
|
@ -1071,8 +1071,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
msg += `@${replyingMessage?.u?.username} `;
|
msg += `@${replyingMessage?.u?.username} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectionString = compareServerVersion(serverVersion, 'lowerThan', '5.0.0') ? ' ' : '\n';
|
return `${msg} ${message}`;
|
||||||
return `${msg}${connectionString}${message}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMentions = (keyword: any, type: string) => {
|
updateMentions = (keyword: any, type: string) => {
|
||||||
|
|
|
@ -46,7 +46,6 @@ const RoomHeaderContainer = React.memo(
|
||||||
const connecting = useSelector((state: IApplicationState) => state.meteor.connecting || state.server.loading);
|
const connecting = useSelector((state: IApplicationState) => state.meteor.connecting || state.server.loading);
|
||||||
const usersTyping = useSelector((state: IApplicationState) => state.usersTyping, shallowEqual);
|
const usersTyping = useSelector((state: IApplicationState) => state.usersTyping, shallowEqual);
|
||||||
const connected = useSelector((state: IApplicationState) => state.meteor.connected);
|
const connected = useSelector((state: IApplicationState) => state.meteor.connected);
|
||||||
const presenceDisabled = useSelector((state: IApplicationState) => state.settings.Presence_broadcast_disabled);
|
|
||||||
const activeUser = useSelector(
|
const activeUser = useSelector(
|
||||||
(state: IApplicationState) => (roomUserId ? state.activeUsers?.[roomUserId] : undefined),
|
(state: IApplicationState) => (roomUserId ? state.activeUsers?.[roomUserId] : undefined),
|
||||||
shallowEqual
|
shallowEqual
|
||||||
|
@ -62,13 +61,9 @@ const RoomHeaderContainer = React.memo(
|
||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
if ((type === 'd' || (tmid && roomUserId)) && activeUser) {
|
if ((type === 'd' || (tmid && roomUserId)) && activeUser) {
|
||||||
if (presenceDisabled) {
|
const { status: statusActiveUser, statusText: statusTextActiveUser } = activeUser;
|
||||||
status = 'disabled';
|
status = statusActiveUser;
|
||||||
} else {
|
statusText = statusTextActiveUser;
|
||||||
const { status: statusActiveUser, statusText: statusTextActiveUser } = activeUser;
|
|
||||||
status = statusActiveUser;
|
|
||||||
statusText = statusTextActiveUser;
|
|
||||||
}
|
|
||||||
} else if (type === 'l' && visitor?.status) {
|
} else if (type === 'l' && visitor?.status) {
|
||||||
const { status: statusVisitor } = visitor;
|
const { status: statusVisitor } = visitor;
|
||||||
status = statusVisitor;
|
status = statusVisitor;
|
||||||
|
|
|
@ -65,7 +65,6 @@ export const UserStatus = () => (
|
||||||
<RoomItem status='busy' />
|
<RoomItem status='busy' />
|
||||||
<RoomItem status='offline' />
|
<RoomItem status='offline' />
|
||||||
<RoomItem status='loading' />
|
<RoomItem status='loading' />
|
||||||
<RoomItem status='disabled' />
|
|
||||||
<RoomItem status='wrong' />
|
<RoomItem status='wrong' />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,17 +8,11 @@ export const useUserStatus = (
|
||||||
id?: string
|
id?: string
|
||||||
): { connected: boolean; status: TUserStatus } => {
|
): { connected: boolean; status: TUserStatus } => {
|
||||||
const connected = useAppSelector(state => state.meteor.connected);
|
const connected = useAppSelector(state => state.meteor.connected);
|
||||||
const presenceDisabled = useAppSelector(state => state.settings.Presence_broadcast_disabled);
|
|
||||||
const userStatus = useAppSelector(state => state.activeUsers[id || '']?.status);
|
const userStatus = useAppSelector(state => state.activeUsers[id || '']?.status);
|
||||||
|
|
||||||
let status = 'loading';
|
let status = 'loading';
|
||||||
if (connected) {
|
if (connected) {
|
||||||
if (type === 'd') {
|
if (type === 'd') {
|
||||||
if (presenceDisabled) {
|
status = userStatus || 'loading';
|
||||||
status = 'disabled';
|
|
||||||
} else {
|
|
||||||
status = userStatus || 'loading';
|
|
||||||
}
|
|
||||||
} else if (type === 'l' && liveChatStatus) {
|
} else if (type === 'l' && liveChatStatus) {
|
||||||
status = liveChatStatus;
|
status = liveChatStatus;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { StyleProp, ViewStyle } from 'react-native';
|
import { StyleProp, ViewStyle } from 'react-native';
|
||||||
import { SvgUri } from 'react-native-svg';
|
import { SvgUri } from 'react-native-svg';
|
||||||
|
|
||||||
|
@ -29,12 +29,22 @@ interface IOmnichannelRoomIconProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnichannelRoomIconProps) => {
|
export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnichannelRoomIconProps) => {
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [svgError, setSvgError] = useState(false);
|
|
||||||
const baseUrl = useAppSelector(state => state.server?.server);
|
const baseUrl = useAppSelector(state => state.server?.server);
|
||||||
const connected = useAppSelector(state => state.meteor?.connected);
|
const connected = useAppSelector(state => state.meteor?.connected);
|
||||||
|
|
||||||
const customIcon = (
|
if (sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon && connected) {
|
||||||
|
return (
|
||||||
|
<SvgUri
|
||||||
|
height={size}
|
||||||
|
width={size}
|
||||||
|
color={STATUS_COLORS[status || 'offline']}
|
||||||
|
uri={`${baseUrl}/api/apps/public/${sourceType.id}/get-sidebar-icon?icon=${sourceType.sidebarIcon}`}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name={iconMap[sourceType?.type || 'other']}
|
name={iconMap[sourceType?.type || 'other']}
|
||||||
size={size}
|
size={size}
|
||||||
|
@ -42,23 +52,4 @@ export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnich
|
||||||
color={STATUS_COLORS[status || 'offline']}
|
color={STATUS_COLORS[status || 'offline']}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!svgError && sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon && connected) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SvgUri
|
|
||||||
height={size}
|
|
||||||
width={size}
|
|
||||||
color={STATUS_COLORS[status || 'offline']}
|
|
||||||
uri={`${baseUrl}/api/apps/public/${sourceType.id}/get-sidebar-icon?icon=${sourceType.sidebarIcon}`}
|
|
||||||
style={style}
|
|
||||||
onError={() => setSvgError(true)}
|
|
||||||
onLoad={() => setLoading(false)}
|
|
||||||
/>
|
|
||||||
{loading ? customIcon : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return customIcon;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,15 +6,9 @@ import { IStatus } from './definition';
|
||||||
import { useAppSelector } from '../../lib/hooks';
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
|
||||||
const StatusContainer = ({ id, style, size = 32, ...props }: Omit<IStatus, 'status'>): React.ReactElement => {
|
const StatusContainer = ({ id, style, size = 32, ...props }: Omit<IStatus, 'status'>): React.ReactElement => {
|
||||||
const status = useAppSelector(state => {
|
const status = useAppSelector(state =>
|
||||||
if (state.settings.Presence_broadcast_disabled) {
|
state.meteor.connected ? state.activeUsers[id] && state.activeUsers[id].status : 'loading'
|
||||||
return 'disabled';
|
) as TUserStatus;
|
||||||
}
|
|
||||||
if (state.meteor.connected) {
|
|
||||||
return state.activeUsers[id] && state.activeUsers[id].status;
|
|
||||||
}
|
|
||||||
return 'loading';
|
|
||||||
}) as TUserStatus;
|
|
||||||
return <Status size={size} style={style} status={status} {...props} />;
|
return <Status size={size} style={style} status={status} {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import i18n from '../../../../i18n';
|
||||||
import { getSubscriptionByRoomId } from '../../../../lib/database/services/Subscription';
|
import { getSubscriptionByRoomId } from '../../../../lib/database/services/Subscription';
|
||||||
import { useAppSelector } from '../../../../lib/hooks';
|
import { useAppSelector } from '../../../../lib/hooks';
|
||||||
import { getRoomAvatar, getUidDirectMessage } from '../../../../lib/methods/helpers';
|
import { getRoomAvatar, getUidDirectMessage } from '../../../../lib/methods/helpers';
|
||||||
|
import { videoConfStartAndJoin } from '../../../../lib/methods/videoConf';
|
||||||
import { useTheme } from '../../../../theme';
|
import { useTheme } from '../../../../theme';
|
||||||
import { useActionSheet } from '../../../ActionSheet';
|
import { useActionSheet } from '../../../ActionSheet';
|
||||||
import AvatarContainer from '../../../Avatar';
|
import AvatarContainer from '../../../Avatar';
|
||||||
|
@ -15,12 +16,12 @@ import { BUTTON_HIT_SLOP } from '../../../message/utils';
|
||||||
import StatusContainer from '../../../Status';
|
import StatusContainer from '../../../Status';
|
||||||
import useStyle from './styles';
|
import useStyle from './styles';
|
||||||
|
|
||||||
export default function StartACallActionSheet({ rid, initCall }: { rid: string; initCall: Function }): React.ReactElement {
|
export default function CallAgainActionSheet({ rid }: { rid: string }): React.ReactElement {
|
||||||
const style = useStyle();
|
const style = useStyle();
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const [user, setUser] = useState({ username: '', avatar: '', uid: '' });
|
const [user, setUser] = useState({ username: '', avatar: '', uid: '', rid: '' });
|
||||||
const [mic, setMic] = useState(true);
|
const [phone, setPhone] = useState(true);
|
||||||
const [cam, setCam] = useState(false);
|
const [camera, setCamera] = useState(false);
|
||||||
const username = useAppSelector(state => state.login.user.username);
|
const username = useAppSelector(state => state.login.user.username);
|
||||||
|
|
||||||
const { hideActionSheet } = useActionSheet();
|
const { hideActionSheet } = useActionSheet();
|
||||||
|
@ -30,7 +31,7 @@ export default function StartACallActionSheet({ rid, initCall }: { rid: string;
|
||||||
const room = await getSubscriptionByRoomId(rid);
|
const room = await getSubscriptionByRoomId(rid);
|
||||||
const uid = (await getUidDirectMessage(room)) as string;
|
const uid = (await getUidDirectMessage(room)) as string;
|
||||||
const avt = getRoomAvatar(room);
|
const avt = getRoomAvatar(room);
|
||||||
setUser({ uid, username: room?.name || '', avatar: avt });
|
setUser({ uid, username: room?.name || '', avatar: avt, rid: room?.id || '' });
|
||||||
})();
|
})();
|
||||||
}, [rid]);
|
}, [rid]);
|
||||||
|
|
||||||
|
@ -42,27 +43,25 @@ export default function StartACallActionSheet({ rid, initCall }: { rid: string;
|
||||||
<Text style={style.actionSheetHeaderTitle}>{i18n.t('Start_a_call')}</Text>
|
<Text style={style.actionSheetHeaderTitle}>{i18n.t('Start_a_call')}</Text>
|
||||||
<View style={style.actionSheetHeaderButtons}>
|
<View style={style.actionSheetHeaderButtons}>
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => setCam(!cam)}
|
onPress={() => setCamera(!camera)}
|
||||||
style={[style.iconCallContainer, cam && style.enabledBackground, { marginRight: 6 }]}
|
style={[style.iconCallContainer, camera && style.enabledBackground, { marginRight: 6 }]}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
>
|
>
|
||||||
<CustomIcon name={cam ? 'camera' : 'camera-disabled'} size={20} color={handleColor(cam)} />
|
<CustomIcon name={camera ? 'camera' : 'camera-disabled'} size={16} color={handleColor(camera)} />
|
||||||
</Touchable>
|
</Touchable>
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => setMic(!mic)}
|
onPress={() => setPhone(!phone)}
|
||||||
style={[style.iconCallContainer, mic && style.enabledBackground]}
|
style={[style.iconCallContainer, phone && style.enabledBackground]}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
>
|
>
|
||||||
<CustomIcon name={mic ? 'microphone' : 'microphone-disabled'} size={20} color={handleColor(mic)} />
|
<CustomIcon name={phone ? 'microphone' : 'microphone-disabled'} size={16} color={handleColor(phone)} />
|
||||||
</Touchable>
|
</Touchable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={style.actionSheetUsernameContainer}>
|
<View style={style.actionSheetUsernameContainer}>
|
||||||
<AvatarContainer text={user.avatar} size={36} />
|
<AvatarContainer text={user.avatar} size={36} />
|
||||||
<StatusContainer size={16} id={user.uid} style={{ marginLeft: 8, marginRight: 6 }} />
|
<StatusContainer size={16} id={user.uid} style={{ marginLeft: 8, marginRight: 6 }} />
|
||||||
<Text style={style.actionSheetUsername} numberOfLines={1}>
|
<Text style={style.actionSheetUsername}>{user.username}</Text>
|
||||||
{user.username}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={style.actionSheetPhotoContainer}>
|
<View style={style.actionSheetPhotoContainer}>
|
||||||
<AvatarContainer size={62} text={username} />
|
<AvatarContainer size={62} text={username} />
|
||||||
|
@ -71,7 +70,7 @@ export default function StartACallActionSheet({ rid, initCall }: { rid: string;
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
hideActionSheet();
|
hideActionSheet();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initCall({ cam, mic });
|
videoConfStartAndJoin(user.rid, camera);
|
||||||
}, 100);
|
}, 100);
|
||||||
}}
|
}}
|
||||||
title={i18n.t('Call')}
|
title={i18n.t('Call')}
|
|
@ -3,16 +3,17 @@ import { Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import { videoConfJoin } from '../../../../lib/methods/videoConf';
|
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
||||||
import useStyle from './styles';
|
import useStyle from './styles';
|
||||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||||
|
|
||||||
const VideoConferenceDirect = React.memo(({ blockId }: { blockId: string }) => {
|
const VideoConferenceDirect = React.memo(({ blockId }: { blockId: string }) => {
|
||||||
const style = useStyle();
|
const style = useStyle();
|
||||||
|
const { joinCall } = useVideoConf();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VideoConferenceBaseContainer variant='incoming'>
|
<VideoConferenceBaseContainer variant='incoming'>
|
||||||
<Touchable style={style.callToActionButton} onPress={() => videoConfJoin(blockId)}>
|
<Touchable style={style.callToActionButton} onPress={() => joinCall(blockId)}>
|
||||||
<Text style={style.callToActionButtonText}>{i18n.t('Join')}</Text>
|
<Text style={style.callToActionButtonText}>{i18n.t('Join')}</Text>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
<Text style={style.callBack}>{i18n.t('Waiting_for_answer')}</Text>
|
<Text style={style.callBack}>{i18n.t('Waiting_for_answer')}</Text>
|
||||||
|
|
|
@ -6,7 +6,9 @@ import { IUser } from '../../../../definitions';
|
||||||
import { VideoConferenceType } from '../../../../definitions/IVideoConference';
|
import { VideoConferenceType } from '../../../../definitions/IVideoConference';
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import { useAppSelector } from '../../../../lib/hooks';
|
import { useAppSelector } from '../../../../lib/hooks';
|
||||||
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
import { useSnaps } from '../../../../lib/hooks/useSnaps';
|
||||||
|
import { useActionSheet } from '../../../ActionSheet';
|
||||||
|
import CallAgainActionSheet from './CallAgainActionSheet';
|
||||||
import { CallParticipants, TCallUsers } from './CallParticipants';
|
import { CallParticipants, TCallUsers } from './CallParticipants';
|
||||||
import useStyle from './styles';
|
import useStyle from './styles';
|
||||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||||
|
@ -24,7 +26,8 @@ export default function VideoConferenceEnded({
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const style = useStyle();
|
const style = useStyle();
|
||||||
const username = useAppSelector(state => state.login.user.username);
|
const username = useAppSelector(state => state.login.user.username);
|
||||||
const { showInitCallActionSheet } = useVideoConf(rid);
|
const { showActionSheet } = useActionSheet();
|
||||||
|
const snaps = useSnaps([1250]);
|
||||||
|
|
||||||
const onlyAuthorOnCall = users.length === 1 && users.some(user => user.username === createdBy.username);
|
const onlyAuthorOnCall = users.length === 1 && users.some(user => user.username === createdBy.username);
|
||||||
|
|
||||||
|
@ -32,7 +35,15 @@ export default function VideoConferenceEnded({
|
||||||
<VideoConferenceBaseContainer variant='ended'>
|
<VideoConferenceBaseContainer variant='ended'>
|
||||||
{type === 'direct' ? (
|
{type === 'direct' ? (
|
||||||
<>
|
<>
|
||||||
<Touchable style={style.callToActionCallBack} onPress={showInitCallActionSheet}>
|
<Touchable
|
||||||
|
style={style.callToActionCallBack}
|
||||||
|
onPress={() =>
|
||||||
|
showActionSheet({
|
||||||
|
children: <CallAgainActionSheet rid={rid} />,
|
||||||
|
snaps
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
<Text style={style.callToActionCallBackText}>
|
<Text style={style.callToActionCallBackText}>
|
||||||
{createdBy.username === username ? i18n.t('Call_back') : i18n.t('Call_again')}
|
{createdBy.username === username ? i18n.t('Call_back') : i18n.t('Call_again')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -3,17 +3,18 @@ import { Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import i18n from '../../../../i18n';
|
import i18n from '../../../../i18n';
|
||||||
import { videoConfJoin } from '../../../../lib/methods/videoConf';
|
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
||||||
import { CallParticipants, TCallUsers } from './CallParticipants';
|
import { CallParticipants, TCallUsers } from './CallParticipants';
|
||||||
import useStyle from './styles';
|
import useStyle from './styles';
|
||||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||||
|
|
||||||
export default function VideoConferenceOutgoing({ users, blockId }: { users: TCallUsers; blockId: string }): React.ReactElement {
|
export default function VideoConferenceOutgoing({ users, blockId }: { users: TCallUsers; blockId: string }): React.ReactElement {
|
||||||
const style = useStyle();
|
const style = useStyle();
|
||||||
|
const { joinCall } = useVideoConf();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VideoConferenceBaseContainer variant='outgoing'>
|
<VideoConferenceBaseContainer variant='outgoing'>
|
||||||
<Touchable style={style.callToActionButton} onPress={() => videoConfJoin(blockId)}>
|
<Touchable style={style.callToActionButton} onPress={() => joinCall(blockId)}>
|
||||||
<Text style={style.callToActionButtonText}>{i18n.t('Join')}</Text>
|
<Text style={style.callToActionButtonText}>{i18n.t('Join')}</Text>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
<CallParticipants users={users} />
|
<CallParticipants users={users} />
|
||||||
|
|
|
@ -100,8 +100,7 @@ export default function useStyle() {
|
||||||
actionSheetUsername: {
|
actionSheetUsername: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
...sharedStyles.textBold,
|
...sharedStyles.textBold,
|
||||||
color: colors.passcodePrimary,
|
color: colors.passcodePrimary
|
||||||
flexShrink: 1
|
|
||||||
},
|
},
|
||||||
enabledBackground: {
|
enabledBackground: {
|
||||||
backgroundColor: colors.conferenceCallEnabledIconBackground
|
backgroundColor: colors.conferenceCallEnabledIconBackground
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { BlockContext } from '@rocket.chat/ui-kit';
|
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
|
|
||||||
import { videoConfJoin } from '../../lib/methods/videoConf';
|
import { useVideoConf } from '../../lib/hooks/useVideoConf';
|
||||||
import { IText } from './interfaces';
|
import { IText } from './interfaces';
|
||||||
|
|
||||||
export const textParser = ([{ text }]: IText[]) => text;
|
export const textParser = ([{ text }]: IText[]) => text;
|
||||||
|
@ -40,6 +40,7 @@ export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUse
|
||||||
const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext);
|
const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext);
|
||||||
const { value = initialValue } = values[actionId] || {};
|
const { value = initialValue } = values[actionId] || {};
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { joinCall } = useVideoConf();
|
||||||
|
|
||||||
const error = errors && actionId && errors[actionId];
|
const error = errors && actionId && errors[actionId];
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUse
|
||||||
try {
|
try {
|
||||||
if (appId === 'videoconf-core' && blockId) {
|
if (appId === 'videoconf-core' && blockId) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return videoConfJoin(blockId);
|
return joinCall(blockId);
|
||||||
}
|
}
|
||||||
await action({
|
await action({
|
||||||
blockId,
|
blockId,
|
||||||
|
|
|
@ -16,13 +16,11 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
bottomContainerText: {
|
bottomContainerText: {
|
||||||
...sharedStyles.textRegular,
|
...sharedStyles.textRegular,
|
||||||
fontSize: 13,
|
fontSize: 13
|
||||||
textAlign: 'center'
|
|
||||||
},
|
},
|
||||||
bottomContainerTextBold: {
|
bottomContainerTextBold: {
|
||||||
...sharedStyles.textSemibold,
|
...sharedStyles.textSemibold,
|
||||||
fontSize: 13,
|
fontSize: 13
|
||||||
textAlign: 'center'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -18,29 +18,13 @@ import MarkdownContext from './MarkdownContext';
|
||||||
|
|
||||||
interface IParagraphProps {
|
interface IParagraphProps {
|
||||||
value: ParagraphProps['value'];
|
value: ParagraphProps['value'];
|
||||||
forceTrim?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Inline = ({ value, forceTrim }: IParagraphProps): React.ReactElement | null => {
|
const Inline = ({ value }: IParagraphProps): React.ReactElement | null => {
|
||||||
const { useRealName, username, navToRoomInfo, mentions, channels } = useContext(MarkdownContext);
|
const { useRealName, username, navToRoomInfo, mentions, channels } = useContext(MarkdownContext);
|
||||||
return (
|
return (
|
||||||
<Text style={styles.inline}>
|
<Text style={styles.inline}>
|
||||||
{value.map((block, index) => {
|
{value.map(block => {
|
||||||
// We are forcing trim when is a `[ ](https://https://open.rocket.chat/) plain_text`
|
|
||||||
// to clean the empty spaces
|
|
||||||
if (forceTrim) {
|
|
||||||
if (index === 0 && block.type === 'LINK') {
|
|
||||||
block.value.label.value =
|
|
||||||
// Need to update the @rocket.chat/message-parser to understand that the label can be a Markup | Markup[]
|
|
||||||
// https://github.com/RocketChat/fuselage/blob/461ecf661d9ff4a46390957c915e4352fa942a7c/packages/message-parser/src/definitions.ts#L141
|
|
||||||
// @ts-ignore
|
|
||||||
block.value?.label?.value?.toString().trimLeft() || block?.value?.label?.[0]?.value?.toString().trimLeft();
|
|
||||||
}
|
|
||||||
if (index === 1 && block.type !== 'LINK') {
|
|
||||||
block.value = block.value?.toString().trimLeft();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
case 'IMAGE':
|
case 'IMAGE':
|
||||||
return <Image value={block.value} />;
|
return <Image value={block.value} />;
|
||||||
|
|
|
@ -381,6 +381,27 @@ export const BlockQuote = () => (
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const rocketChatLink = [
|
||||||
|
{
|
||||||
|
type: 'PARAGRAPH',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: 'LINK',
|
||||||
|
value: {
|
||||||
|
src: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'https://rocket.chat'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: 'PLAIN_TEXT',
|
||||||
|
value: 'https://rocket.chat'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const markdownLink = [
|
const markdownLink = [
|
||||||
{
|
{
|
||||||
type: 'PARAGRAPH',
|
type: 'PARAGRAPH',
|
||||||
|
@ -466,6 +487,7 @@ const markdownLinkWithEmphasis = [
|
||||||
|
|
||||||
export const Links = () => (
|
export const Links = () => (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
<NewMarkdown tokens={rocketChatLink} />
|
||||||
<NewMarkdown tokens={markdownLink} />
|
<NewMarkdown tokens={markdownLink} />
|
||||||
<NewMarkdown tokens={markdownLinkWithEmphasis} />
|
<NewMarkdown tokens={markdownLinkWithEmphasis} />
|
||||||
</View>
|
</View>
|
||||||
|
@ -784,128 +806,3 @@ export const InlineKatex = () => (
|
||||||
<NewMarkdown tokens={inlineKatex} />
|
<NewMarkdown tokens={inlineKatex} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageQuote = {
|
|
||||||
/**
|
|
||||||
# Hello head 1
|
|
||||||
[ ](https://google.com)
|
|
||||||
*/
|
|
||||||
headAndLink: [
|
|
||||||
{ type: 'HEADING', level: 1, value: [{ type: 'PLAIN_TEXT', value: 'Hello head 1' }] },
|
|
||||||
{ type: 'LINE_BREAK' },
|
|
||||||
{
|
|
||||||
type: 'PARAGRAPH',
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
type: 'LINK',
|
|
||||||
value: { src: { type: 'PLAIN_TEXT', value: 'https://google.com' }, label: { type: 'PLAIN_TEXT', value: ' ' } }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
/**
|
|
||||||
# Head 1 as the first line then line break and after paragraph
|
|
||||||
bla bla bla bla bla bla
|
|
||||||
bla bla bla bla bla bla
|
|
||||||
[ ](https://google.com)
|
|
||||||
*/
|
|
||||||
headTextAndLink: [
|
|
||||||
{
|
|
||||||
type: 'HEADING',
|
|
||||||
level: 1,
|
|
||||||
value: [{ type: 'PLAIN_TEXT', value: 'Head 1 as the first line then line break and after paragraph' }]
|
|
||||||
},
|
|
||||||
{ type: 'LINE_BREAK' },
|
|
||||||
{ type: 'PARAGRAPH', value: [{ type: 'PLAIN_TEXT', value: 'bla bla bla bla bla bla ' }] },
|
|
||||||
{ type: 'PARAGRAPH', value: [{ type: 'PLAIN_TEXT', value: 'bla bla bla bla bla bla ' }] },
|
|
||||||
{
|
|
||||||
type: 'PARAGRAPH',
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
type: 'LINK',
|
|
||||||
value: { src: { type: 'PLAIN_TEXT', value: 'https://google.com' }, label: { type: 'PLAIN_TEXT', value: ' ' } }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
/**
|
|
||||||
[ ](permalink from message)\n# Head 1 after a forced line break
|
|
||||||
asdas asd asd asd
|
|
||||||
*/
|
|
||||||
headTextAndQuote: [
|
|
||||||
{
|
|
||||||
type: 'PARAGRAPH',
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
type: 'LINK',
|
|
||||||
value: {
|
|
||||||
src: { type: 'PLAIN_TEXT', value: 'https://open.rocket.chat/direct/subaru123?msg=QB42gWcaO6BgqtLTo' },
|
|
||||||
label: { type: 'PLAIN_TEXT', value: ' ' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ type: 'PLAIN_TEXT', value: ' ' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ type: 'HEADING', level: 1, value: [{ type: 'PLAIN_TEXT', value: 'Head 1 after a forced line break' }] },
|
|
||||||
{ type: 'LINE_BREAK' },
|
|
||||||
{ type: 'PARAGRAPH', value: [{ type: 'PLAIN_TEXT', value: 'Description' }] }
|
|
||||||
],
|
|
||||||
/**
|
|
||||||
[ ](https://google.com) *There is a link before this bold separated by single space*
|
|
||||||
*/
|
|
||||||
linkAndBoldText: [
|
|
||||||
{
|
|
||||||
type: 'PARAGRAPH',
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
type: 'LINK',
|
|
||||||
value: { src: { type: 'PLAIN_TEXT', value: 'https://google.com' }, label: { type: 'PLAIN_TEXT', value: ' ' } }
|
|
||||||
},
|
|
||||||
{ type: 'PLAIN_TEXT', value: ' ' },
|
|
||||||
{ type: 'BOLD', value: [{ type: 'PLAIN_TEXT', value: 'There is a link before this bold separated by single space' }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
simpleQuote: [
|
|
||||||
{
|
|
||||||
type: 'PARAGRAPH',
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
type: 'LINK',
|
|
||||||
value: {
|
|
||||||
src: {
|
|
||||||
type: 'PLAIN_TEXT',
|
|
||||||
value: 'https://open.rocket.chat/group/quoteeee9798789?msg=ZZp6t2dCRX4TqExht'
|
|
||||||
},
|
|
||||||
// format of label for servers greater or equal than 6.0
|
|
||||||
label: [
|
|
||||||
{
|
|
||||||
type: 'PLAIN_TEXT',
|
|
||||||
value: ' '
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'PARAGRAPH',
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
type: 'PLAIN_TEXT',
|
|
||||||
value: 'Quoting a message wrote before'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MessageQuote = () => (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<NewMarkdown tokens={messageQuote.headAndLink} />
|
|
||||||
<NewMarkdown tokens={messageQuote.headTextAndLink} />
|
|
||||||
<NewMarkdown tokens={messageQuote.headTextAndQuote} />
|
|
||||||
<NewMarkdown tokens={messageQuote.linkAndBoldText} />
|
|
||||||
<NewMarkdown tokens={messageQuote.simpleQuote} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
|
@ -12,28 +12,10 @@ interface IParagraphProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Paragraph = ({ value }: IParagraphProps) => {
|
const Paragraph = ({ value }: IParagraphProps) => {
|
||||||
let forceTrim = false;
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
if (
|
|
||||||
value?.[0]?.type === 'LINK' &&
|
|
||||||
// Need to update the @rocket.chat/message-parser to understand that the label can be a Markup | Markup[]
|
|
||||||
// https://github.com/RocketChat/fuselage/blob/461ecf661d9ff4a46390957c915e4352fa942a7c/packages/message-parser/src/definitions.ts#L141
|
|
||||||
// @ts-ignore
|
|
||||||
(value?.[0]?.value?.label?.value?.toString().trim() === '' || value?.[0]?.value?.label?.[0]?.value?.toString().trim() === '')
|
|
||||||
) {
|
|
||||||
// We are returning null when we receive a message like this: `[ ](https://open.rocket.chat/)\nplain_text`
|
|
||||||
// to avoid render a line empty above the the message
|
|
||||||
if (value.length === 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (value.length === 2 && value?.[1]?.type === 'PLAIN_TEXT' && value?.[1]?.value?.toString().trim() === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
forceTrim = true;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>
|
<Text style={[styles.text, { color: themes[theme].bodyText }]}>
|
||||||
<Inline value={value} forceTrim={forceTrim} />
|
<Inline value={value} />
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface IPlainProps {
|
||||||
value: PlainProps['value'];
|
value: PlainProps['value'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Plain = ({ value }: IPlainProps): React.ReactElement => (
|
const Plain = ({ value }: IPlainProps) => (
|
||||||
<Text accessibilityLabel={value} style={styles.plainText}>
|
<Text accessibilityLabel={value} style={styles.plainText}>
|
||||||
{value}
|
{value}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Tasks as TasksProps } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
import Inline from './Inline';
|
import Inline from './Inline';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
|
import { themes } from '../../../lib/constants';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface ITasksProps {
|
interface ITasksProps {
|
||||||
|
@ -11,15 +12,13 @@ interface ITasksProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TaskList = ({ value = [] }: ITasksProps) => {
|
const TaskList = ({ value = [] }: ITasksProps) => {
|
||||||
const { colors } = useTheme();
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
{value.map(item => (
|
{value.map(item => (
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Text style={[styles.text, { color: colors.bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
|
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
|
||||||
<Text style={[styles.inline, { color: colors.bodyText }]}>
|
<Inline value={item.value} />
|
||||||
<Inline value={item.value} />
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -10,12 +10,12 @@ import { themes } from '../../lib/constants';
|
||||||
import { IMessageCallButton } from './interfaces';
|
import { IMessageCallButton } from './interfaces';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const CallButton = React.memo(({ handleEnterCall }: IMessageCallButton) => {
|
const CallButton = React.memo(({ callJitsi }: IMessageCallButton) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={handleEnterCall}
|
onPress={callJitsi}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
|
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { themes } from '../../../../lib/constants';
|
||||||
import { CustomIcon } from '../../../CustomIcon';
|
import { CustomIcon } from '../../../CustomIcon';
|
||||||
import styles from '../../styles';
|
import styles from '../../styles';
|
||||||
import { useTheme } from '../../../../theme';
|
import { useTheme } from '../../../../theme';
|
||||||
|
|
||||||
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread?: boolean }) => {
|
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread?: boolean }) => {
|
||||||
const { colors } = useTheme();
|
const { theme } = useTheme();
|
||||||
if (isReadReceiptEnabled) {
|
if (isReadReceiptEnabled && !unread && unread !== null) {
|
||||||
return (
|
return <CustomIcon name='check' color={themes[theme].tintColor} size={16} style={styles.rightIcons} />;
|
||||||
<CustomIcon
|
|
||||||
name='check'
|
|
||||||
color={!unread && unread !== null ? colors.tintColor : colors.auxiliaryTintColor}
|
|
||||||
size={16}
|
|
||||||
style={styles.rightIcons}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -53,7 +53,7 @@ const Content = React.memo(
|
||||||
content = (
|
content = (
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={props.msg}
|
msg={props.msg}
|
||||||
md={props.type !== 'e2e' ? props.md : undefined}
|
md={props.md}
|
||||||
getCustomEmoji={props.getCustomEmoji}
|
getCustomEmoji={props.getCustomEmoji}
|
||||||
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
||||||
username={user.username}
|
username={user.username}
|
||||||
|
|
|
@ -634,6 +634,28 @@ export const URL = () => (
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const URLImagePreview = () => (
|
||||||
|
<>
|
||||||
|
<Message
|
||||||
|
urls={[
|
||||||
|
{
|
||||||
|
url: 'https://www.google.com/logos/doodles/2022/seasonal-holidays-2022-6753651837109831.4-law.gif',
|
||||||
|
title: 'Google',
|
||||||
|
description:
|
||||||
|
"Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for."
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Message
|
||||||
|
urls={[
|
||||||
|
{
|
||||||
|
url: 'https://www.google.com/logos/doodles/2022/seasonal-holidays-2022-6753651837109831.4-law.gif'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
export const CustomFields = () => (
|
export const CustomFields = () => (
|
||||||
<>
|
<>
|
||||||
<Message
|
<Message
|
||||||
|
|
|
@ -18,7 +18,7 @@ const MessageAvatar = React.memo(({ isHeader, avatar, author, small, navToRoomIn
|
||||||
style={small ? styles.avatarSmall : styles.avatar}
|
style={small ? styles.avatarSmall : styles.avatar}
|
||||||
text={avatar ? '' : author.username}
|
text={avatar ? '' : author.username}
|
||||||
size={small ? 20 : 36}
|
size={small ? 20 : 36}
|
||||||
borderRadius={4}
|
borderRadius={small ? 2 : 4}
|
||||||
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
|
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
|
|
|
@ -247,8 +247,6 @@ const Reply = React.memo(
|
||||||
>
|
>
|
||||||
<View style={styles.attachmentContainer}>
|
<View style={styles.attachmentContainer}>
|
||||||
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
|
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
|
||||||
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
|
||||||
<UrlImage image={attachment.thumb_url} />
|
|
||||||
<Attachments
|
<Attachments
|
||||||
attachments={attachment.attachments}
|
attachments={attachment.attachments}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
@ -257,6 +255,8 @@ const Reply = React.memo(
|
||||||
isReply
|
isReply
|
||||||
id={messageId}
|
id={messageId}
|
||||||
/>
|
/>
|
||||||
|
<UrlImage image={attachment.thumb_url} />
|
||||||
|
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||||
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<View style={[styles.backdrop]}>
|
<View style={[styles.backdrop]}>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import Clipboard from '@react-native-clipboard/clipboard';
|
import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
@ -48,23 +48,16 @@ const styles = StyleSheet.create({
|
||||||
height: 150,
|
height: 150,
|
||||||
borderTopLeftRadius: 4,
|
borderTopLeftRadius: 4,
|
||||||
borderTopRightRadius: 4
|
borderTopRightRadius: 4
|
||||||
|
},
|
||||||
|
imageWithoutContent: {
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
height: 0,
|
||||||
|
borderWidth: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const UrlImage = React.memo(
|
|
||||||
({ image }: { image: string }) => {
|
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
|
|
||||||
if (!image) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
|
|
||||||
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
|
||||||
},
|
|
||||||
(prevProps, nextProps) => prevProps.image === nextProps.image
|
|
||||||
);
|
|
||||||
|
|
||||||
const UrlContent = React.memo(
|
const UrlContent = React.memo(
|
||||||
({ title, description, theme }: { title: string; description: string; theme: TSupportedThemes }) => (
|
({ title, description, theme }: { title: string; description: string; theme: TSupportedThemes }) => (
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
|
@ -94,9 +87,14 @@ const UrlContent = React.memo(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
type TImageLoadedState = 'loading' | 'done' | 'error';
|
||||||
|
|
||||||
const Url = React.memo(
|
const Url = React.memo(
|
||||||
({ url, index, theme }: { url: IUrl; index: number; theme: TSupportedThemes }) => {
|
({ url, index, theme }: { url: IUrl; index: number; theme: TSupportedThemes }) => {
|
||||||
if (!url || url?.ignoreParse) {
|
const [imageLoadedState, setImageLoadedState] = useState<TImageLoadedState>('loading');
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
|
if (!url || url?.ignoreParse || imageLoadedState === 'error') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +105,13 @@ const Url = React.memo(
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasContent = url.title || url.description;
|
||||||
|
|
||||||
|
let image = url.image || url.url;
|
||||||
|
if (image) {
|
||||||
|
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
@ -118,13 +123,22 @@ const Url = React.memo(
|
||||||
{
|
{
|
||||||
backgroundColor: themes[theme].chatComponentBackground,
|
backgroundColor: themes[theme].chatComponentBackground,
|
||||||
borderColor: themes[theme].borderColor
|
borderColor: themes[theme].borderColor
|
||||||
}
|
},
|
||||||
|
imageLoadedState === 'loading' && styles.loading
|
||||||
]}
|
]}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<UrlImage image={url.image} />
|
{image ? (
|
||||||
<UrlContent title={url.title} description={url.description} theme={theme} />
|
<FastImage
|
||||||
|
source={{ uri: image }}
|
||||||
|
style={[styles.image, !hasContent && styles.imageWithoutContent, imageLoadedState === 'loading' && styles.loading]}
|
||||||
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
|
onError={() => setImageLoadedState('error')}
|
||||||
|
onLoad={() => setImageLoadedState('done')}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{hasContent ? <UrlContent title={url.title} description={url.description} theme={theme} /> : null}
|
||||||
</>
|
</>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
);
|
);
|
||||||
|
@ -146,7 +160,6 @@ const Urls = React.memo(
|
||||||
(oldProps, newProps) => dequal(oldProps.urls, newProps.urls)
|
(oldProps, newProps) => dequal(oldProps.urls, newProps.urls)
|
||||||
);
|
);
|
||||||
|
|
||||||
UrlImage.displayName = 'MessageUrlImage';
|
|
||||||
UrlContent.displayName = 'MessageUrlContent';
|
UrlContent.displayName = 'MessageUrlContent';
|
||||||
Url.displayName = 'MessageUrl';
|
Url.displayName = 'MessageUrl';
|
||||||
Urls.displayName = 'MessageUrls';
|
Urls.displayName = 'MessageUrls';
|
||||||
|
|
|
@ -50,7 +50,7 @@ interface IMessageContainerProps {
|
||||||
showAttachment: (file: IAttachment) => void;
|
showAttachment: (file: IAttachment) => void;
|
||||||
onReactionLongPress?: (item: TAnyMessageModel) => void;
|
onReactionLongPress?: (item: TAnyMessageModel) => void;
|
||||||
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||||
handleEnterCall?: () => void;
|
callJitsi?: () => void;
|
||||||
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
||||||
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
|
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
|
||||||
threadBadgeColor?: string;
|
threadBadgeColor?: string;
|
||||||
|
@ -69,6 +69,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
getCustomEmoji: () => null,
|
getCustomEmoji: () => null,
|
||||||
onLongPress: () => {},
|
onLongPress: () => {},
|
||||||
|
callJitsi: () => {},
|
||||||
blockAction: () => {},
|
blockAction: () => {},
|
||||||
archived: false,
|
archived: false,
|
||||||
broadcast: false,
|
broadcast: false,
|
||||||
|
@ -337,7 +338,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
navToRoomInfo,
|
navToRoomInfo,
|
||||||
getCustomEmoji,
|
getCustomEmoji,
|
||||||
isThreadRoom,
|
isThreadRoom,
|
||||||
handleEnterCall,
|
callJitsi,
|
||||||
blockAction,
|
blockAction,
|
||||||
rid,
|
rid,
|
||||||
threadBadgeColor,
|
threadBadgeColor,
|
||||||
|
@ -455,7 +456,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
showAttachment={showAttachment}
|
showAttachment={showAttachment}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
navToRoomInfo={navToRoomInfo}
|
navToRoomInfo={navToRoomInfo}
|
||||||
handleEnterCall={handleEnterCall}
|
callJitsi={callJitsi}
|
||||||
blockAction={blockAction}
|
blockAction={blockAction}
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
|
|
|
@ -40,7 +40,7 @@ export interface IMessageBroadcast {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageCallButton {
|
export interface IMessageCallButton {
|
||||||
handleEnterCall?: () => void;
|
callJitsi?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageContent {
|
export interface IMessageContent {
|
||||||
|
|
|
@ -4,34 +4,37 @@ import type { IRoom } from './IRoom';
|
||||||
import type { IUser } from './IUser';
|
import type { IUser } from './IUser';
|
||||||
import type { IMessage } from './IMessage';
|
import type { IMessage } from './IMessage';
|
||||||
|
|
||||||
export declare enum VideoConferenceStatus {
|
export enum VideoConferenceStatus {
|
||||||
CALLING = 0,
|
CALLING = 0,
|
||||||
STARTED = 1,
|
STARTED = 1,
|
||||||
EXPIRED = 2,
|
EXPIRED = 2,
|
||||||
ENDED = 3,
|
ENDED = 3,
|
||||||
DECLINED = 4
|
DECLINED = 4
|
||||||
}
|
}
|
||||||
export declare type DirectCallInstructions = {
|
|
||||||
|
export type DirectCallInstructions = {
|
||||||
type: 'direct';
|
type: 'direct';
|
||||||
calleeId: IUser['_id'];
|
callee: IUser['_id'];
|
||||||
callId: string;
|
callId: string;
|
||||||
};
|
};
|
||||||
export declare type ConferenceInstructions = {
|
|
||||||
|
export type ConferenceInstructions = {
|
||||||
type: 'videoconference';
|
type: 'videoconference';
|
||||||
callId: string;
|
callId: string;
|
||||||
rid: IRoom['_id'];
|
rid: IRoom['_id'];
|
||||||
};
|
};
|
||||||
export declare type LivechatInstructions = {
|
|
||||||
|
export type LivechatInstructions = {
|
||||||
type: 'livechat';
|
type: 'livechat';
|
||||||
callId: string;
|
callId: string;
|
||||||
};
|
};
|
||||||
export declare type VideoConferenceType =
|
|
||||||
| DirectCallInstructions['type']
|
export type VideoConferenceType = DirectCallInstructions['type'] | ConferenceInstructions['type'] | LivechatInstructions['type'];
|
||||||
| ConferenceInstructions['type']
|
|
||||||
| LivechatInstructions['type'];
|
|
||||||
export interface IVideoConferenceUser extends Pick<Required<IUser>, '_id' | 'username' | 'name' | 'avatarETag'> {
|
export interface IVideoConferenceUser extends Pick<Required<IUser>, '_id' | 'username' | 'name' | 'avatarETag'> {
|
||||||
ts: Date;
|
ts: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVideoConference extends IRocketChatRecord {
|
export interface IVideoConference extends IRocketChatRecord {
|
||||||
type: VideoConferenceType;
|
type: VideoConferenceType;
|
||||||
rid: string;
|
rid: string;
|
||||||
|
@ -42,68 +45,51 @@ export interface IVideoConference extends IRocketChatRecord {
|
||||||
ended?: IMessage['_id'];
|
ended?: IMessage['_id'];
|
||||||
};
|
};
|
||||||
url?: string;
|
url?: string;
|
||||||
createdBy: Pick<Required<IUser>, '_id' | 'username' | 'name'>;
|
|
||||||
|
createdBy: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
endedBy?: Pick<Required<IUser>, '_id' | 'username' | 'name'>;
|
|
||||||
|
endedBy?: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||||
endedAt?: Date;
|
endedAt?: Date;
|
||||||
|
|
||||||
providerName: string;
|
providerName: string;
|
||||||
providerData?: Record<string, any>;
|
providerData?: Record<string, any>;
|
||||||
|
|
||||||
ringing?: boolean;
|
ringing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDirectVideoConference extends IVideoConference {
|
export interface IDirectVideoConference extends IVideoConference {
|
||||||
type: 'direct';
|
type: 'direct';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGroupVideoConference extends IVideoConference {
|
export interface IGroupVideoConference extends IVideoConference {
|
||||||
type: 'videoconference';
|
type: 'videoconference';
|
||||||
anonymousUsers: number;
|
anonymousUsers: number;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILivechatVideoConference extends IVideoConference {
|
export interface ILivechatVideoConference extends IVideoConference {
|
||||||
type: 'livechat';
|
type: 'livechat';
|
||||||
}
|
}
|
||||||
export declare type VideoConference = IDirectVideoConference | IGroupVideoConference | ILivechatVideoConference;
|
|
||||||
export declare type VideoConferenceInstructions = DirectCallInstructions | ConferenceInstructions | LivechatInstructions;
|
export type VideoConference = IDirectVideoConference | IGroupVideoConference | ILivechatVideoConference;
|
||||||
export declare const isDirectVideoConference: (call: VideoConference | undefined | null) => call is IDirectVideoConference;
|
|
||||||
export declare const isGroupVideoConference: (call: VideoConference | undefined | null) => call is IGroupVideoConference;
|
export type VideoConferenceInstructions = DirectCallInstructions | ConferenceInstructions | LivechatInstructions;
|
||||||
export declare const isLivechatVideoConference: (call: VideoConference | undefined | null) => call is ILivechatVideoConference;
|
|
||||||
declare type GroupVideoConferenceCreateData = Omit<IGroupVideoConference, 'createdBy'> & {
|
export const isDirectVideoConference = (call: VideoConference | undefined | null): call is IDirectVideoConference =>
|
||||||
createdBy: IUser['_id'];
|
call?.type === 'direct';
|
||||||
};
|
|
||||||
declare type DirectVideoConferenceCreateData = Omit<IDirectVideoConference, 'createdBy'> & {
|
export const isGroupVideoConference = (call: VideoConference | undefined | null): call is IGroupVideoConference =>
|
||||||
createdBy: IUser['_id'];
|
call?.type === 'videoconference';
|
||||||
};
|
|
||||||
declare type LivechatVideoConferenceCreateData = Omit<ILivechatVideoConference, 'createdBy'> & {
|
export const isLivechatVideoConference = (call: VideoConference | undefined | null): call is ILivechatVideoConference =>
|
||||||
createdBy: IUser['_id'];
|
call?.type === 'livechat';
|
||||||
};
|
|
||||||
export declare type VideoConferenceCreateData = AtLeast<
|
type GroupVideoConferenceCreateData = Omit<IGroupVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
||||||
|
type DirectVideoConferenceCreateData = Omit<IDirectVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
||||||
|
type LivechatVideoConferenceCreateData = Omit<ILivechatVideoConference, 'createdBy'> & { createdBy: IUser['_id'] };
|
||||||
|
|
||||||
|
export type VideoConferenceCreateData = AtLeast<
|
||||||
DirectVideoConferenceCreateData | GroupVideoConferenceCreateData | LivechatVideoConferenceCreateData,
|
DirectVideoConferenceCreateData | GroupVideoConferenceCreateData | LivechatVideoConferenceCreateData,
|
||||||
'createdBy' | 'type' | 'rid' | 'providerName' | 'providerData'
|
'createdBy' | 'type' | 'rid' | 'providerName' | 'providerData'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type VideoConferenceCapabilities = {
|
|
||||||
mic?: boolean;
|
|
||||||
cam?: boolean;
|
|
||||||
title?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type VideoConfStartProps = { roomId: string; title?: string; allowRinging?: boolean };
|
|
||||||
|
|
||||||
export type VideoConfJoinProps = {
|
|
||||||
callId: string;
|
|
||||||
state?: {
|
|
||||||
mic?: boolean;
|
|
||||||
cam?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type VideoConfCancelProps = {
|
|
||||||
callId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type VideoConfListProps = {
|
|
||||||
roomId: string;
|
|
||||||
count?: number;
|
|
||||||
offset?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type VideoConfInfoProps = { callId: string };
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export const STATUSES = ['offline', 'online', 'away', 'busy', 'disabled'] as const;
|
export const STATUSES = ['offline', 'online', 'away', 'busy'] as const;
|
||||||
|
|
||||||
export type TUserStatus = typeof STATUSES[number];
|
export type TUserStatus = typeof STATUSES[number];
|
||||||
|
|
|
@ -1,45 +1,27 @@
|
||||||
import {
|
import { VideoConference } from '../../IVideoConference';
|
||||||
VideoConfCancelProps,
|
|
||||||
VideoConference,
|
|
||||||
VideoConferenceCapabilities,
|
|
||||||
VideoConferenceInstructions,
|
|
||||||
VideoConfInfoProps,
|
|
||||||
VideoConfJoinProps,
|
|
||||||
VideoConfListProps,
|
|
||||||
VideoConfStartProps
|
|
||||||
} from '../../IVideoConference';
|
|
||||||
import { PaginatedResult } from '../helpers/PaginatedResult';
|
|
||||||
|
|
||||||
export type VideoConferenceEndpoints = {
|
export type VideoConferenceEndpoints = {
|
||||||
'video-conference.start': {
|
|
||||||
POST: (params: VideoConfStartProps) => { data: VideoConferenceInstructions & { providerName: string } };
|
|
||||||
};
|
|
||||||
|
|
||||||
'video-conference.join': {
|
|
||||||
POST: (params: VideoConfJoinProps) => { url: string; providerName: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
'video-conference.cancel': {
|
|
||||||
POST: (params: VideoConfCancelProps) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
'video-conference.info': {
|
|
||||||
GET: (params: VideoConfInfoProps) => VideoConference & { capabilities: VideoConferenceCapabilities };
|
|
||||||
};
|
|
||||||
|
|
||||||
'video-conference.list': {
|
|
||||||
GET: (params: VideoConfListProps) => PaginatedResult<{ data: VideoConference[] }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
'video-conference.capabilities': {
|
|
||||||
GET: () => { providerName: string; capabilities: VideoConferenceCapabilities };
|
|
||||||
};
|
|
||||||
|
|
||||||
'video-conference.providers': {
|
|
||||||
GET: () => { data: { key: string; label: string }[] };
|
|
||||||
};
|
|
||||||
|
|
||||||
'video-conference/jitsi.update-timeout': {
|
'video-conference/jitsi.update-timeout': {
|
||||||
POST: (params: { roomId: string }) => void;
|
POST: (params: { roomId: string }) => void;
|
||||||
};
|
};
|
||||||
|
'video-conference.join': {
|
||||||
|
POST: (params: { callId: string; state: { cam: boolean } }) => { url: string; providerName: string };
|
||||||
|
};
|
||||||
|
'video-conference.start': {
|
||||||
|
POST: (params: { roomId: string }) => { url: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
'video-conference.cancel': {
|
||||||
|
POST: (params: { callId: string }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
'video-conference.info': {
|
||||||
|
GET: (params: { callId: string }) => VideoConference & {
|
||||||
|
capabilities: {
|
||||||
|
mic?: boolean;
|
||||||
|
cam?: boolean;
|
||||||
|
title?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,3 @@ declare module 'react-native-restart';
|
||||||
declare module 'react-native-jitsi-meet';
|
declare module 'react-native-jitsi-meet';
|
||||||
declare module 'rn-root-view';
|
declare module 'rn-root-view';
|
||||||
declare module 'react-native-math-view';
|
declare module 'react-native-math-view';
|
||||||
declare module '@env' {
|
|
||||||
export const RUNNING_E2E_TESTS: string;
|
|
||||||
}
|
|
||||||
|
|
|
@ -876,20 +876,5 @@
|
||||||
"Call": "Call",
|
"Call": "Call",
|
||||||
"Reply_in_direct_message": "Reply in Direct Message",
|
"Reply_in_direct_message": "Reply in Direct Message",
|
||||||
"room_archived": "archived room",
|
"room_archived": "archived room",
|
||||||
"room_unarchived": "unarchived room",
|
"room_unarchived": "unarchived room"
|
||||||
"no-videoconf-provider-app-header": "Conference call not available",
|
|
||||||
"no-videoconf-provider-app-body": "Conference call apps can be installed in the Rocket.Chat marketplace by a workspace admin.",
|
|
||||||
"admin-no-videoconf-provider-app-header": "Conference call not enabled",
|
|
||||||
"admin-no-videoconf-provider-app-body": "Conference call apps are available in the Rocket.Chat marketplace.",
|
|
||||||
"no-active-video-conf-provider-header": "Conference call not enabled",
|
|
||||||
"no-active-video-conf-provider-body": "A workspace admin needs to enable the conference call feature first.",
|
|
||||||
"admin-no-active-video-conf-provider-header": "Conference call not enabled",
|
|
||||||
"admin-no-active-video-conf-provider-body": "Configure conference calls in order to make it available on this workspace.",
|
|
||||||
"video-conf-provider-not-configured-header": "Conference call not enabled",
|
|
||||||
"video-conf-provider-not-configured-body": "A workspace admin needs to enable the conference calls feature first.",
|
|
||||||
"admin-video-conf-provider-not-configured-header": "Conference call not enabled",
|
|
||||||
"admin-video-conf-provider-not-configured-body": "Configure conference calls in order to make it available on this workspace.",
|
|
||||||
"Presence_Cap_Warning_Title": "User status temporarily disabled",
|
|
||||||
"Presence_Cap_Warning_Description": "Active connections have reached the limit for the workspace, thus the service that handles user status is disabled. It can be re-enabled manually in workspace settings.",
|
|
||||||
"Learn_more": "Learn more"
|
|
||||||
}
|
}
|
|
@ -875,7 +875,5 @@
|
||||||
"Call": "Ligar",
|
"Call": "Ligar",
|
||||||
"Reply_in_direct_message": "Responder por mensagem direta",
|
"Reply_in_direct_message": "Responder por mensagem direta",
|
||||||
"room_archived": "{{username}} arquivou a sala",
|
"room_archived": "{{username}} arquivou a sala",
|
||||||
"room_unarchived": "{{username}} desarquivou a sala",
|
"room_unarchived": "{{username}} desarquivou a sala"
|
||||||
"Presence_Cap_Warning_Title": "Status do usuário desabilitado temporariamente",
|
|
||||||
"Presence_Cap_Warning_Description": "O limite de conexões ativas para a workspace foi atingido, por isso o serviço responsável pela presença dos usuários está temporariamente desabilitado. Ele pode ser reabilitado manualmente nas configurações da workspace."
|
|
||||||
}
|
}
|
|
@ -3,8 +3,7 @@ export const STATUS_COLORS: any = {
|
||||||
busy: '#f5455c',
|
busy: '#f5455c',
|
||||||
away: '#ffd21f',
|
away: '#ffd21f',
|
||||||
offline: '#cbced1',
|
offline: '#cbced1',
|
||||||
loading: '#9ea2a8',
|
loading: '#9ea2a8'
|
||||||
disabled: '#F38C39'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SWITCH_TRACK_COLOR = {
|
export const SWITCH_TRACK_COLOR = {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// 🚨🚨 48 settings after login. Pay attention not to reach 50 as that's the limit per request.
|
|
||||||
export const defaultSettings = {
|
export const defaultSettings = {
|
||||||
Accounts_AllowEmailChange: {
|
Accounts_AllowEmailChange: {
|
||||||
type: 'valueAsBoolean'
|
type: 'valueAsBoolean'
|
||||||
|
@ -230,8 +229,5 @@ export const defaultSettings = {
|
||||||
},
|
},
|
||||||
Number_of_users_autocomplete_suggestions: {
|
Number_of_users_autocomplete_suggestions: {
|
||||||
type: 'valueAsNumber'
|
type: 'valueAsNumber'
|
||||||
},
|
|
||||||
Presence_broadcast_disabled: {
|
|
||||||
type: 'valueAsBoolean'
|
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -8,6 +8,5 @@ export * from './localAuthentication';
|
||||||
export * from './localPath';
|
export * from './localPath';
|
||||||
export * from './messagesStatus';
|
export * from './messagesStatus';
|
||||||
export * from './messageTypeLoad';
|
export * from './messageTypeLoad';
|
||||||
export * from './notifications';
|
|
||||||
export * from './defaultSettings';
|
export * from './defaultSettings';
|
||||||
export * from './tablet';
|
export * from './tablet';
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export const NOTIFICATION_PRESENCE_CAP = 'NOTIFICATION_PRESENCE_CAP';
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet';
|
||||||
|
import i18n from '../../i18n';
|
||||||
|
import { videoConfJoin } from '../methods/videoConf';
|
||||||
|
|
||||||
|
export const useVideoConf = (): { joinCall: (blockId: string) => void } => {
|
||||||
|
const { showActionSheet } = useActionSheet();
|
||||||
|
|
||||||
|
const joinCall = useCallback(blockId => {
|
||||||
|
const options: TActionSheetOptionsItem[] = [
|
||||||
|
{
|
||||||
|
title: i18n.t('Video_call'),
|
||||||
|
icon: 'camera',
|
||||||
|
onPress: () => videoConfJoin(blockId, true)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18n.t('Voice_call'),
|
||||||
|
icon: 'microphone',
|
||||||
|
onPress: () => videoConfJoin(blockId, false)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
showActionSheet({ options });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { joinCall };
|
||||||
|
};
|
|
@ -1,113 +0,0 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { Q } from '@nozbe/watermelondb';
|
|
||||||
|
|
||||||
import { useActionSheet } from '../../containers/ActionSheet';
|
|
||||||
import StartACallActionSheet from '../../containers/UIKit/VideoConferenceBlock/components/StartACallActionSheet';
|
|
||||||
import { ISubscription, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
|
||||||
import i18n from '../../i18n';
|
|
||||||
import { getUserSelector } from '../../selectors/login';
|
|
||||||
import database from '../database';
|
|
||||||
import { getSubscriptionByRoomId } from '../database/services/Subscription';
|
|
||||||
import { callJitsi } from '../methods';
|
|
||||||
import { compareServerVersion, showErrorAlert } from '../methods/helpers';
|
|
||||||
import { videoConfStartAndJoin } from '../methods/videoConf';
|
|
||||||
import { Services } from '../services';
|
|
||||||
import { useAppSelector } from './useAppSelector';
|
|
||||||
import { useSnaps } from './useSnaps';
|
|
||||||
|
|
||||||
const availabilityErrors = {
|
|
||||||
NOT_CONFIGURED: 'video-conf-provider-not-configured',
|
|
||||||
NOT_ACTIVE: 'no-active-video-conf-provider',
|
|
||||||
NO_APP: 'no-videoconf-provider-app'
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const handleErrors = (isAdmin: boolean, error: typeof availabilityErrors[keyof typeof availabilityErrors]) => {
|
|
||||||
if (isAdmin) return showErrorAlert(i18n.t(`admin-${error}-body`), i18n.t(`admin-${error}-header`));
|
|
||||||
return showErrorAlert(i18n.t(`${error}-body`), i18n.t(`${error}-header`));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useVideoConf = (rid: string): { showInitCallActionSheet: () => Promise<void>; showCallOption: boolean } => {
|
|
||||||
const [showCallOption, setShowCallOption] = useState(false);
|
|
||||||
|
|
||||||
const serverVersion = useAppSelector(state => state.server.version);
|
|
||||||
const jitsiEnabled = useAppSelector(state => state.settings.Jitsi_Enabled);
|
|
||||||
const jitsiEnableTeams = useAppSelector(state => state.settings.Jitsi_Enable_Teams);
|
|
||||||
const jitsiEnableChannels = useAppSelector(state => state.settings.Jitsi_Enable_Channels);
|
|
||||||
const user = useAppSelector(state => getUserSelector(state));
|
|
||||||
|
|
||||||
const isServer5OrNewer = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0');
|
|
||||||
|
|
||||||
const { showActionSheet } = useActionSheet();
|
|
||||||
const snaps = useSnaps([1250]);
|
|
||||||
|
|
||||||
const handleShowCallOption = (room: TSubscriptionModel) => {
|
|
||||||
if (isServer5OrNewer) return setShowCallOption(true);
|
|
||||||
const isJitsiDisabledForTeams = room.teamMain && !jitsiEnableTeams;
|
|
||||||
const isJitsiDisabledForChannels = !room.teamMain && (room.t === 'p' || room.t === 'c') && !jitsiEnableChannels;
|
|
||||||
|
|
||||||
if (room.t === SubscriptionType.DIRECT) return setShowCallOption(!!jitsiEnabled);
|
|
||||||
if (room.t === SubscriptionType.CHANNEL) return setShowCallOption(!isJitsiDisabledForChannels);
|
|
||||||
if (room.t === SubscriptionType.GROUP) return setShowCallOption(!isJitsiDisabledForTeams);
|
|
||||||
|
|
||||||
return setShowCallOption(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const canInitAnCall = async () => {
|
|
||||||
if (isServer5OrNewer) {
|
|
||||||
try {
|
|
||||||
await Services.videoConferenceGetCapabilities();
|
|
||||||
return true;
|
|
||||||
} catch (error: any) {
|
|
||||||
const isAdmin = !!['admin'].find(role => user.roles?.includes(role));
|
|
||||||
switch (error?.error) {
|
|
||||||
case availabilityErrors.NOT_CONFIGURED:
|
|
||||||
return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED);
|
|
||||||
case availabilityErrors.NOT_ACTIVE:
|
|
||||||
return handleErrors(isAdmin, availabilityErrors.NOT_ACTIVE);
|
|
||||||
case availabilityErrors.NO_APP:
|
|
||||||
return handleErrors(isAdmin, availabilityErrors.NO_APP);
|
|
||||||
default:
|
|
||||||
return handleErrors(isAdmin, availabilityErrors.NOT_CONFIGURED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initCall = async ({ cam, mic }: { cam: boolean; mic: boolean }) => {
|
|
||||||
if (isServer5OrNewer) return videoConfStartAndJoin({ rid, cam, mic });
|
|
||||||
const room = (await getSubscriptionByRoomId(rid)) as ISubscription;
|
|
||||||
callJitsi({ room, cam });
|
|
||||||
};
|
|
||||||
|
|
||||||
const showInitCallActionSheet = async () => {
|
|
||||||
const canInit = await canInitAnCall();
|
|
||||||
if (canInit) {
|
|
||||||
showActionSheet({
|
|
||||||
children: <StartACallActionSheet rid={rid} initCall={initCall} />,
|
|
||||||
snaps
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const initSubscription = () => {
|
|
||||||
try {
|
|
||||||
const db = database.active;
|
|
||||||
const observeSubCollection = db.get('subscriptions').query(Q.where('rid', rid)).observe();
|
|
||||||
const subObserveQuery = observeSubCollection.subscribe(data => {
|
|
||||||
if (data[0]) {
|
|
||||||
handleShowCallOption(data[0]);
|
|
||||||
subObserveQuery.unsubscribe();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log("observeSubscriptions: Can't find subscription to observe");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
initSubscription();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { showInitCallActionSheet, showCallOption };
|
|
||||||
};
|
|
|
@ -52,7 +52,7 @@ export const deleteAllAudioFiles = async (serverUrl: string): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const serverUrlParsed = sanitizeString(serverUrl);
|
const serverUrlParsed = sanitizeString(serverUrl);
|
||||||
const path = `${FileSystem.documentDirectory}audios/${serverUrlParsed}`;
|
const path = `${FileSystem.documentDirectory}audios/${serverUrlParsed}`;
|
||||||
await FileSystem.deleteAsync(path, { idempotent: true });
|
await FileSystem.deleteAsync(path);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(error);
|
log(error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,8 +46,8 @@ export function callJitsiWithoutServer(path: string): void {
|
||||||
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false });
|
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function callJitsi({ room, cam = false }: { room: ISubscription; cam?: boolean }): Promise<void> {
|
export async function callJitsi(room: ISubscription, onlyAudio = false): Promise<void> {
|
||||||
logEvent(cam ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||||
const url = await jitsiURL({ room });
|
const url = await jitsiURL({ room });
|
||||||
Navigation.navigate('JitsiMeetView', { url, onlyAudio: cam, rid: room?.rid });
|
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid: room?.rid });
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import database from '../database';
|
||||||
import sdk from '../services/sdk';
|
import sdk from '../services/sdk';
|
||||||
import protectedFunction from './helpers/protectedFunction';
|
import protectedFunction from './helpers/protectedFunction';
|
||||||
import { parseSettings, _prepareSettings } from './parseSettings';
|
import { parseSettings, _prepareSettings } from './parseSettings';
|
||||||
import { setPresenceCap } from './getUsersPresence';
|
|
||||||
|
|
||||||
const serverInfoKeys = [
|
const serverInfoKeys = [
|
||||||
'Site_Name',
|
'Site_Name',
|
||||||
|
@ -158,11 +157,8 @@ export async function getSettings(): Promise<void> {
|
||||||
const data: IData[] = result.settings || [];
|
const data: IData[] = result.settings || [];
|
||||||
const filteredSettings: IPreparedSettings[] = _prepareSettings(data);
|
const filteredSettings: IPreparedSettings[] = _prepareSettings(data);
|
||||||
const filteredSettingsIds = filteredSettings.map(s => s._id);
|
const filteredSettingsIds = filteredSettings.map(s => s._id);
|
||||||
const parsedSettings = parseSettings(filteredSettings);
|
|
||||||
|
|
||||||
reduxStore.dispatch(addSettings(parsedSettings));
|
reduxStore.dispatch(addSettings(parseSettings(filteredSettings)));
|
||||||
|
|
||||||
setPresenceCap(parsedSettings.Presence_broadcast_disabled);
|
|
||||||
|
|
||||||
// filter server info
|
// filter server info
|
||||||
const serverInfo = filteredSettings.filter(i1 => serverInfoKeys.includes(i1._id));
|
const serverInfo = filteredSettings.filter(i1 => serverInfoKeys.includes(i1._id));
|
||||||
|
|
|
@ -9,9 +9,6 @@ import database from '../database';
|
||||||
import { IUser } from '../../definitions';
|
import { IUser } from '../../definitions';
|
||||||
import sdk from '../services/sdk';
|
import sdk from '../services/sdk';
|
||||||
import { compareServerVersion } from './helpers';
|
import { compareServerVersion } from './helpers';
|
||||||
import userPreferences from './userPreferences';
|
|
||||||
import { NOTIFICATION_PRESENCE_CAP } from '../constants';
|
|
||||||
import { setNotificationPresenceCap } from '../../actions/app';
|
|
||||||
|
|
||||||
export const _activeUsersSubTimeout: { activeUsersSubTimeout: boolean | ReturnType<typeof setTimeout> | number } = {
|
export const _activeUsersSubTimeout: { activeUsersSubTimeout: boolean | ReturnType<typeof setTimeout> | number } = {
|
||||||
activeUsersSubTimeout: false
|
activeUsersSubTimeout: false
|
||||||
|
@ -127,16 +124,3 @@ export function getUserPresence(uid: string) {
|
||||||
usersBatch.push(uid);
|
usersBatch.push(uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setPresenceCap = async (enabled: boolean) => {
|
|
||||||
if (enabled) {
|
|
||||||
const notificationPresenceCap = await userPreferences.getBool(NOTIFICATION_PRESENCE_CAP);
|
|
||||||
if (notificationPresenceCap !== false) {
|
|
||||||
userPreferences.setBool(NOTIFICATION_PRESENCE_CAP, true);
|
|
||||||
reduxStore.dispatch(setNotificationPresenceCap(true));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
userPreferences.removeItem(NOTIFICATION_PRESENCE_CAP);
|
|
||||||
reduxStore.dispatch(setNotificationPresenceCap(false));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { Alert, Linking } from 'react-native';
|
import { Alert, Linking } from 'react-native';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
|
||||||
import { RUNNING_E2E_TESTS } from '@env';
|
|
||||||
|
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
import { isFDroidBuild, STORE_REVIEW_LINK } from '../../constants';
|
import { isFDroidBuild, STORE_REVIEW_LINK } from '../../constants';
|
||||||
|
@ -88,15 +86,14 @@ class ReviewApp {
|
||||||
positiveEventCount = 0;
|
positiveEventCount = 0;
|
||||||
|
|
||||||
pushPositiveEvent = () => {
|
pushPositiveEvent = () => {
|
||||||
if (isFDroidBuild || RUNNING_E2E_TESTS === 'true') {
|
if (!isFDroidBuild) {
|
||||||
return;
|
if (this.positiveEventCount >= numberOfPositiveEvent) {
|
||||||
}
|
return;
|
||||||
if (this.positiveEventCount >= numberOfPositiveEvent) {
|
}
|
||||||
return;
|
this.positiveEventCount += 1;
|
||||||
}
|
if (this.positiveEventCount === numberOfPositiveEvent) {
|
||||||
this.positiveEventCount += 1;
|
tryReview();
|
||||||
if (this.positiveEventCount === numberOfPositiveEvent) {
|
}
|
||||||
tryReview();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,7 @@ export const localSearchSubscription = async ({ text = '', filterUsers = true, f
|
||||||
t: item.t,
|
t: item.t,
|
||||||
encrypted: item.encrypted,
|
encrypted: item.encrypted,
|
||||||
lastMessage: item.lastMessage,
|
lastMessage: item.lastMessage,
|
||||||
status: item.status,
|
status: item.status
|
||||||
teamMain: item.teamMain
|
|
||||||
})) as ISearchLocal[];
|
})) as ISearchLocal[];
|
||||||
|
|
||||||
return search;
|
return search;
|
||||||
|
|
|
@ -138,23 +138,6 @@ export default class RoomSubscription {
|
||||||
reduxStore.dispatch(removeUserTyping(name));
|
reduxStore.dispatch(removeUserTyping(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (ev === 'user-activity') {
|
|
||||||
const { user } = reduxStore.getState().login;
|
|
||||||
const { UI_Use_Real_Name } = reduxStore.getState().settings;
|
|
||||||
const { subscribedRoom } = reduxStore.getState().room;
|
|
||||||
if (subscribedRoom !== _rid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const [name, activities] = ddpMessage.fields.args;
|
|
||||||
const key = UI_Use_Real_Name ? 'name' : 'username';
|
|
||||||
if (name !== user[key]) {
|
|
||||||
if (activities.includes('user-typing')) {
|
|
||||||
reduxStore.dispatch(addUserTyping(name));
|
|
||||||
}
|
|
||||||
if (!activities.length) {
|
|
||||||
reduxStore.dispatch(removeUserTyping(name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (ev === 'deleteMessage') {
|
} else if (ev === 'deleteMessage') {
|
||||||
InteractionManager.runAfterInteractions(async () => {
|
InteractionManager.runAfterInteractions(async () => {
|
||||||
if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) {
|
if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) {
|
||||||
|
|
|
@ -16,43 +16,42 @@ class UserPreferences {
|
||||||
|
|
||||||
getString(key: string): string | null {
|
getString(key: string): string | null {
|
||||||
try {
|
try {
|
||||||
return this.mmkv.getString(key) ?? null;
|
return this.mmkv.getString(key) || null;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setString(key: string, value: string): boolean | undefined {
|
setString(key: string, value: string): boolean | undefined {
|
||||||
return this.mmkv.setString(key, value) ?? undefined;
|
return this.mmkv.setString(key, value) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBool(key: string): boolean | null {
|
getBool(key: string): boolean | null {
|
||||||
try {
|
try {
|
||||||
console.log(this.mmkv.getBool(key));
|
return this.mmkv.getBool(key) || null;
|
||||||
return this.mmkv.getBool(key) ?? null;
|
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setBool(key: string, value: boolean): boolean | undefined {
|
setBool(key: string, value: boolean): boolean | undefined {
|
||||||
return this.mmkv.setBool(key, value) ?? undefined;
|
return this.mmkv.setBool(key, value) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMap(key: string): object | null {
|
getMap(key: string): object | null {
|
||||||
try {
|
try {
|
||||||
return this.mmkv.getMap(key) ?? null;
|
return this.mmkv.getMap(key) || null;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setMap(key: string, value: object): boolean | undefined {
|
setMap(key: string, value: object): boolean | undefined {
|
||||||
return this.mmkv.setMap(key, value) ?? undefined;
|
return this.mmkv.setMap(key, value) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem(key: string): boolean | undefined {
|
removeItem(key: string): boolean | undefined {
|
||||||
return this.mmkv.removeItem(key) ?? undefined;
|
return this.mmkv.removeItem(key) || undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ const handleBltPermission = async (): Promise<Permission[]> => {
|
||||||
return [PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION];
|
return [PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const videoConfJoin = async (callId: string, cam?: boolean, mic?: boolean): Promise<void> => {
|
export const videoConfJoin = async (callId: string, cam: boolean) => {
|
||||||
try {
|
try {
|
||||||
const result = await Services.videoConferenceJoin(callId, cam, mic);
|
const result = await Services.videoConferenceJoin(callId, cam);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
const bltPermission = await handleBltPermission();
|
const bltPermission = await handleBltPermission();
|
||||||
|
@ -44,11 +44,11 @@ export const videoConfJoin = async (callId: string, cam?: boolean, mic?: boolean
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const videoConfStartAndJoin = async ({ rid, cam, mic }: { rid: string; cam?: boolean; mic?: boolean }): Promise<void> => {
|
export const videoConfStartAndJoin = async (rid: string, cam: boolean) => {
|
||||||
try {
|
try {
|
||||||
const videoConfResponse = await Services.videoConferenceStart(rid);
|
const videoConfResponse: any = await Services.videoConferenceStart(rid);
|
||||||
if (videoConfResponse.success) {
|
if (videoConfResponse.success) {
|
||||||
videoConfJoin(videoConfResponse.data.callId, cam, mic);
|
videoConfJoin(videoConfResponse.data.callId, cam);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorAlert(i18n.t('error-init-video-conf'));
|
showErrorAlert(i18n.t('error-init-video-conf'));
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import BackgroundTimer from 'react-native-background-timer';
|
|
||||||
|
|
||||||
import { Services } from '../services';
|
|
||||||
|
|
||||||
let interval: number | null = null;
|
|
||||||
|
|
||||||
export const initVideoConfTimer = (rid: string): void => {
|
|
||||||
if (rid) {
|
|
||||||
Services.updateJitsiTimeout(rid).catch((e: unknown) => console.log(e));
|
|
||||||
if (interval) {
|
|
||||||
BackgroundTimer.clearInterval(interval);
|
|
||||||
BackgroundTimer.stopBackgroundTimer();
|
|
||||||
interval = null;
|
|
||||||
}
|
|
||||||
interval = BackgroundTimer.setInterval(() => {
|
|
||||||
Services.updateJitsiTimeout(rid).catch((e: unknown) => console.log(e));
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const endVideoConfTimer = (): void => {
|
|
||||||
if (interval) {
|
|
||||||
BackgroundTimer.clearInterval(interval);
|
|
||||||
interval = null;
|
|
||||||
BackgroundTimer.stopBackgroundTimer();
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -20,16 +20,7 @@ import { updatePermission } from '../../actions/permissions';
|
||||||
import EventEmitter from '../methods/helpers/events';
|
import EventEmitter from '../methods/helpers/events';
|
||||||
import { updateSettings } from '../../actions/settings';
|
import { updateSettings } from '../../actions/settings';
|
||||||
import { defaultSettings, MIN_ROCKETCHAT_VERSION } from '../constants';
|
import { defaultSettings, MIN_ROCKETCHAT_VERSION } from '../constants';
|
||||||
import {
|
import { getSettings, IActiveUsers, unsubscribeRooms, _activeUsers, _setUser, _setUserTimer, onRolesChanged } from '../methods';
|
||||||
getSettings,
|
|
||||||
IActiveUsers,
|
|
||||||
unsubscribeRooms,
|
|
||||||
_activeUsers,
|
|
||||||
_setUser,
|
|
||||||
_setUserTimer,
|
|
||||||
onRolesChanged,
|
|
||||||
setPresenceCap
|
|
||||||
} from '../methods';
|
|
||||||
import { compareServerVersion, isIOS, isSsl } from '../methods/helpers';
|
import { compareServerVersion, isIOS, isSsl } from '../methods/helpers';
|
||||||
|
|
||||||
interface IServices {
|
interface IServices {
|
||||||
|
@ -153,10 +144,6 @@ function connect({ server, logoutOnError = false }: { server: string; logoutOnEr
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
store.dispatch(updateSettings(_id, value));
|
store.dispatch(updateSettings(_id, value));
|
||||||
|
|
||||||
if (_id === 'Presence_broadcast_disabled') {
|
|
||||||
setPresenceCap(value);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -812,14 +812,10 @@ export const addUsersToRoom = (rid: string): Promise<boolean> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emitTyping = (room: IRoom, typing = true) => {
|
export const emitTyping = (room: IRoom, typing = true) => {
|
||||||
const { login, settings, server } = reduxStore.getState();
|
const { login, settings } = reduxStore.getState();
|
||||||
const { UI_Use_Real_Name } = settings;
|
const { UI_Use_Real_Name } = settings;
|
||||||
const { version: serverVersion } = server;
|
|
||||||
const { user } = login;
|
const { user } = login;
|
||||||
const name = UI_Use_Real_Name ? user.name : user.username;
|
const name = UI_Use_Real_Name ? user.name : user.username;
|
||||||
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.0.0')) {
|
|
||||||
return sdk.methodCall('stream-notify-room', `${room}/user-activity`, name, typing ? ['user-typing'] : []);
|
|
||||||
}
|
|
||||||
return sdk.methodCall('stream-notify-room', `${room}/typing`, name, typing);
|
return sdk.methodCall('stream-notify-room', `${room}/typing`, name, typing);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -936,10 +932,8 @@ export function getUserInfo(userId: string) {
|
||||||
|
|
||||||
export const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('rooms.favorite', { roomId, favorite });
|
export const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('rooms.favorite', { roomId, favorite });
|
||||||
|
|
||||||
export const videoConferenceJoin = (callId: string, cam?: boolean, mic?: boolean) =>
|
export const videoConferenceJoin = (callId: string, cam: boolean) =>
|
||||||
sdk.post('video-conference.join', { callId, state: { cam: !!cam, mic: mic === undefined ? true : mic } });
|
sdk.post('video-conference.join', { callId, state: { cam } });
|
||||||
|
|
||||||
export const videoConferenceGetCapabilities = () => sdk.get('video-conference.capabilities');
|
|
||||||
|
|
||||||
export const videoConferenceStart = (roomId: string) => sdk.post('video-conference.start', { roomId });
|
export const videoConferenceStart = (roomId: string) => sdk.post('video-conference.start', { roomId });
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { twoFactor } from './twoFactor';
|
||||||
import { isSsl } from '../methods/helpers/url';
|
import { isSsl } from '../methods/helpers/url';
|
||||||
import { store as reduxStore } from '../store/auxStore';
|
import { store as reduxStore } from '../store/auxStore';
|
||||||
import { Serialized, MatchPathPattern, OperationParams, PathFor, ResultFor } from '../../definitions/rest/helpers';
|
import { Serialized, MatchPathPattern, OperationParams, PathFor, ResultFor } from '../../definitions/rest/helpers';
|
||||||
import { compareServerVersion, random } from '../methods/helpers';
|
import { random } from '../methods/helpers';
|
||||||
|
|
||||||
class Sdk {
|
class Sdk {
|
||||||
private sdk: typeof Rocketchat;
|
private sdk: typeof Rocketchat;
|
||||||
|
@ -162,22 +162,7 @@ class Sdk {
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeRoom(...args: any[]) {
|
subscribeRoom(...args: any[]) {
|
||||||
const { server } = reduxStore.getState();
|
return this.current.subscribeRoom(...args);
|
||||||
const { version: serverVersion } = server;
|
|
||||||
const topic = 'stream-notify-room';
|
|
||||||
let eventUserTyping;
|
|
||||||
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.0.0')) {
|
|
||||||
eventUserTyping = this.subscribe(topic, `${args[0]}/user-activity`, ...args);
|
|
||||||
} else {
|
|
||||||
eventUserTyping = this.subscribe(topic, `${args[0]}/typing`, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taken from https://github.com/RocketChat/Rocket.Chat.js.SDK/blob/454b4ba784095057b8de862eb99340311b672e15/lib/drivers/ddp.ts#L555
|
|
||||||
return Promise.all([
|
|
||||||
this.subscribe('stream-room-messages', args[0], ...args),
|
|
||||||
eventUserTyping,
|
|
||||||
this.subscribe(topic, `${args[0]}/deleteMessage`, ...args)
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe(subscription: any[]) {
|
unsubscribe(subscription: any[]) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { appStart, appInit, setMasterDetail, setNotificationPresenceCap, appReady } from '../actions/app';
|
import { appStart, appInit, setMasterDetail } from '../actions/app';
|
||||||
import { initialState } from './app';
|
import { initialState } from './app';
|
||||||
import { mockedStore } from './mockedStore';
|
import { mockedStore } from './mockedStore';
|
||||||
import { RootEnum } from '../definitions';
|
import { RootEnum } from '../definitions';
|
||||||
|
@ -20,9 +20,6 @@ describe('test reducer', () => {
|
||||||
mockedStore.dispatch(appInit());
|
mockedStore.dispatch(appInit());
|
||||||
const { ready } = mockedStore.getState().app;
|
const { ready } = mockedStore.getState().app;
|
||||||
expect(ready).toEqual(false);
|
expect(ready).toEqual(false);
|
||||||
mockedStore.dispatch(appReady());
|
|
||||||
const { ready: ready2 } = mockedStore.getState().app;
|
|
||||||
expect(ready2).toEqual(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return ready state after dispatch setMasterDetail action', () => {
|
it('should return ready state after dispatch setMasterDetail action', () => {
|
||||||
|
@ -44,13 +41,4 @@ describe('test reducer', () => {
|
||||||
expect(foreground).toEqual(false);
|
expect(foreground).toEqual(false);
|
||||||
expect(background).toEqual(true);
|
expect(background).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return correct state after dispatch setNotificationPresenceCap action', () => {
|
|
||||||
mockedStore.dispatch(setNotificationPresenceCap(true));
|
|
||||||
const { notificationPresenceCap } = mockedStore.getState().app;
|
|
||||||
expect(notificationPresenceCap).toEqual(true);
|
|
||||||
mockedStore.dispatch(setNotificationPresenceCap(false));
|
|
||||||
const { notificationPresenceCap: notificationPresenceCap2 } = mockedStore.getState().app;
|
|
||||||
expect(notificationPresenceCap2).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,6 @@ export interface IApp {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
foreground: boolean;
|
foreground: boolean;
|
||||||
background: boolean;
|
background: boolean;
|
||||||
notificationPresenceCap: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialState: IApp = {
|
export const initialState: IApp = {
|
||||||
|
@ -18,8 +17,7 @@ export const initialState: IApp = {
|
||||||
text: undefined,
|
text: undefined,
|
||||||
ready: false,
|
ready: false,
|
||||||
foreground: true,
|
foreground: true,
|
||||||
background: false,
|
background: false
|
||||||
notificationPresenceCap: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function app(state = initialState, action: TActionApp): IApp {
|
export default function app(state = initialState, action: TActionApp): IApp {
|
||||||
|
@ -57,11 +55,6 @@ export default function app(state = initialState, action: TActionApp): IApp {
|
||||||
...state,
|
...state,
|
||||||
isMasterDetail: action.isMasterDetail
|
isMasterDetail: action.isMasterDetail
|
||||||
};
|
};
|
||||||
case APP.SET_NOTIFICATION_PRESENCE_CAP:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
notificationPresenceCap: action.show
|
|
||||||
};
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,11 @@ const ChatsStackNavigator = () => {
|
||||||
/>
|
/>
|
||||||
<ChatsStack.Screen name='SelectedUsersView' component={SelectedUsersView} />
|
<ChatsStack.Screen name='SelectedUsersView' component={SelectedUsersView} />
|
||||||
<ChatsStack.Screen name='InviteUsersView' component={InviteUsersView} />
|
<ChatsStack.Screen name='InviteUsersView' component={InviteUsersView} />
|
||||||
<ChatsStack.Screen name='InviteUsersEditView' component={InviteUsersEditView} />
|
<ChatsStack.Screen
|
||||||
|
name='InviteUsersEditView'
|
||||||
|
component={InviteUsersEditView}
|
||||||
|
options={InviteUsersEditView.navigationOptions}
|
||||||
|
/>
|
||||||
<ChatsStack.Screen name='MessagesView' component={MessagesView} />
|
<ChatsStack.Screen name='MessagesView' component={MessagesView} />
|
||||||
<ChatsStack.Screen name='AutoTranslateView' component={AutoTranslateView} options={AutoTranslateView.navigationOptions} />
|
<ChatsStack.Screen name='AutoTranslateView' component={AutoTranslateView} options={AutoTranslateView.navigationOptions} />
|
||||||
<ChatsStack.Screen name='DirectoryView' component={DirectoryView} options={DirectoryView.navigationOptions} />
|
<ChatsStack.Screen name='DirectoryView' component={DirectoryView} options={DirectoryView.navigationOptions} />
|
||||||
|
|
|
@ -142,7 +142,11 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
||||||
component={AddExistingChannelView}
|
component={AddExistingChannelView}
|
||||||
options={AddExistingChannelView.navigationOptions}
|
options={AddExistingChannelView.navigationOptions}
|
||||||
/>
|
/>
|
||||||
<ModalStack.Screen name='InviteUsersEditView' component={InviteUsersEditView} />
|
<ModalStack.Screen
|
||||||
|
name='InviteUsersEditView'
|
||||||
|
component={InviteUsersEditView}
|
||||||
|
options={InviteUsersEditView.navigationOptions}
|
||||||
|
/>
|
||||||
<ModalStack.Screen name='MessagesView' component={MessagesView} />
|
<ModalStack.Screen name='MessagesView' component={MessagesView} />
|
||||||
<ModalStack.Screen name='AutoTranslateView' component={AutoTranslateView} options={AutoTranslateView.navigationOptions} />
|
<ModalStack.Screen name='AutoTranslateView' component={AutoTranslateView} options={AutoTranslateView.navigationOptions} />
|
||||||
<ModalStack.Screen
|
<ModalStack.Screen
|
||||||
|
|
|
@ -94,7 +94,6 @@ export type ChatsStackParamList = {
|
||||||
showButton?: boolean;
|
showButton?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
showSkipText?: boolean;
|
|
||||||
nextAction?(): void;
|
nextAction?(): void;
|
||||||
};
|
};
|
||||||
InviteUsersView: {
|
InviteUsersView: {
|
||||||
|
|
|
@ -243,7 +243,7 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS
|
||||||
title: item.name as string,
|
title: item.name as string,
|
||||||
onPress: () => this.onPressItem(item),
|
onPress: () => this.onPressItem(item),
|
||||||
baseUrl,
|
baseUrl,
|
||||||
testID: `directory-view-item-${item.name}`,
|
testID: `directory-view-item-${item.name}`.toLowerCase(),
|
||||||
style,
|
style,
|
||||||
user,
|
user,
|
||||||
theme,
|
theme,
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { TextInputProps } from 'react-native';
|
|
||||||
import RNPickerSelect from 'react-native-picker-select';
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
import { inviteLinksSetParams } from '../../actions/inviteLinks';
|
|
||||||
import { useTheme } from '../../theme';
|
|
||||||
import { useAppSelector } from '../../lib/hooks';
|
|
||||||
import I18n from '../../i18n';
|
|
||||||
import styles from './styles';
|
|
||||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
|
||||||
|
|
||||||
const OPTIONS = {
|
|
||||||
days: [
|
|
||||||
{
|
|
||||||
label: '1',
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '7',
|
|
||||||
value: 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '15',
|
|
||||||
value: 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '30',
|
|
||||||
value: 30
|
|
||||||
}
|
|
||||||
],
|
|
||||||
maxUses: [
|
|
||||||
{
|
|
||||||
label: '1',
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '5',
|
|
||||||
value: 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '10',
|
|
||||||
value: 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '25',
|
|
||||||
value: 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '50',
|
|
||||||
value: 50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '100',
|
|
||||||
value: 100
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const Picker = ({ param, first }: { param: 'days' | 'maxUses'; first: string }): JSX.Element => {
|
|
||||||
const { colors } = useTheme();
|
|
||||||
const inviteLinkParam = useAppSelector(state => state.inviteLinks[param]);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
const onValueChangePicker = (value: number) => {
|
|
||||||
logEvent(events.IU_EDIT_SET_LINK_PARAM);
|
|
||||||
const params = {
|
|
||||||
[param]: value
|
|
||||||
};
|
|
||||||
dispatch(inviteLinksSetParams(params));
|
|
||||||
};
|
|
||||||
|
|
||||||
const textInputStyle: TextInputProps = { style: { ...styles.pickerText, color: colors.actionTintColor } };
|
|
||||||
const firstEl = [
|
|
||||||
{
|
|
||||||
label: I18n.t(first),
|
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<RNPickerSelect
|
|
||||||
style={{ viewContainer: styles.viewContainer }}
|
|
||||||
value={inviteLinkParam}
|
|
||||||
textInputProps={textInputStyle}
|
|
||||||
useNativeAndroidPickerStyle={false}
|
|
||||||
onValueChange={value => onValueChangePicker(value)}
|
|
||||||
items={firstEl.concat(OPTIONS[param])}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Picker;
|
|
|
@ -1,54 +1,149 @@
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||||
import React, { useLayoutEffect } from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { TextInputProps, View } from 'react-native';
|
||||||
import { useDispatch } from 'react-redux';
|
import RNPickerSelect from 'react-native-picker-select';
|
||||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { inviteLinksCreate } from '../../actions/inviteLinks';
|
import { inviteLinksCreate, inviteLinksSetParams } from '../../actions/inviteLinks';
|
||||||
|
import { themes } from '../../lib/constants';
|
||||||
import Button from '../../containers/Button';
|
import Button from '../../containers/Button';
|
||||||
import * as List from '../../containers/List';
|
import * as List from '../../containers/List';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
|
import { IApplicationState, IBaseScreen } from '../../definitions';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { ChatsStackParamList } from '../../stacks/types';
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
|
import { withTheme } from '../../theme';
|
||||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Picker from './Picker';
|
|
||||||
|
|
||||||
const InviteUsersEditView = () => {
|
const OPTIONS = {
|
||||||
const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'InviteUsersEditView'>>();
|
days: [
|
||||||
const { rid } = useRoute<RouteProp<ChatsStackParamList, 'InviteUsersEditView'>>().params;
|
{
|
||||||
const dispatch = useDispatch();
|
label: '1',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '7',
|
||||||
|
value: 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '15',
|
||||||
|
value: 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '30',
|
||||||
|
value: 30
|
||||||
|
}
|
||||||
|
],
|
||||||
|
maxUses: [
|
||||||
|
{
|
||||||
|
label: '1',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '5',
|
||||||
|
value: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '10',
|
||||||
|
value: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '25',
|
||||||
|
value: 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '50',
|
||||||
|
value: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '100',
|
||||||
|
value: 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
interface IInviteUsersEditViewProps extends IBaseScreen<ChatsStackParamList, 'InviteUsersEditView'> {
|
||||||
navigation.setOptions({
|
days: number;
|
||||||
title: I18n.t('Invite_users')
|
maxUses: number;
|
||||||
});
|
}
|
||||||
}, [navigation]);
|
|
||||||
|
|
||||||
const createInviteLink = () => {
|
class InviteUsersEditView extends React.Component<IInviteUsersEditViewProps, any> {
|
||||||
|
static navigationOptions = (): StackNavigationOptions => ({
|
||||||
|
title: I18n.t('Invite_users')
|
||||||
|
});
|
||||||
|
|
||||||
|
private rid: string;
|
||||||
|
|
||||||
|
constructor(props: IInviteUsersEditViewProps) {
|
||||||
|
super(props);
|
||||||
|
this.rid = props.route.params?.rid;
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChangePicker = (key: string, value: number) => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
logEvent(events.IU_EDIT_SET_LINK_PARAM);
|
||||||
|
const params = {
|
||||||
|
[key]: value
|
||||||
|
};
|
||||||
|
dispatch(inviteLinksSetParams(params));
|
||||||
|
};
|
||||||
|
|
||||||
|
createInviteLink = () => {
|
||||||
|
const { dispatch, navigation } = this.props;
|
||||||
logEvent(events.IU_EDIT_CREATE_LINK);
|
logEvent(events.IU_EDIT_CREATE_LINK);
|
||||||
dispatch(inviteLinksCreate(rid));
|
dispatch(inviteLinksCreate(this.rid));
|
||||||
navigation.pop();
|
navigation.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
renderPicker = (key: 'days' | 'maxUses', first: string) => {
|
||||||
<SafeAreaView>
|
const { props } = this;
|
||||||
<List.Container>
|
const { theme } = props;
|
||||||
<StatusBar />
|
const textInputStyle: TextInputProps = { style: { ...styles.pickerText, color: themes[theme].actionTintColor } };
|
||||||
<List.Section>
|
const firstEl = [
|
||||||
<List.Separator />
|
{
|
||||||
<List.Item title='Expiration_Days' right={() => <Picker param={'days'} first={'Never'} />} />
|
label: I18n.t(first),
|
||||||
<List.Separator />
|
value: 0
|
||||||
<List.Item title='Max_number_of_uses' right={() => <Picker param='maxUses' first='No_limit' />} />
|
}
|
||||||
<List.Separator />
|
];
|
||||||
</List.Section>
|
return (
|
||||||
<View style={styles.innerContainer}>
|
<RNPickerSelect
|
||||||
<Button title={I18n.t('Generate_New_Link')} type='primary' onPress={createInviteLink} />
|
style={{ viewContainer: styles.viewContainer }}
|
||||||
</View>
|
value={props[key]}
|
||||||
</List.Container>
|
textInputProps={textInputStyle}
|
||||||
</SafeAreaView>
|
useNativeAndroidPickerStyle={false}
|
||||||
);
|
placeholder={{}}
|
||||||
};
|
onValueChange={value => this.onValueChangePicker(key, value)}
|
||||||
|
items={firstEl.concat(OPTIONS[key])}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default InviteUsersEditView;
|
render() {
|
||||||
|
return (
|
||||||
|
<SafeAreaView>
|
||||||
|
<List.Container>
|
||||||
|
<StatusBar />
|
||||||
|
<List.Section>
|
||||||
|
<List.Separator />
|
||||||
|
<List.Item title='Expiration_Days' right={() => this.renderPicker('days', 'Never')} />
|
||||||
|
<List.Separator />
|
||||||
|
<List.Item title='Max_number_of_uses' right={() => this.renderPicker('maxUses', 'No_limit')} />
|
||||||
|
<List.Separator />
|
||||||
|
</List.Section>
|
||||||
|
<View style={styles.innerContainer}>
|
||||||
|
<Button title={I18n.t('Generate_New_Link')} type='primary' onPress={this.createInviteLink} />
|
||||||
|
</View>
|
||||||
|
</List.Container>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
|
days: state.inviteLinks.days,
|
||||||
|
maxUses: state.inviteLinks.maxUses
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(withTheme(InviteUsersEditView));
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BackHandler, NativeEventSubscription } from 'react-native';
|
import { BackHandler, NativeEventSubscription } from 'react-native';
|
||||||
|
import BackgroundTimer from 'react-native-background-timer';
|
||||||
import { isAppInstalled, openAppWithUri } from 'react-native-send-intent';
|
import { isAppInstalled, openAppWithUri } from 'react-native-send-intent';
|
||||||
import WebView from 'react-native-webview';
|
import WebView from 'react-native-webview';
|
||||||
import { WebViewMessage, WebViewNavigation } from 'react-native-webview/lib/WebViewTypes';
|
import { WebViewMessage, WebViewNavigation } from 'react-native-webview/lib/WebViewTypes';
|
||||||
|
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
||||||
|
|
||||||
import { IBaseScreen } from '../definitions';
|
import { IBaseScreen } from '../definitions';
|
||||||
import { events, logEvent } from '../lib/methods/helpers/log';
|
import { events, logEvent } from '../lib/methods/helpers/log';
|
||||||
import { endVideoConfTimer, initVideoConfTimer } from '../lib/methods/videoConfTimer';
|
import { Services } from '../lib/services';
|
||||||
import { ChatsStackParamList } from '../stacks/types';
|
import { ChatsStackParamList } from '../stacks/types';
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
private rid: string;
|
private rid: string;
|
||||||
private url: string;
|
private url: string;
|
||||||
private videoConf: boolean;
|
private videoConf: boolean;
|
||||||
|
private jitsiTimeout: number | null;
|
||||||
private backHandler!: NativeEventSubscription;
|
private backHandler!: NativeEventSubscription;
|
||||||
|
|
||||||
constructor(props: TJitsiMeetViewProps) {
|
constructor(props: TJitsiMeetViewProps) {
|
||||||
|
@ -26,6 +28,7 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
this.rid = props.route.params?.rid;
|
this.rid = props.route.params?.rid;
|
||||||
this.url = props.route.params?.url;
|
this.url = props.route.params?.url;
|
||||||
this.videoConf = !!props.route.params?.videoConf;
|
this.videoConf = !!props.route.params?.videoConf;
|
||||||
|
this.jitsiTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -47,8 +50,10 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
||||||
if (!this.videoConf) {
|
if (this.jitsiTimeout && !this.videoConf) {
|
||||||
endVideoConfTimer();
|
BackgroundTimer.clearInterval(this.jitsiTimeout);
|
||||||
|
this.jitsiTimeout = null;
|
||||||
|
BackgroundTimer.stopBackgroundTimer();
|
||||||
}
|
}
|
||||||
this.backHandler.remove();
|
this.backHandler.remove();
|
||||||
deactivateKeepAwake();
|
deactivateKeepAwake();
|
||||||
|
@ -59,7 +64,15 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
onConferenceJoined = () => {
|
onConferenceJoined = () => {
|
||||||
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
|
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
|
||||||
if (this.rid && !this.videoConf) {
|
if (this.rid && !this.videoConf) {
|
||||||
initVideoConfTimer(this.rid);
|
Services.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
|
||||||
|
if (this.jitsiTimeout) {
|
||||||
|
BackgroundTimer.clearInterval(this.jitsiTimeout);
|
||||||
|
BackgroundTimer.stopBackgroundTimer();
|
||||||
|
this.jitsiTimeout = null;
|
||||||
|
}
|
||||||
|
this.jitsiTimeout = BackgroundTimer.setInterval(() => {
|
||||||
|
Services.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
|
||||||
|
}, 10000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,7 +90,7 @@ class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<WebView
|
<WebView
|
||||||
source={{ uri: `${this.url}${this.url.includes('#config') ? '&' : '#'}config.disableDeepLinking=true` }}
|
source={{ uri: `${this.url}&config.disableDeepLinking=true` }}
|
||||||
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
|
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
|
||||||
onNavigationStateChange={this.onNavigationStateChange}
|
onNavigationStateChange={this.onNavigationStateChange}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { useAppSelector } from '../lib/hooks';
|
||||||
import { events, logEvent } from '../lib/methods/helpers/log';
|
import { events, logEvent } from '../lib/methods/helpers/log';
|
||||||
import { getUserSelector } from '../selectors/login';
|
import { getUserSelector } from '../selectors/login';
|
||||||
import { ChatsStackParamList } from '../stacks/types';
|
import { ChatsStackParamList } from '../stacks/types';
|
||||||
import { endVideoConfTimer, initVideoConfTimer } from '../lib/methods/videoConfTimer';
|
|
||||||
|
|
||||||
const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
|
const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
|
||||||
`${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`;
|
`${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`;
|
||||||
|
@ -17,7 +16,7 @@ const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLF
|
||||||
const JitsiMeetView = (): React.ReactElement => {
|
const JitsiMeetView = (): React.ReactElement => {
|
||||||
const { goBack } = useNavigation();
|
const { goBack } = useNavigation();
|
||||||
const {
|
const {
|
||||||
params: { url, onlyAudio, videoConf, rid }
|
params: { url, onlyAudio, videoConf }
|
||||||
} = useRoute<RouteProp<ChatsStackParamList, 'JitsiMeetView'>>();
|
} = useRoute<RouteProp<ChatsStackParamList, 'JitsiMeetView'>>();
|
||||||
const user = useAppSelector(state => getUserSelector(state));
|
const user = useAppSelector(state => getUserSelector(state));
|
||||||
const baseUrl = useAppSelector(state => state.server.server);
|
const baseUrl = useAppSelector(state => state.server.server);
|
||||||
|
@ -39,8 +38,8 @@ const JitsiMeetView = (): React.ReactElement => {
|
||||||
const regex = /(?:\/.*\/)(.*)/;
|
const regex = /(?:\/.*\/)(.*)/;
|
||||||
const urlWithoutServer = regex.exec(url)![1];
|
const urlWithoutServer = regex.exec(url)![1];
|
||||||
const serverUrl = url.replace(`/${urlWithoutServer}`, '');
|
const serverUrl = url.replace(`/${urlWithoutServer}`, '');
|
||||||
const room = (url.includes('jwt=') ? urlWithoutServer.split('jwt=')[0] : urlWithoutServer.split('#')[0]).replace('?', '');
|
const room = urlWithoutServer.split('#')[0];
|
||||||
const jwtToken = url.includes('jwt=') ? url.substring(url.indexOf('jwt=') + 4, url.lastIndexOf('#config')) : undefined;
|
|
||||||
const conferenceOptions = {
|
const conferenceOptions = {
|
||||||
room,
|
room,
|
||||||
serverUrl,
|
serverUrl,
|
||||||
|
@ -52,19 +51,24 @@ const JitsiMeetView = (): React.ReactElement => {
|
||||||
audioOnly,
|
audioOnly,
|
||||||
audioMuted: false,
|
audioMuted: false,
|
||||||
videoMuted: audioOnly,
|
videoMuted: audioOnly,
|
||||||
token: jwtToken,
|
|
||||||
featureFlags: {
|
featureFlags: {
|
||||||
'calendar.enabled': false
|
'live-streaming.enabled': false,
|
||||||
|
'calendar.enabled': false,
|
||||||
|
'call-integration.enabled': false,
|
||||||
|
'pip.enabled': false,
|
||||||
|
'invite.enabled': false,
|
||||||
|
'welcomepage.enabled': false,
|
||||||
|
'add-people.enabled': false
|
||||||
},
|
},
|
||||||
configOverrides: {
|
configOverrides: {
|
||||||
'breakoutRooms.hideAddRoomButton': false
|
'breakoutRooms.hideAddRoomButton': false,
|
||||||
|
'breakoutRooms.hideAutoAssignButton': false,
|
||||||
|
'breakoutRooms.hideJoinRoomButton': false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
|
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
|
||||||
if (!videoConf) initVideoConfTimer(rid);
|
|
||||||
await JitsiMeet.launchJitsiMeetView(conferenceOptions);
|
await JitsiMeet.launchJitsiMeetView(conferenceOptions);
|
||||||
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
logEvent(videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
|
||||||
if (!videoConf) endVideoConfTimer();
|
|
||||||
goBack();
|
goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import * as List from '../../../containers/List';
|
|
||||||
import i18n from '../../../i18n';
|
|
||||||
import { useVideoConf } from '../../../lib/hooks/useVideoConf';
|
|
||||||
|
|
||||||
export default function CallSection({ rid }: { rid: string }): React.ReactElement | null {
|
|
||||||
const { showCallOption, showInitCallActionSheet } = useVideoConf(rid);
|
|
||||||
|
|
||||||
if (showCallOption)
|
|
||||||
return (
|
|
||||||
<List.Section>
|
|
||||||
<List.Separator />
|
|
||||||
<List.Item
|
|
||||||
title={i18n.t('Call')}
|
|
||||||
onPress={showInitCallActionSheet}
|
|
||||||
testID='room-actions-call'
|
|
||||||
left={() => <List.Icon name='phone' />}
|
|
||||||
showActionIndicator
|
|
||||||
/>
|
|
||||||
<List.Separator />
|
|
||||||
</List.Section>
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -33,7 +33,7 @@ import sharedStyles from '../Styles';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { ERoomType } from '../../definitions/ERoomType';
|
import { ERoomType } from '../../definitions/ERoomType';
|
||||||
import { E2E_ROOM_TYPES, SWITCH_TRACK_COLOR, themes } from '../../lib/constants';
|
import { E2E_ROOM_TYPES, SWITCH_TRACK_COLOR, themes } from '../../lib/constants';
|
||||||
import { getPermalinkChannel } from '../../lib/methods';
|
import { callJitsi, getPermalinkChannel } from '../../lib/methods';
|
||||||
import {
|
import {
|
||||||
canAutoTranslate as canAutoTranslateMethod,
|
canAutoTranslate as canAutoTranslateMethod,
|
||||||
getRoomAvatar,
|
getRoomAvatar,
|
||||||
|
@ -48,9 +48,9 @@ import { getSubscriptionByRoomId } from '../../lib/database/services/Subscriptio
|
||||||
import { IActionSheetProvider, withActionSheet } from '../../containers/ActionSheet';
|
import { IActionSheetProvider, withActionSheet } from '../../containers/ActionSheet';
|
||||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
import { closeLivechat } from '../../lib/methods/helpers/closeLivechat';
|
import { closeLivechat } from '../../lib/methods/helpers/closeLivechat';
|
||||||
|
import { videoConfStartAndJoin } from '../../lib/methods/videoConf';
|
||||||
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
||||||
import { ILivechatTag } from '../../definitions/ILivechatTag';
|
import { ILivechatTag } from '../../definitions/ILivechatTag';
|
||||||
import CallSection from './components/CallSection';
|
|
||||||
|
|
||||||
interface IOnPressTouch {
|
interface IOnPressTouch {
|
||||||
<T extends keyof ChatsStackParamList>(item: { route?: T; params?: ChatsStackParamList[T]; event?: Function }): void;
|
<T extends keyof ChatsStackParamList>(item: { route?: T; params?: ChatsStackParamList[T]; event?: Function }): void;
|
||||||
|
@ -730,6 +730,16 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
startVideoConf = ({ video }: { video: boolean }): void => {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { serverVersion } = this.props;
|
||||||
|
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0')) {
|
||||||
|
videoConfStartAndJoin(room.rid, video);
|
||||||
|
} else {
|
||||||
|
callJitsi(room, !video);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
renderRoomInfo = () => {
|
renderRoomInfo = () => {
|
||||||
const { room, member } = this.state;
|
const { room, member } = this.state;
|
||||||
const { rid, name, t, topic, source } = room;
|
const { rid, name, t, topic, source } = room;
|
||||||
|
@ -805,6 +815,63 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderJitsi = () => {
|
||||||
|
const { room } = this.state;
|
||||||
|
const {
|
||||||
|
jitsiEnabled,
|
||||||
|
jitsiEnableTeams,
|
||||||
|
jitsiEnableChannels,
|
||||||
|
serverVersion,
|
||||||
|
videoConf_Enable_DMs,
|
||||||
|
videoConf_Enable_Channels,
|
||||||
|
videoConf_Enable_Groups,
|
||||||
|
videoConf_Enable_Teams
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const isJitsiDisabledForTeams = room.teamMain && !jitsiEnableTeams;
|
||||||
|
const isJitsiDisabledForChannels = !room.teamMain && (room.t === 'p' || room.t === 'c') && !jitsiEnableChannels;
|
||||||
|
|
||||||
|
const isVideoConfDisabledForTeams = !!room.teamMain && !videoConf_Enable_Teams;
|
||||||
|
const isVideoConfDisabledForChannels = !room.teamMain && room.t === 'c' && !videoConf_Enable_Channels;
|
||||||
|
const isVideoConfDisabledForGroups = !room.teamMain && room.t === 'p' && !videoConf_Enable_Groups;
|
||||||
|
const isVideoConfDisabledForDirect = !room.teamMain && room.t === 'd' && !videoConf_Enable_DMs;
|
||||||
|
|
||||||
|
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0')) {
|
||||||
|
if (
|
||||||
|
isVideoConfDisabledForTeams ||
|
||||||
|
isVideoConfDisabledForChannels ||
|
||||||
|
isVideoConfDisabledForGroups ||
|
||||||
|
isVideoConfDisabledForDirect
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (!jitsiEnabled || isJitsiDisabledForTeams || isJitsiDisabledForChannels) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List.Section>
|
||||||
|
<List.Separator />
|
||||||
|
<List.Item
|
||||||
|
title='Voice_call'
|
||||||
|
onPress={() => this.startVideoConf({ video: false })}
|
||||||
|
testID='room-actions-voice'
|
||||||
|
left={() => <List.Icon name='phone' />}
|
||||||
|
showActionIndicator
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
<List.Item
|
||||||
|
title='Video_call'
|
||||||
|
onPress={() => this.startVideoConf({ video: true })}
|
||||||
|
testID='room-actions-video'
|
||||||
|
left={() => <List.Icon name='camera' />}
|
||||||
|
showActionIndicator
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
</List.Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
renderE2EEncryption = () => {
|
renderE2EEncryption = () => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { encryptionEnabled } = this.props;
|
const { encryptionEnabled } = this.props;
|
||||||
|
@ -1041,7 +1108,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<List.Container testID='room-actions-scrollview'>
|
<List.Container testID='room-actions-scrollview'>
|
||||||
{this.renderRoomInfo()}
|
{this.renderRoomInfo()}
|
||||||
<CallSection rid={rid} />
|
{this.renderJitsi()}
|
||||||
{this.renderE2EEncryption()}
|
{this.renderE2EEncryption()}
|
||||||
<List.Section>
|
<List.Section>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
@ -1232,6 +1299,13 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
userId: getUserSelector(state).id,
|
userId: getUserSelector(state).id,
|
||||||
|
jitsiEnabled: (state.settings.Jitsi_Enabled || false) as boolean,
|
||||||
|
jitsiEnableTeams: (state.settings.Jitsi_Enable_Teams || false) as boolean,
|
||||||
|
jitsiEnableChannels: (state.settings.Jitsi_Enable_Channels || false) as boolean,
|
||||||
|
videoConf_Enable_DMs: (state.settings.VideoConf_Enable_DMs ?? true) as boolean,
|
||||||
|
videoConf_Enable_Channels: (state.settings.VideoConf_Enable_Channels ?? true) as boolean,
|
||||||
|
videoConf_Enable_Groups: (state.settings.VideoConf_Enable_Groups ?? true) as boolean,
|
||||||
|
videoConf_Enable_Teams: (state.settings.VideoConf_Enable_Teams ?? true) as boolean,
|
||||||
encryptionEnabled: state.encryption.enabled,
|
encryptionEnabled: state.encryption.enabled,
|
||||||
serverVersion: state.server.version,
|
serverVersion: state.server.version,
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
|
|
|
@ -655,6 +655,7 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
|
||||||
label={I18n.t('Password')}
|
label={I18n.t('Password')}
|
||||||
value={joinCode}
|
value={joinCode}
|
||||||
onChangeText={value => this.setState({ joinCode: value })}
|
onChangeText={value => this.setState({ joinCode: value })}
|
||||||
|
onSubmitEditing={this.submit}
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
testID='room-info-edit-view-password'
|
testID='room-info-edit-view-password'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Text } from 'react-native';
|
|
||||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
|
||||||
|
|
||||||
import { CustomIcon, TIconsName } from '../../../containers/CustomIcon';
|
|
||||||
import styles from '../styles';
|
|
||||||
import { useTheme } from '../../../theme';
|
|
||||||
import { useVideoConf } from '../../../lib/hooks/useVideoConf';
|
|
||||||
import i18n from '../../../i18n';
|
|
||||||
import { useAppSelector } from '../../../lib/hooks';
|
|
||||||
import { compareServerVersion } from '../../../lib/methods/helpers';
|
|
||||||
|
|
||||||
// TODO: change other icons on future
|
|
||||||
function UserInfoButton({
|
|
||||||
danger,
|
|
||||||
iconName,
|
|
||||||
onPress,
|
|
||||||
label,
|
|
||||||
showIcon
|
|
||||||
}: {
|
|
||||||
danger?: boolean;
|
|
||||||
iconName: TIconsName;
|
|
||||||
onPress?: (prop: any) => void;
|
|
||||||
label: string;
|
|
||||||
showIcon?: boolean;
|
|
||||||
}): React.ReactElement | null {
|
|
||||||
const { colors } = useTheme();
|
|
||||||
const color = danger ? colors.dangerColor : colors.actionTintColor;
|
|
||||||
|
|
||||||
if (showIcon)
|
|
||||||
return (
|
|
||||||
<BorderlessButton testID={`room-info-view-${iconName}`} onPress={onPress} style={styles.roomButton}>
|
|
||||||
<CustomIcon name={iconName} size={30} color={color} />
|
|
||||||
<Text style={[styles.roomButtonText, { color }]}>{label}</Text>
|
|
||||||
</BorderlessButton>
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CallButton({ rid, isDirect }: { rid: string; isDirect: boolean }): React.ReactElement | null {
|
|
||||||
const { showCallOption, showInitCallActionSheet } = useVideoConf(rid);
|
|
||||||
const serverVersion = useAppSelector(state => state.server.version);
|
|
||||||
const greaterThanFive = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.0.0');
|
|
||||||
|
|
||||||
const showIcon = greaterThanFive ? showCallOption : showCallOption && isDirect;
|
|
||||||
|
|
||||||
return <UserInfoButton onPress={showInitCallActionSheet} iconName='phone' label={i18n.t('Call')} showIcon={showIcon} />;
|
|
||||||
}
|
|
|
@ -1,43 +1,43 @@
|
||||||
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native';
|
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, Text, View } from 'react-native';
|
import { ScrollView, Text, View } from 'react-native';
|
||||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
|
||||||
import UAParser from 'ua-parser-js';
|
import UAParser from 'ua-parser-js';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native';
|
||||||
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import Avatar from '../../containers/Avatar';
|
|
||||||
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
|
||||||
import { MarkdownPreview } from '../../containers/markdown';
|
|
||||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
|
||||||
import Status from '../../containers/Status';
|
import Status from '../../containers/Status';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
|
||||||
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
|
||||||
import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
|
||||||
import I18n from '../../i18n';
|
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription';
|
|
||||||
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
|
||||||
import EventEmitter from '../../lib/methods/helpers/events';
|
|
||||||
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
|
||||||
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
|
|
||||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
|
||||||
import { Services } from '../../lib/services';
|
|
||||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
|
||||||
import { ChatsStackParamList } from '../../stacks/types';
|
|
||||||
import { TSupportedThemes, withTheme } from '../../theme';
|
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import Channel from './Channel';
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
import { CallButton } from './components/UserInfoButton';
|
import I18n from '../../i18n';
|
||||||
import Direct from './Direct';
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
|
import StatusBar from '../../containers/StatusBar';
|
||||||
|
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
|
import { themes } from '../../lib/constants';
|
||||||
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
|
import { MarkdownPreview } from '../../containers/markdown';
|
||||||
|
import { LISTENER } from '../../containers/Toast';
|
||||||
|
import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
|
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
||||||
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import Livechat from './Livechat';
|
import Livechat from './Livechat';
|
||||||
|
import Channel from './Channel';
|
||||||
|
import Direct from './Direct';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
|
import { SubscriptionType, TSubscriptionModel, ISubscription, IUser, IApplicationState } from '../../definitions';
|
||||||
|
import { ILivechatVisitor } from '../../definitions/ILivechatVisitor';
|
||||||
|
import { callJitsi } from '../../lib/methods';
|
||||||
|
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
||||||
|
import { Services } from '../../lib/services';
|
||||||
|
import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription';
|
||||||
|
import { handleIgnore } from '../../lib/methods/helpers/handleIgnore';
|
||||||
|
|
||||||
interface IGetRoomTitle {
|
interface IGetRoomTitle {
|
||||||
room: ISubscription;
|
room: ISubscription;
|
||||||
|
@ -386,6 +386,11 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
videoCall = () => {
|
||||||
|
const { room } = this.state;
|
||||||
|
callJitsi(room);
|
||||||
|
};
|
||||||
|
|
||||||
handleBlockUser = async (rid: string, blocked: string, block: boolean) => {
|
handleBlockUser = async (rid: string, blocked: string, block: boolean) => {
|
||||||
logEvent(events.RI_TOGGLE_BLOCK_USER);
|
logEvent(events.RI_TOGGLE_BLOCK_USER);
|
||||||
try {
|
try {
|
||||||
|
@ -420,7 +425,8 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
||||||
};
|
};
|
||||||
|
|
||||||
renderButtons = () => {
|
renderButtons = () => {
|
||||||
const { roomFromRid, roomUser, room } = this.state;
|
const { roomFromRid, roomUser } = this.state;
|
||||||
|
const { jitsiEnabled } = this.props;
|
||||||
|
|
||||||
const isFromDm = roomFromRid?.rid ? new RegExp(roomUser._id).test(roomFromRid.rid) : false;
|
const isFromDm = roomFromRid?.rid ? new RegExp(roomUser._id).test(roomFromRid.rid) : false;
|
||||||
const isDirectFromSaved = this.isDirect && this.fromRid && roomFromRid;
|
const isDirectFromSaved = this.isDirect && this.fromRid && roomFromRid;
|
||||||
|
@ -436,7 +442,9 @@ class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewStat
|
||||||
return (
|
return (
|
||||||
<View style={styles.roomButtonsContainer}>
|
<View style={styles.roomButtonsContainer}>
|
||||||
{this.renderButton(() => this.handleCreateDirectMessage(this.goRoom), 'message', I18n.t('Message'))}
|
{this.renderButton(() => this.handleCreateDirectMessage(this.goRoom), 'message', I18n.t('Message'))}
|
||||||
<CallButton isDirect={this.isDirect} rid={room.rid} />
|
{jitsiEnabled && this.isDirect
|
||||||
|
? this.renderButton(() => this.handleCreateDirectMessage(this.videoCall), 'camera', I18n.t('Video_call'))
|
||||||
|
: null}
|
||||||
{isDirectFromSaved && !isFromDm && !isDmWithMyself
|
{isDirectFromSaved && !isFromDm && !isDmWithMyself
|
||||||
? this.renderButton(
|
? this.renderButton(
|
||||||
() => handleIgnore(roomUser._id, !isIgnored, roomFromRid.rid),
|
() => handleIgnore(roomUser._id, !isIgnored, roomFromRid.rid),
|
||||||
|
|
|
@ -75,8 +75,7 @@ export default function ActionsSection({ rid, t, joined }: IActionsSection): Rea
|
||||||
route: 'SelectedUsersView',
|
route: 'SelectedUsersView',
|
||||||
params: {
|
params: {
|
||||||
title: i18n.t('Add_users'),
|
title: i18n.t('Add_users'),
|
||||||
nextAction: addUser,
|
nextAction: addUser
|
||||||
showSkipText: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
import { IApplicationState, ISubscription, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions';
|
import { isTeamRoom } from '../../lib/methods/helpers/room';
|
||||||
|
import { IApplicationState, SubscriptionType, TMessageModel, TSubscriptionModel } from '../../definitions';
|
||||||
import { ChatsStackParamList } from '../../stacks/types';
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
import { TActionSheetOptionsItem } from '../../containers/ActionSheet';
|
import { TActionSheetOptionsItem } from '../../containers/ActionSheet';
|
||||||
import i18n from '../../i18n';
|
import i18n from '../../i18n';
|
||||||
|
@ -19,11 +20,12 @@ import { onHoldLivechat, returnLivechat } from '../../lib/services/restApi';
|
||||||
import { closeLivechat as closeLivechatService } from '../../lib/methods/helpers/closeLivechat';
|
import { closeLivechat as closeLivechatService } from '../../lib/methods/helpers/closeLivechat';
|
||||||
import { Services } from '../../lib/services';
|
import { Services } from '../../lib/services';
|
||||||
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
import { ILivechatDepartment } from '../../definitions/ILivechatDepartment';
|
||||||
import HeaderCallButton from './components/HeaderCallButton';
|
|
||||||
|
|
||||||
interface IRightButtonsProps extends Pick<ISubscription, 't'> {
|
interface IRightButtonsProps {
|
||||||
userId?: string;
|
userId?: string;
|
||||||
threadsEnabled: boolean;
|
threadsEnabled: boolean;
|
||||||
|
rid?: string;
|
||||||
|
t: string;
|
||||||
tmid?: string;
|
tmid?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
|
@ -41,7 +43,6 @@ interface IRightButtonsProps extends Pick<ISubscription, 't'> {
|
||||||
livechatRequestComment: boolean;
|
livechatRequestComment: boolean;
|
||||||
showActionSheet: Function;
|
showActionSheet: Function;
|
||||||
departmentId?: string;
|
departmentId?: string;
|
||||||
rid?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRigthButtonsState {
|
interface IRigthButtonsState {
|
||||||
|
@ -337,7 +338,7 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state;
|
const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state;
|
||||||
const { t, tmid, threadsEnabled, rid } = this.props;
|
const { t, tmid, threadsEnabled, teamId, joined } = this.props;
|
||||||
|
|
||||||
if (t === 'l') {
|
if (t === 'l') {
|
||||||
if (!this.isOmnichannelPreview()) {
|
if (!this.isOmnichannelPreview()) {
|
||||||
|
@ -362,13 +363,15 @@ class RightButtonsContainer extends Component<IRightButtonsProps, IRigthButtonsS
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<HeaderButton.Container>
|
<HeaderButton.Container>
|
||||||
{rid ? <HeaderCallButton rid={rid} /> : null}
|
{isTeamRoom({ teamId, joined }) ? (
|
||||||
|
<HeaderButton.Item iconName='channel-public' onPress={this.goTeamChannels} testID='room-view-header-team-channels' />
|
||||||
|
) : null}
|
||||||
{threadsEnabled ? (
|
{threadsEnabled ? (
|
||||||
<HeaderButton.Item
|
<HeaderButton.Item
|
||||||
iconName='threads'
|
iconName='threads'
|
||||||
onPress={this.goThreadsView}
|
onPress={this.goThreadsView}
|
||||||
testID='room-view-header-threads'
|
testID='room-view-header-threads'
|
||||||
badge={() => <HeaderButton.BadgeUnread tunread={tunread} tunreadUser={tunreadUser} tunreadGroup={tunreadGroup} />}
|
badge={() => <HeaderButton.Badge tunread={tunread} tunreadUser={tunreadUser} tunreadGroup={tunreadGroup} />}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<HeaderButton.Item iconName='search' onPress={this.goSearchView} testID='room-view-search' />
|
<HeaderButton.Item iconName='search' onPress={this.goSearchView} testID='room-view-search' />
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import * as HeaderButton from '../../../containers/HeaderButton';
|
|
||||||
import { useVideoConf } from '../../../lib/hooks/useVideoConf';
|
|
||||||
|
|
||||||
export default function HeaderCallButton({ rid }: { rid: string }): React.ReactElement | null {
|
|
||||||
const { showInitCallActionSheet, showCallOption } = useVideoConf(rid);
|
|
||||||
|
|
||||||
if (showCallOption)
|
|
||||||
return <HeaderButton.Item iconName='phone' onPress={showInitCallActionSheet} testID='room-view-header-call' />;
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -627,7 +627,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
joined={joined}
|
joined={joined}
|
||||||
status={room.status}
|
status={room.status}
|
||||||
omnichannelPermissions={omnichannelPermissions}
|
omnichannelPermissions={omnichannelPermissions}
|
||||||
t={(this.t || t) as SubscriptionType}
|
t={this.t || t}
|
||||||
encrypted={encrypted}
|
encrypted={encrypted}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
toggleFollowThread={this.toggleFollowThread}
|
toggleFollowThread={this.toggleFollowThread}
|
||||||
|
@ -785,22 +785,13 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCloseEmoji = (action?: Function, params?: any) => {
|
|
||||||
if (this.messagebox?.current) {
|
|
||||||
return this.messagebox?.current.closeEmojiAndAction(action, params);
|
|
||||||
}
|
|
||||||
if (action) {
|
|
||||||
return action(params);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
errorActionsShow = (message: TAnyMessageModel) => {
|
errorActionsShow = (message: TAnyMessageModel) => {
|
||||||
this.handleCloseEmoji(this.messageErrorActions?.showMessageErrorActions, message);
|
this.messagebox?.current?.closeEmojiAndAction(this.messageErrorActions?.showMessageErrorActions, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
showActionSheet = (options: any) => {
|
showActionSheet = (options: any) => {
|
||||||
const { showActionSheet } = this.props;
|
const { showActionSheet } = this.props;
|
||||||
this.handleCloseEmoji(showActionSheet, options);
|
this.messagebox?.current?.closeEmojiAndAction(showActionSheet, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
onEditInit = (message: TAnyMessageModel) => {
|
onEditInit = (message: TAnyMessageModel) => {
|
||||||
|
@ -859,7 +850,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
onReactionInit = (message: TAnyMessageModel) => {
|
onReactionInit = (message: TAnyMessageModel) => {
|
||||||
this.handleCloseEmoji(() => {
|
this.messagebox?.current?.closeEmojiAndAction(() => {
|
||||||
this.setState({ selectedMessage: message }, this.showReactionPicker);
|
this.setState({ selectedMessage: message }, this.showReactionPicker);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -874,7 +865,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
if (message.tmid && !this.tmid) {
|
if (message.tmid && !this.tmid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.handleCloseEmoji(this.messageActions?.showMessageActions, message);
|
this.messagebox?.current?.closeEmojiAndAction(this.messageActions?.showMessageActions, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
showAttachment = (attachment: IAttachment) => {
|
showAttachment = (attachment: IAttachment) => {
|
||||||
|
@ -903,7 +894,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
this.setState({ selectedMessage: message });
|
this.setState({ selectedMessage: message });
|
||||||
const { showActionSheet } = this.props;
|
const { showActionSheet } = this.props;
|
||||||
const { selectedMessage } = this.state;
|
const { selectedMessage } = this.state;
|
||||||
this.handleCloseEmoji(showActionSheet, {
|
this.messagebox?.current?.closeEmojiAndAction(showActionSheet, {
|
||||||
children: <ReactionsList reactions={selectedMessage?.reactions} getCustomEmoji={this.getCustomEmoji} />,
|
children: <ReactionsList reactions={selectedMessage?.reactions} getCustomEmoji={this.getCustomEmoji} />,
|
||||||
snaps: ['50%', '80%'],
|
snaps: ['50%', '80%'],
|
||||||
enableContentPanningGesture: false
|
enableContentPanningGesture: false
|
||||||
|
@ -1197,11 +1188,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
if ('id' in item && item.t === E2E_MESSAGE_TYPE && item.e2e !== E2E_STATUS.DONE) {
|
if ('id' in item && item.t === E2E_MESSAGE_TYPE && item.e2e !== E2E_STATUS.DONE) {
|
||||||
name = I18n.t('Encrypted_message');
|
name = I18n.t('Encrypted_message');
|
||||||
}
|
}
|
||||||
if (!jumpToMessageId) {
|
|
||||||
setTimeout(() => {
|
|
||||||
sendLoadingEvent({ visible: false });
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
return navigation.push('RoomView', {
|
return navigation.push('RoomView', {
|
||||||
rid: this.rid,
|
rid: this.rid,
|
||||||
tmid: item.tmid,
|
tmid: item.tmid,
|
||||||
|
@ -1234,15 +1220,14 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// OLD METHOD - support versions before 5.0.0
|
handleCallJitsi = () => {
|
||||||
handleEnterCall = () => {
|
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
if ('id' in room) {
|
if ('id' in room) {
|
||||||
const { jitsiTimeout } = room;
|
const { jitsiTimeout } = room;
|
||||||
if (jitsiTimeout && jitsiTimeout < new Date()) {
|
if (jitsiTimeout && jitsiTimeout < new Date()) {
|
||||||
showErrorAlert(I18n.t('Call_already_ended'));
|
showErrorAlert(I18n.t('Call_already_ended'));
|
||||||
} else {
|
} else {
|
||||||
callJitsi({ room });
|
callJitsi(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1388,14 +1373,14 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
autoTranslateLanguage={'id' in room ? room.autoTranslateLanguage : undefined}
|
autoTranslateLanguage={'id' in room ? room.autoTranslateLanguage : undefined}
|
||||||
navToRoomInfo={this.navToRoomInfo}
|
navToRoomInfo={this.navToRoomInfo}
|
||||||
getCustomEmoji={this.getCustomEmoji}
|
getCustomEmoji={this.getCustomEmoji}
|
||||||
handleEnterCall={this.handleEnterCall}
|
callJitsi={this.handleCallJitsi}
|
||||||
blockAction={this.blockAction}
|
blockAction={this.blockAction}
|
||||||
threadBadgeColor={this.getBadgeColor(item?.id)}
|
threadBadgeColor={this.getBadgeColor(item?.id)}
|
||||||
toggleFollowThread={this.toggleFollowThread}
|
toggleFollowThread={this.toggleFollowThread}
|
||||||
jumpToMessage={this.jumpToMessageByUrl}
|
jumpToMessage={this.jumpToMessageByUrl}
|
||||||
highlighted={highlightedMessage === item.id}
|
highlighted={highlightedMessage === item.id}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
closeEmojiAndAction={this.handleCloseEmoji}
|
closeEmojiAndAction={this.messagebox?.current?.closeEmojiAndAction}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,6 @@ interface IRoomsListViewProps {
|
||||||
StoreLastMessage: boolean;
|
StoreLastMessage: boolean;
|
||||||
useRealName: boolean;
|
useRealName: boolean;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
notificationPresenceCap: boolean;
|
|
||||||
subscribedRoom: string;
|
subscribedRoom: string;
|
||||||
width: number;
|
width: number;
|
||||||
insets: {
|
insets: {
|
||||||
|
@ -147,7 +146,6 @@ const shouldUpdateProps = [
|
||||||
'StoreLastMessage',
|
'StoreLastMessage',
|
||||||
'theme',
|
'theme',
|
||||||
'isMasterDetail',
|
'isMasterDetail',
|
||||||
'notificationPresenceCap',
|
|
||||||
'refreshing',
|
'refreshing',
|
||||||
'queueSize',
|
'queueSize',
|
||||||
'inquiryEnabled',
|
'inquiryEnabled',
|
||||||
|
@ -257,18 +255,21 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: IRoomsListViewProps, nextState: IRoomsListViewState) {
|
shouldComponentUpdate(nextProps: IRoomsListViewProps, nextState: IRoomsListViewState) {
|
||||||
const { chatsUpdate, searching, item, canCreateRoom, omnichannelsUpdate } = this.state;
|
const { chatsUpdate, searching, item, canCreateRoom, omnichannelsUpdate } = this.state;
|
||||||
|
// eslint-disable-next-line react/destructuring-assignment
|
||||||
const propsUpdated = shouldUpdateProps.some(key => nextProps[key] !== this.props[key]);
|
const propsUpdated = shouldUpdateProps.some(key => nextProps[key] !== this.props[key]);
|
||||||
if (propsUpdated) {
|
if (propsUpdated) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if some display props are changed to force update when focus this view again
|
// check if some display props are changed to force update when focus this view again
|
||||||
|
// eslint-disable-next-line react/destructuring-assignment
|
||||||
const displayUpdated = displayPropsShouldUpdate.some(key => nextProps[key] !== this.props[key]);
|
const displayUpdated = displayPropsShouldUpdate.some(key => nextProps[key] !== this.props[key]);
|
||||||
if (displayUpdated) {
|
if (displayUpdated) {
|
||||||
this.shouldUpdate = true;
|
this.shouldUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if some sort preferences are changed to getSubscription() when focus this view again
|
// check if some sort preferences are changed to getSubscription() when focus this view again
|
||||||
|
// eslint-disable-next-line react/destructuring-assignment
|
||||||
const sortPreferencesUpdate = sortPreferencesShouldUpdate.some(key => nextProps[key] !== this.props[key]);
|
const sortPreferencesUpdate = sortPreferencesShouldUpdate.some(key => nextProps[key] !== this.props[key]);
|
||||||
if (sortPreferencesUpdate) {
|
if (sortPreferencesUpdate) {
|
||||||
this.sortPreferencesChanged = true;
|
this.sortPreferencesChanged = true;
|
||||||
|
@ -338,7 +339,6 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
showUnread,
|
showUnread,
|
||||||
subscribedRoom,
|
subscribedRoom,
|
||||||
isMasterDetail,
|
isMasterDetail,
|
||||||
notificationPresenceCap,
|
|
||||||
insets,
|
insets,
|
||||||
createTeamPermission,
|
createTeamPermission,
|
||||||
createPublicChannelPermission,
|
createPublicChannelPermission,
|
||||||
|
@ -366,11 +366,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
if (isMasterDetail && item?.rid !== subscribedRoom && subscribedRoom !== prevProps.subscribedRoom) {
|
if (isMasterDetail && item?.rid !== subscribedRoom && subscribedRoom !== prevProps.subscribedRoom) {
|
||||||
this.setState({ item: { rid: subscribedRoom } as ISubscription });
|
this.setState({ item: { rid: subscribedRoom } as ISubscription });
|
||||||
}
|
}
|
||||||
if (
|
if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) {
|
||||||
insets.left !== prevProps.insets.left ||
|
|
||||||
insets.right !== prevProps.insets.right ||
|
|
||||||
notificationPresenceCap !== prevProps.notificationPresenceCap
|
|
||||||
) {
|
|
||||||
this.setHeader();
|
this.setHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,7 +421,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
|
|
||||||
getHeader = (): StackNavigationOptions => {
|
getHeader = (): StackNavigationOptions => {
|
||||||
const { searching, canCreateRoom } = this.state;
|
const { searching, canCreateRoom } = this.state;
|
||||||
const { navigation, isMasterDetail, notificationPresenceCap } = this.props;
|
const { navigation, isMasterDetail } = this.props;
|
||||||
if (searching) {
|
if (searching) {
|
||||||
return {
|
return {
|
||||||
headerTitleAlign: 'left',
|
headerTitleAlign: 'left',
|
||||||
|
@ -455,7 +451,6 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
: // @ts-ignore
|
: // @ts-ignore
|
||||||
() => navigation.toggleDrawer()
|
() => navigation.toggleDrawer()
|
||||||
}
|
}
|
||||||
badge={() => (notificationPresenceCap ? <HeaderButton.BadgeWarn /> : null)}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
headerTitle: () => <RoomsListHeaderView />,
|
headerTitle: () => <RoomsListHeaderView />,
|
||||||
|
@ -1039,7 +1034,6 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
notificationPresenceCap: state.app.notificationPresenceCap,
|
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
changingServer: state.server.changingServer,
|
changingServer: state.server.changingServer,
|
||||||
searchText: state.rooms.searchText,
|
searchText: state.rooms.searchText,
|
||||||
|
|
|
@ -34,7 +34,7 @@ const SelectedUsersView = () => {
|
||||||
const [chats, setChats] = useState<ISelectedUser[]>([]);
|
const [chats, setChats] = useState<ISelectedUser[]>([]);
|
||||||
const [search, setSearch] = useState<TSearch[]>([]);
|
const [search, setSearch] = useState<TSearch[]>([]);
|
||||||
|
|
||||||
const { maxUsers, showButton, title, buttonText, showSkipText = true, nextAction } = useRoute<TRoute>().params;
|
const { maxUsers, showButton, title, buttonText, nextAction } = useRoute<TRoute>().params;
|
||||||
const navigation = useNavigation<TNavigation>();
|
const navigation = useNavigation<TNavigation>();
|
||||||
|
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
@ -58,25 +58,20 @@ const SelectedUsersView = () => {
|
||||||
|
|
||||||
const isGroupChat = () => maxUsers && maxUsers > 2;
|
const isGroupChat = () => maxUsers && maxUsers > 2;
|
||||||
|
|
||||||
const handleButtonTitle = (buttonTextHeader: string) => {
|
|
||||||
if (users.length > 0) {
|
|
||||||
return buttonTextHeader;
|
|
||||||
}
|
|
||||||
return showSkipText ? I18n.t('Skip') : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const titleHeader = title ?? I18n.t('Select_Members');
|
const titleHeader = title ?? I18n.t('Select_Members');
|
||||||
const buttonTextHeader = buttonText ?? I18n.t('Next');
|
const buttonTextHeader = buttonText ?? I18n.t('Next');
|
||||||
const nextActionHeader = nextAction ?? (() => {});
|
const nextActionHeader = nextAction ?? (() => {});
|
||||||
const buttonTitle = handleButtonTitle(buttonTextHeader);
|
|
||||||
const options = {
|
const options = {
|
||||||
title: titleHeader,
|
title: titleHeader,
|
||||||
headerRight: () =>
|
headerRight: () =>
|
||||||
(!maxUsers || showButton || (isGroupChat() && users.length > 1)) &&
|
(!maxUsers || showButton || (isGroupChat() && users.length > 1)) && (
|
||||||
!!buttonTitle && (
|
|
||||||
<HeaderButton.Container>
|
<HeaderButton.Container>
|
||||||
<HeaderButton.Item title={buttonTitle} onPress={nextActionHeader} testID='selected-users-view-submit' />
|
<HeaderButton.Item
|
||||||
|
title={users.length > 0 ? buttonTextHeader : I18n.t('Skip')}
|
||||||
|
onPress={nextActionHeader}
|
||||||
|
testID='selected-users-view-submit'
|
||||||
|
/>
|
||||||
</HeaderButton.Container>
|
</HeaderButton.Container>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
@ -250,8 +250,7 @@ class ShareListView extends React.Component<IShareListViewProps, IState> {
|
||||||
prid: item.prid,
|
prid: item.prid,
|
||||||
uids: item.uids,
|
uids: item.uids,
|
||||||
usernames: item.usernames,
|
usernames: item.usernames,
|
||||||
topic: item.topic,
|
topic: item.topic
|
||||||
teamMain: item.teamMain
|
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -381,7 +380,6 @@ class ShareListView extends React.Component<IShareListViewProps, IState> {
|
||||||
type={item.prid ? 'discussion' : item.t}
|
type={item.prid ? 'discussion' : item.t}
|
||||||
onPress={() => this.shareMessage(item)}
|
onPress={() => this.shareMessage(item)}
|
||||||
testID={`share-extension-item-${item.name}`}
|
testID={`share-extension-item-${item.name}`}
|
||||||
teamMain={item.teamMain}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { DrawerNavigationProp } from '@react-navigation/drawer';
|
import { DrawerNavigationProp } from '@react-navigation/drawer';
|
||||||
import { DrawerNavigationState } from '@react-navigation/native';
|
import { DrawerNavigationState } from '@react-navigation/native';
|
||||||
import { Alert, ScrollView, Text, TouchableWithoutFeedback, View, Linking } from 'react-native';
|
import { ScrollView, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import { Dispatch } from 'redux';
|
|
||||||
|
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import Status from '../../containers/Status/Status';
|
import Status from '../../containers/Status/Status';
|
||||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||||
import userPreferences from '../../lib/methods/userPreferences';
|
|
||||||
import { CustomIcon } from '../../containers/CustomIcon';
|
import { CustomIcon } from '../../containers/CustomIcon';
|
||||||
import { NOTIFICATION_PRESENCE_CAP, STATUS_COLORS, themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { TSupportedThemes, withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
|
@ -23,7 +21,6 @@ import styles from './styles';
|
||||||
import { DrawerParamList } from '../../stacks/types';
|
import { DrawerParamList } from '../../stacks/types';
|
||||||
import { IApplicationState, IUser } from '../../definitions';
|
import { IApplicationState, IUser } from '../../definitions';
|
||||||
import * as List from '../../containers/List';
|
import * as List from '../../containers/List';
|
||||||
import { setNotificationPresenceCap } from '../../actions/app';
|
|
||||||
|
|
||||||
interface ISidebarState {
|
interface ISidebarState {
|
||||||
showStatus: boolean;
|
showStatus: boolean;
|
||||||
|
@ -32,7 +29,6 @@ interface ISidebarState {
|
||||||
interface ISidebarProps {
|
interface ISidebarProps {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
navigation?: DrawerNavigationProp<DrawerParamList>;
|
navigation?: DrawerNavigationProp<DrawerParamList>;
|
||||||
dispatch: Dispatch;
|
|
||||||
state?: DrawerNavigationState<DrawerParamList>;
|
state?: DrawerNavigationState<DrawerParamList>;
|
||||||
Site_Name: string;
|
Site_Name: string;
|
||||||
user: IUser;
|
user: IUser;
|
||||||
|
@ -40,8 +36,6 @@ interface ISidebarProps {
|
||||||
loadingServer: boolean;
|
loadingServer: boolean;
|
||||||
useRealName: boolean;
|
useRealName: boolean;
|
||||||
allowStatusMessage: boolean;
|
allowStatusMessage: boolean;
|
||||||
notificationPresenceCap: boolean;
|
|
||||||
Presence_broadcast_disabled: boolean;
|
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
viewStatisticsPermission: string[];
|
viewStatisticsPermission: string[];
|
||||||
viewRoomAdministrationPermission: string[];
|
viewRoomAdministrationPermission: string[];
|
||||||
|
@ -65,10 +59,8 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||||
baseUrl,
|
baseUrl,
|
||||||
state,
|
state,
|
||||||
isMasterDetail,
|
isMasterDetail,
|
||||||
notificationPresenceCap,
|
|
||||||
useRealName,
|
useRealName,
|
||||||
theme,
|
theme,
|
||||||
Presence_broadcast_disabled,
|
|
||||||
viewStatisticsPermission,
|
viewStatisticsPermission,
|
||||||
viewRoomAdministrationPermission,
|
viewRoomAdministrationPermission,
|
||||||
viewUserAdministrationPermission,
|
viewUserAdministrationPermission,
|
||||||
|
@ -99,15 +91,9 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||||
if (nextProps.isMasterDetail !== isMasterDetail) {
|
if (nextProps.isMasterDetail !== isMasterDetail) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextProps.notificationPresenceCap !== notificationPresenceCap) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.useRealName !== useRealName) {
|
if (nextProps.useRealName !== useRealName) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextProps.Presence_broadcast_disabled !== Presence_broadcast_disabled) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!dequal(nextProps.viewStatisticsPermission, viewStatisticsPermission)) {
|
if (!dequal(nextProps.viewStatisticsPermission, viewStatisticsPermission)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -170,33 +156,6 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||||
navigation?.closeDrawer();
|
navigation?.closeDrawer();
|
||||||
};
|
};
|
||||||
|
|
||||||
onPressLearnMorePresenceCap = () => {
|
|
||||||
Linking.openURL('https://go.rocket.chat/i/presence-cap-learn-more');
|
|
||||||
};
|
|
||||||
|
|
||||||
onPressPresenceLearnMore = () => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
dispatch(setNotificationPresenceCap(false));
|
|
||||||
userPreferences.setBool(NOTIFICATION_PRESENCE_CAP, false);
|
|
||||||
|
|
||||||
Alert.alert(
|
|
||||||
I18n.t('Presence_Cap_Warning_Title'),
|
|
||||||
I18n.t('Presence_Cap_Warning_Description'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: I18n.t('Learn_more'),
|
|
||||||
onPress: this.onPressLearnMorePresenceCap,
|
|
||||||
style: 'cancel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: I18n.t('Close'),
|
|
||||||
style: 'default'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
{ cancelable: false }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderAdmin = () => {
|
renderAdmin = () => {
|
||||||
const { theme, isMasterDetail } = this.props;
|
const { theme, isMasterDetail } = this.props;
|
||||||
if (!this.getIsAdmin()) {
|
if (!this.getIsAdmin()) {
|
||||||
|
@ -260,27 +219,14 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderCustomStatus = () => {
|
renderCustomStatus = () => {
|
||||||
const { user, theme, Presence_broadcast_disabled, notificationPresenceCap } = this.props;
|
const { user, theme } = this.props;
|
||||||
|
|
||||||
let status = user?.status;
|
|
||||||
if (Presence_broadcast_disabled) {
|
|
||||||
status = 'disabled';
|
|
||||||
}
|
|
||||||
|
|
||||||
let right: React.ReactElement | undefined = <CustomIcon name='edit' size={20} color={themes[theme!].titleText} />;
|
|
||||||
if (notificationPresenceCap) {
|
|
||||||
right = <View style={[styles.customStatusDisabled, { backgroundColor: STATUS_COLORS.disabled }]} />;
|
|
||||||
} else if (Presence_broadcast_disabled) {
|
|
||||||
right = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
text={user.statusText || I18n.t('Edit_Status')}
|
text={user.statusText || I18n.t('Edit_Status')}
|
||||||
left={<Status size={24} status={status} />}
|
left={<Status size={24} status={user?.status} />}
|
||||||
theme={theme!}
|
theme={theme!}
|
||||||
right={right}
|
right={<CustomIcon name='edit' size={20} color={themes[theme!].titleText} />}
|
||||||
onPress={() => (Presence_broadcast_disabled ? this.onPressPresenceLearnMore() : this.sidebarNavigate('StatusView'))}
|
onPress={() => this.sidebarNavigate('StatusView')}
|
||||||
testID={`sidebar-custom-status-${user.status}`}
|
testID={`sidebar-custom-status-${user.status}`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -348,8 +294,6 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
loadingServer: state.server.loading,
|
loadingServer: state.server.loading,
|
||||||
useRealName: state.settings.UI_Use_Real_Name as boolean,
|
useRealName: state.settings.UI_Use_Real_Name as boolean,
|
||||||
allowStatusMessage: state.settings.Accounts_AllowUserStatusMessageChange as boolean,
|
allowStatusMessage: state.settings.Accounts_AllowUserStatusMessageChange as boolean,
|
||||||
Presence_broadcast_disabled: state.settings.Presence_broadcast_disabled as boolean,
|
|
||||||
notificationPresenceCap: state.app.notificationPresenceCap,
|
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
viewStatisticsPermission: state.permissions['view-statistics'] as string[],
|
viewStatisticsPermission: state.permissions['view-statistics'] as string[],
|
||||||
viewRoomAdministrationPermission: state.permissions['view-room-administration'] as string[],
|
viewRoomAdministrationPermission: state.permissions['view-room-administration'] as string[],
|
||||||
|
|
|
@ -63,10 +63,5 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
inverted: {
|
inverted: {
|
||||||
transform: [{ scaleY: -1 }]
|
transform: [{ scaleY: -1 }]
|
||||||
},
|
|
||||||
customStatusDisabled: {
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
borderRadius: 5
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue