Compare commits
89 Commits
develop
...
chore.try-
Author | SHA1 | Date |
---|---|---|
Diego Mello | 891b910772 | |
Diego Mello | d047cb0c49 | |
Diego Mello | 83b5e8a98a | |
Diego Mello | d860991c87 | |
Diego Mello | 7e849943fe | |
Diego Mello | 2b06b01bd6 | |
Diego Mello | 8678d079bc | |
Diego Mello | ff00ad169e | |
Diego Mello | 4e11189f7f | |
Diego Mello | 428fb5dccc | |
Diego Mello | ab53a5aaea | |
Diego Mello | 4dea3e7ce8 | |
Diego Mello | 868bfb7a4c | |
Diego Mello | e088d76724 | |
Diego Mello | e6ec8b4e39 | |
Diego Mello | 2683976d21 | |
Diego Mello | 976753c08b | |
Diego Mello | fd82771438 | |
Diego Mello | c610b6d7ec | |
Diego Mello | f71be55140 | |
Diego Mello | 8c9f7ffa54 | |
Diego Mello | 001f2a0c17 | |
Diego Mello | ffb31d399d | |
Diego Mello | 15bb21eb2f | |
Diego Mello | 9788e8a11f | |
Diego Mello | 2c62d9cb46 | |
Diego Mello | ae7398233a | |
Diego Mello | 7b526fe700 | |
Diego Mello | 654feb719c | |
Diego Mello | dbb9396042 | |
Diego Mello | c877a0600b | |
Diego Mello | 696a7e623d | |
Diego Mello | da113ac1d0 | |
Diego Mello | 1af737ee71 | |
Diego Mello | c867f2928f | |
Diego Mello | 87a5793f24 | |
Diego Mello | f5615c50f6 | |
Diego Mello | b2db5500b6 | |
Diego Mello | c6ecfa17a3 | |
Diego Mello | 595f010742 | |
Diego Mello | 0c4c361446 | |
Diego Mello | 53cda7ecb1 | |
Diego Mello | 04e49563f2 | |
Diego Mello | 36f0935e55 | |
Diego Mello | 2cefe12a91 | |
Diego Mello | 21dbc7e112 | |
Diego Mello | aaef77a58f | |
Diego Mello | cc5fe4c910 | |
Diego Mello | a593129a3c | |
Diego Mello | de6a897ec4 | |
Diego Mello | d42f623a2f | |
Diego Mello | 4aeda3f778 | |
Diego Mello | 34a30214c2 | |
Diego Mello | 79be07072c | |
Diego Mello | a73a535221 | |
Diego Mello | 4ca5e3a0a7 | |
Diego Mello | c0b3daa225 | |
Diego Mello | a130583ff4 | |
Diego Mello | 0b7c075560 | |
Diego Mello | edc67d424b | |
Diego Mello | fad04765d4 | |
Diego Mello | 7beaa62003 | |
Diego Mello | f67a4010bc | |
Diego Mello | 2ee4efd255 | |
Diego Mello | 4532a6929c | |
Diego Mello | 71dc0b8f50 | |
Diego Mello | 8ab84541f3 | |
Diego Mello | 363331c953 | |
Diego Mello | 360afcdd45 | |
Diego Mello | 83701d002f | |
Diego Mello | c0d5a84ccd | |
Diego Mello | 6b7b01af8d | |
Diego Mello | bac9eb79bb | |
Diego Mello | b9cafe9d31 | |
Diego Mello | a38bc37aaa | |
Diego Mello | 490e567c85 | |
Diego Mello | 45042661c5 | |
Diego Mello | e0b5030f6a | |
Diego Mello | 1ea3916e94 | |
Diego Mello | 537cd0032e | |
Diego Mello | e4f0fdb525 | |
Diego Mello | 86a7297abc | |
Diego Mello | 34f2da40aa | |
Diego Mello | 0317c4c27a | |
Diego Mello | 898a4a398b | |
Diego Mello | 6bb7db9993 | |
Diego Mello | 86d5529cfd | |
Diego Mello | 7bd4182bf7 | |
Diego Mello | b20c9b1632 |
|
@ -1,12 +1,9 @@
|
|||
defaults: &defaults
|
||||
working_directory: ~/repo
|
||||
|
||||
orbs:
|
||||
android: circleci/android@2.1.2
|
||||
|
||||
macos: &macos
|
||||
macos:
|
||||
xcode: "14.2.0"
|
||||
xcode: "13.3.0"
|
||||
resource_class: large
|
||||
|
||||
bash-env: &bash-env
|
||||
|
@ -54,14 +51,14 @@ save-gems-cache: &save-gems-cache
|
|||
update-fastlane-ios: &update-fastlane-ios
|
||||
name: Update Fastlane
|
||||
command: |
|
||||
echo "ruby-2.7.7" > ~/.ruby-version
|
||||
echo "ruby-2.6.4" > ~/.ruby-version
|
||||
bundle install
|
||||
working_directory: ios
|
||||
|
||||
update-fastlane-android: &update-fastlane-android
|
||||
name: Update Fastlane
|
||||
command: |
|
||||
echo "ruby-2.7.7" > ~/.ruby-version
|
||||
echo "ruby-2.6.4" > ~/.ruby-version
|
||||
bundle install
|
||||
working_directory: android
|
||||
|
||||
|
@ -121,26 +118,26 @@ commands:
|
|||
if [[ $CIRCLE_JOB == "android-build-official" ]]; then
|
||||
echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties
|
||||
echo -e "BugsnagAPIKey=$BUGSNAG_KEY_OFFICIAL" >> ./gradle.properties
|
||||
echo $KEYSTORE_OFFICIAL_BASE64 | base64 --decode > ./app/$KEYSTORE_OFFICIAL
|
||||
echo $CHAT_ROCKET_ANDROID_STORE_FILE_BASE64_JKS | base64 --decode > ./app/$KEYSTORE_OFFICIAL
|
||||
echo -e "KEYSTORE=$KEYSTORE_OFFICIAL" >> ./gradle.properties
|
||||
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_OFFICIAL_PASSWORD" >> ./gradle.properties
|
||||
echo -e "KEY_ALIAS=$KEYSTORE_OFFICIAL_ALIAS" >> ./gradle.properties
|
||||
echo -e "KEY_PASSWORD=$KEYSTORE_OFFICIAL_PASSWORD" >> ./gradle.properties
|
||||
echo -e "KEYSTORE_PASSWORD=$CHAT_ROCKET_ANDROID_STORE_PASSWORD" >> ./gradle.properties
|
||||
echo -e "KEY_ALIAS=$CHAT_ROCKET_ANDROID_KEY_ALIAS" >> ./gradle.properties
|
||||
echo -e "KEY_PASSWORD=$CHAT_ROCKET_ANDROID_KEY_PASSWORD" >> ./gradle.properties
|
||||
else
|
||||
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
|
||||
echo $KEYSTORE_BASE64 | base64 --decode > ./app/$KEYSTORE
|
||||
echo -e "KEYSTORE=$KEYSTORE" >> ./gradle.properties
|
||||
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
|
||||
echo -e "KEY_ALIAS=$KEY_ALIAS" >> ./gradle.properties
|
||||
echo -e "KEY_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
|
||||
fi
|
||||
working_directory: android
|
||||
|
||||
- run:
|
||||
name: Set Google Services
|
||||
command: |
|
||||
if [[ $GOOGLE_SERVICES_ANDROID ]]; then
|
||||
if [[ $KEYSTORE ]]; then
|
||||
echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json
|
||||
fi
|
||||
working_directory: android/app
|
||||
|
@ -154,7 +151,7 @@ commands:
|
|||
if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
|
||||
./gradlew bundleExperimentalPlayRelease
|
||||
fi
|
||||
if [[ ! $GOOGLE_SERVICES_ANDROID ]]; then
|
||||
if [[ ! $KEYSTORE ]]; then
|
||||
./gradlew assembleExperimentalPlayDebug
|
||||
fi
|
||||
working_directory: android
|
||||
|
@ -203,12 +200,8 @@ commands:
|
|||
- run:
|
||||
name: Set Google Services
|
||||
command: |
|
||||
if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
|
||||
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
|
||||
echo $GOOGLE_SERVICES_IOS | base64 --decode > GoogleService-Info.plist
|
||||
else
|
||||
echo $GOOGLE_SERVICES_IOS_EXPERIMENTAL | base64 --decode > GoogleService-Info.plist
|
||||
fi
|
||||
if [[ $KEYSTORE ]]; then
|
||||
echo $GOOGLE_SERVICES_IOS | base64 --decode > GoogleService-Info.plist
|
||||
fi
|
||||
working_directory: ios
|
||||
- run:
|
||||
|
@ -230,12 +223,12 @@ commands:
|
|||
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist
|
||||
fi
|
||||
|
||||
if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
|
||||
echo $APP_STORE_CONNECT_API_KEY_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
|
||||
if [[ $APP_STORE_CONNECT_API_BASE64 ]]; then
|
||||
echo $APP_STORE_CONNECT_API_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
|
||||
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
|
||||
bundle exec fastlane ios build_official
|
||||
else
|
||||
if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
|
||||
if [[ $KEYSTORE ]]; then
|
||||
bundle exec fastlane ios build_experimental
|
||||
else
|
||||
bundle exec fastlane ios build_fork
|
||||
|
@ -325,19 +318,11 @@ commands:
|
|||
- run:
|
||||
name: Fastlane Tesflight Upload
|
||||
command: |
|
||||
echo $APP_STORE_CONNECT_API_KEY_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
|
||||
echo $APP_STORE_CONNECT_API_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
|
||||
bundle exec fastlane ios beta official:<< parameters.official >>
|
||||
working_directory: ios
|
||||
- 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
|
||||
|
||||
# EXECUTORS
|
||||
|
@ -449,94 +434,6 @@ jobs:
|
|||
- upload-to-google-play-beta:
|
||||
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-build-experimental:
|
||||
executor: mac-env
|
||||
|
@ -560,89 +457,11 @@ jobs:
|
|||
- upload-to-testflight:
|
||||
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:
|
||||
build-and-test:
|
||||
jobs:
|
||||
- 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-hold-build-experimental:
|
||||
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/**'],
|
||||
globals: {
|
||||
by: true,
|
||||
detox: true,
|
||||
device: true,
|
||||
element: true,
|
||||
waitFor: true
|
||||
},
|
||||
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/e2e_account.js
|
||||
e2e/e2e_account.ts
|
||||
junit.xml
|
||||
|
||||
*.p8
|
|
@ -1 +1 @@
|
|||
2.7.7
|
||||
2.7.4
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -1,4 +1,4 @@
|
|||
source 'https://rubygems.org'
|
||||
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
||||
ruby '2.7.7'
|
||||
ruby '2.7.4'
|
||||
gem 'cocoapods', '~> 1.11', '>= 1.11.2'
|
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
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode VERSIONCODE as Integer
|
||||
versionName "4.37.0"
|
||||
versionName "4.35.0"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
if (!isFoss) {
|
||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||
|
@ -250,7 +250,6 @@ android {
|
|||
release {
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
|
||||
proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
|
||||
signingConfig signingConfigs.release
|
||||
if (!isFoss) {
|
||||
firebaseCrashlytics {
|
||||
|
@ -269,11 +268,6 @@ android {
|
|||
// pickFirst '**/x86_64/libc++_shared.so'
|
||||
// }
|
||||
|
||||
// FIXME: Remove when we update RN
|
||||
packagingOptions {
|
||||
pickFirst '**/*.so'
|
||||
}
|
||||
|
||||
// applicationVariants are e.g. debug, release
|
||||
|
||||
flavorDimensions "app", "type"
|
||||
|
@ -286,6 +280,10 @@ android {
|
|||
dimension = "app"
|
||||
buildConfigField "boolean", "IS_OFFICIAL", "false"
|
||||
}
|
||||
e2e {
|
||||
dimension = "app"
|
||||
buildConfigField "boolean", "IS_OFFICIAL", "false"
|
||||
}
|
||||
foss {
|
||||
dimension = "type"
|
||||
buildConfigField "boolean", "FDROID_BUILD", "true"
|
||||
|
@ -313,6 +311,16 @@ android {
|
|||
java.srcDirs = ['src/main/java', 'src/play/java']
|
||||
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 ->
|
||||
|
@ -377,9 +385,8 @@ dependencies {
|
|||
implementation "com.github.bumptech.glide:glide:4.9.0"
|
||||
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
|
||||
implementation "com.tencent:mmkv-static:1.2.10"
|
||||
androidTestImplementation('com.wix:detox:+')
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'com.facebook.soloader:soloader:0.10.4'
|
||||
androidTestImplementation('com.wix:detox:+') { transitive = true }
|
||||
androidTestImplementation 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
|
|
|
@ -18,7 +18,7 @@ public class DetoxTest {
|
|||
@Rule
|
||||
// Replace 'MainActivity' with the value of android:name entry in
|
||||
// <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
|
||||
public void runDetoxTests() {
|
||||
|
|
|
@ -23,6 +23,8 @@ import com.facebook.react.ReactInstanceManager;
|
|||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import com.facebook.react.modules.network.CustomClientBuilder;
|
||||
import tech.bam.rnperformance.flipper.RNPerfMonitorPlugin;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
|
@ -33,6 +35,8 @@ public class ReactNativeFlipper {
|
|||
client.addPlugin(new DatabasesFlipperPlugin(context));
|
||||
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
|
||||
client.addPlugin(CrashReporterPlugin.getInstance());
|
||||
client.addPlugin(new RNPerfMonitorPlugin(reactInstanceManager));
|
||||
|
||||
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
|
||||
NetworkingModule.setCustomClientBuilder(
|
||||
new CustomClientBuilder() {
|
||||
|
|
|
@ -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>
|
|
@ -11,9 +11,6 @@
|
|||
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
|
||||
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
|
||||
|
||||
<!-- permissions related to jitsi call -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
|
||||
<application
|
||||
android:name="chat.rocket.reactnative.MainApplication"
|
||||
android:allowBackup="false"
|
||||
|
|
Binary file not shown.
|
@ -7,8 +7,4 @@
|
|||
tools:ignore="AcceptsUserCertificates" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
|
@ -1,5 +1,9 @@
|
|||
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.
|
||||
buildscript {
|
||||
def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase()
|
||||
|
@ -71,38 +75,5 @@ allprojects {
|
|||
google()
|
||||
maven { url 'https://maven.google.com' }
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,9 +28,7 @@ export const ROOM = createRequestTypes('ROOM', [
|
|||
'DELETE',
|
||||
'REMOVED',
|
||||
'FORWARD',
|
||||
'USER_TYPING',
|
||||
'HISTORY_REQUEST',
|
||||
'HISTORY_FINISHED'
|
||||
'USER_TYPING'
|
||||
]);
|
||||
export const INQUIRY = createRequestTypes('INQUIRY', [
|
||||
...defaultTypes,
|
||||
|
@ -40,14 +38,7 @@ export const INQUIRY = createRequestTypes('INQUIRY', [
|
|||
'QUEUE_UPDATE',
|
||||
'QUEUE_REMOVE'
|
||||
]);
|
||||
export const APP = createRequestTypes('APP', [
|
||||
'START',
|
||||
'READY',
|
||||
'INIT',
|
||||
'INIT_LOCAL_SETTINGS',
|
||||
'SET_MASTER_DETAIL',
|
||||
'SET_NOTIFICATION_PRESENCE_CAP'
|
||||
]);
|
||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS', 'SET_MASTER_DETAIL']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);
|
||||
|
|
|
@ -12,11 +12,7 @@ interface ISetMasterDetail extends Action {
|
|||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
interface ISetNotificationPresenceCap extends Action {
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
export type TActionApp = IAppStart & ISetMasterDetail & ISetNotificationPresenceCap;
|
||||
export type TActionApp = IAppStart & ISetMasterDetail;
|
||||
|
||||
interface Params {
|
||||
root: RootEnum;
|
||||
|
@ -55,10 +51,3 @@ export function setMasterDetail(isMasterDetail: boolean): ISetMasterDetail {
|
|||
isMasterDetail
|
||||
};
|
||||
}
|
||||
|
||||
export function setNotificationPresenceCap(show: boolean): ISetNotificationPresenceCap {
|
||||
return {
|
||||
type: APP.SET_NOTIFICATION_PRESENCE_CAP,
|
||||
show
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Action } from 'redux';
|
||||
|
||||
import { ERoomType, RoomType } from '../definitions';
|
||||
import { ERoomType } from '../definitions/ERoomType';
|
||||
import { ROOM } from './actionsTypes';
|
||||
|
||||
// TYPE RETURN RELATED
|
||||
|
@ -44,24 +44,7 @@ interface IUserTyping extends Action {
|
|||
status: boolean;
|
||||
}
|
||||
|
||||
export interface IRoomHistoryRequest extends Action {
|
||||
rid: string;
|
||||
t: RoomType;
|
||||
loaderId: string;
|
||||
}
|
||||
|
||||
export interface IRoomHistoryFinished extends Action {
|
||||
loaderId: string;
|
||||
}
|
||||
|
||||
export type TActionsRoom = TSubscribeRoom &
|
||||
TUnsubscribeRoom &
|
||||
ILeaveRoom &
|
||||
IDeleteRoom &
|
||||
IForwardRoom &
|
||||
IUserTyping &
|
||||
IRoomHistoryRequest &
|
||||
IRoomHistoryFinished;
|
||||
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping;
|
||||
|
||||
export function subscribeRoom(rid: string): TSubscribeRoom {
|
||||
return {
|
||||
|
@ -116,19 +99,3 @@ export function userTyping(rid: string, status = true): IUserTyping {
|
|||
status
|
||||
};
|
||||
}
|
||||
|
||||
export function roomHistoryRequest({ rid, t, loaderId }: { rid: string; t: RoomType; loaderId: string }): IRoomHistoryRequest {
|
||||
return {
|
||||
type: ROOM.HISTORY_REQUEST,
|
||||
rid,
|
||||
t,
|
||||
loaderId
|
||||
};
|
||||
}
|
||||
|
||||
export function roomHistoryFinished({ loaderId }: { loaderId: string }): IRoomHistoryFinished {
|
||||
return {
|
||||
type: ROOM.HISTORY_FINISHED,
|
||||
loaderId
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export const mappedIcons = {
|
||||
'status-disabled': 59837,
|
||||
'lamp-bulb': 59836,
|
||||
'phone-in': 59835,
|
||||
'basketball': 59776,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,31 +1,34 @@
|
|||
import React from 'react';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
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 scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||
import { IEmoji } from '../../definitions/IEmoji';
|
||||
import { PressableEmoji } from './PressableEmoji';
|
||||
|
||||
interface IEmojiCategoryProps {
|
||||
emojis: IEmoji[];
|
||||
onEmojiSelected: (emoji: IEmoji) => void;
|
||||
tabLabel?: string; // needed for react-native-scrollable-tab-view only
|
||||
parentWidth: number;
|
||||
}
|
||||
|
||||
const EmojiCategory = ({ onEmojiSelected, emojis, parentWidth }: IEmojiCategoryProps): React.ReactElement | null => {
|
||||
if (!parentWidth) {
|
||||
return null;
|
||||
}
|
||||
const EmojiCategory = ({ onEmojiSelected, emojis }: IEmojiCategoryProps): React.ReactElement | null => {
|
||||
const { width } = useWindowDimensions();
|
||||
|
||||
const numColumns = Math.trunc(parentWidth / EMOJI_BUTTON_SIZE);
|
||||
const marginHorizontal = (parentWidth % EMOJI_BUTTON_SIZE) / 2;
|
||||
const numColumns = Math.trunc(width / EMOJI_BUTTON_SIZE);
|
||||
const marginHorizontal = (width % EMOJI_BUTTON_SIZE) / 2;
|
||||
|
||||
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={onEmojiSelected} />;
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<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)}
|
||||
data={emojis}
|
||||
renderItem={renderItem}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
|
||||
|
@ -20,8 +20,6 @@ const EmojiPicker = ({
|
|||
searchedEmojis = []
|
||||
}: IEmojiPickerProps): React.ReactElement | null => {
|
||||
const { colors } = useTheme();
|
||||
const [parentWidth, setParentWidth] = useState(0);
|
||||
|
||||
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
|
||||
|
||||
const allCustomEmojis: ICustomEmojis = useAppSelector(
|
||||
|
@ -52,14 +50,7 @@ const EmojiPicker = ({
|
|||
if (!emojis.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<EmojiCategory
|
||||
parentWidth={parentWidth}
|
||||
emojis={emojis}
|
||||
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
|
||||
tabLabel={label}
|
||||
/>
|
||||
);
|
||||
return <EmojiCategory emojis={emojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} tabLabel={label} />;
|
||||
};
|
||||
|
||||
if (!loaded) {
|
||||
|
@ -67,13 +58,9 @@ const EmojiPicker = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={styles.emojiPickerContainer} onLayout={e => setParentWidth(e.nativeEvent.layout.width)}>
|
||||
<View style={styles.emojiPickerContainer}>
|
||||
{searching ? (
|
||||
<EmojiCategory
|
||||
emojis={searchedEmojis}
|
||||
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
|
||||
parentWidth={parentWidth}
|
||||
/>
|
||||
<EmojiCategory emojis={searchedEmojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} />
|
||||
) : (
|
||||
<ScrollableTabView
|
||||
renderTabBar={() => <TabBar />}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
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';
|
||||
|
||||
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 => (
|
||||
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: STATUS_COLORS.disabled }]} />
|
||||
);
|
||||
export default Badge;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { Header, HeaderBackground } from '@react-navigation/elements';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
|
@ -104,10 +103,9 @@ export const Badge = () => (
|
|||
<HeaderExample
|
||||
left={() => (
|
||||
<HeaderButton.Container left>
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadUser={[1]} />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadGroup={[1]} />} />
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} tunreadUser={[1]} />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} tunreadGroup={[1]} />} />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
/>
|
||||
|
@ -116,23 +114,20 @@ export const Badge = () => (
|
|||
|
||||
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
||||
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<HeaderExample
|
||||
left={() => (
|
||||
<HeaderButton.Container left>
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
||||
<HeaderButton.Item iconName='threads' />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
right={() => (
|
||||
<HeaderButton.Container>
|
||||
<HeaderButton.Item title='Threads' />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
colors={colors[theme]}
|
||||
/>
|
||||
</View>
|
||||
<HeaderExample
|
||||
left={() => (
|
||||
<HeaderButton.Container left>
|
||||
<HeaderButton.Item iconName='threads' />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
right={() => (
|
||||
<HeaderButton.Container>
|
||||
<HeaderButton.Item title='Threads' />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
colors={colors[theme]}
|
||||
/>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { default as Container } from './HeaderButtonContainer';
|
||||
export { default as Item } from './HeaderButtonItem';
|
||||
export * from './HeaderButtonItemBadge';
|
||||
export { default as Badge } from './HeaderButtonItemBadge';
|
||||
export * from './Common';
|
||||
|
|
|
@ -10,12 +10,12 @@ import { useFrequentlyUsedEmoji } from '../../lib/hooks';
|
|||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||
import { useDimensions } from '../../dimensions';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import { IEmoji, TAnyMessageModel } from '../../definitions';
|
||||
import { IEmoji, TAnyMessage } from '../../definitions';
|
||||
import Touch from '../Touch';
|
||||
|
||||
export interface IHeader {
|
||||
handleReaction: (emoji: IEmoji | null, message: TAnyMessageModel) => void;
|
||||
message: TAnyMessageModel;
|
||||
handleReaction: (emoji: IEmoji | null, message: TAnyMessage) => void;
|
||||
message: TAnyMessage;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
|||
import moment from 'moment';
|
||||
|
||||
import database from '../../lib/database';
|
||||
import { getMessageById } from '../../lib/database/services/Message';
|
||||
import I18n from '../../i18n';
|
||||
import log, { logEvent } from '../../lib/methods/helpers/log';
|
||||
import Navigation from '../../lib/navigation/appNavigation';
|
||||
|
@ -15,7 +16,7 @@ import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
|||
import { TActionSheetOptionsItem, useActionSheet, ACTION_SHEET_ANIMATION_DURATION } from '../ActionSheet';
|
||||
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
||||
import events from '../../lib/methods/helpers/log/events';
|
||||
import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { IApplicationState, IEmoji, ILoggedUser, TAnyMessage, TSubscriptionModel } from '../../definitions';
|
||||
import { getPermalinkMessage } from '../../lib/methods';
|
||||
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
||||
import { Services } from '../../lib/services';
|
||||
|
@ -24,10 +25,10 @@ export interface IMessageActionsProps {
|
|||
room: TSubscriptionModel;
|
||||
tmid?: string;
|
||||
user: Pick<ILoggedUser, 'id'>;
|
||||
editInit: (message: TAnyMessageModel) => void;
|
||||
reactionInit: (message: TAnyMessageModel) => void;
|
||||
editInit: (message: TAnyMessage) => void;
|
||||
reactionInit: (message: TAnyMessage) => void;
|
||||
onReactionPress: (shortname: IEmoji, messageId: string) => void;
|
||||
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
|
||||
replyInit: (message: TAnyMessage, mention: boolean) => void;
|
||||
isMasterDetail: boolean;
|
||||
isReadOnly: boolean;
|
||||
Message_AllowDeleting?: boolean;
|
||||
|
@ -46,7 +47,7 @@ export interface IMessageActionsProps {
|
|||
}
|
||||
|
||||
export interface IMessageActions {
|
||||
showMessageActions: (message: TAnyMessageModel) => Promise<void>;
|
||||
showMessageActions: (message: TAnyMessage) => Promise<void>;
|
||||
}
|
||||
|
||||
const MessageActions = React.memo(
|
||||
|
@ -109,9 +110,9 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const isOwn = (message: TAnyMessageModel) => message.u && message.u._id === user.id;
|
||||
const isOwn = (message: TAnyMessage) => message.u && message.u._id === user.id;
|
||||
|
||||
const allowEdit = (message: TAnyMessageModel) => {
|
||||
const allowEdit = (message: TAnyMessage) => {
|
||||
if (isReadOnly) {
|
||||
return false;
|
||||
}
|
||||
|
@ -135,7 +136,7 @@ const MessageActions = React.memo(
|
|||
return true;
|
||||
};
|
||||
|
||||
const allowDelete = (message: TAnyMessageModel) => {
|
||||
const allowDelete = (message: TAnyMessage) => {
|
||||
if (isReadOnly) {
|
||||
return false;
|
||||
}
|
||||
|
@ -166,19 +167,19 @@ const MessageActions = React.memo(
|
|||
return true;
|
||||
};
|
||||
|
||||
const getPermalink = (message: TAnyMessageModel) => getPermalinkMessage(message);
|
||||
const getPermalink = (message: TAnyMessage) => getPermalinkMessage(message);
|
||||
|
||||
const handleReply = (message: TAnyMessageModel) => {
|
||||
const handleReply = (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_REPLY);
|
||||
replyInit(message, true);
|
||||
};
|
||||
|
||||
const handleEdit = (message: TAnyMessageModel) => {
|
||||
const handleEdit = (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_EDIT);
|
||||
editInit(message);
|
||||
};
|
||||
|
||||
const handleCreateDiscussion = (message: TAnyMessageModel) => {
|
||||
const handleCreateDiscussion = (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_DISCUSSION);
|
||||
const params = { message, channel: room, showCloseModal: true };
|
||||
if (isMasterDetail) {
|
||||
|
@ -188,7 +189,7 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handleUnread = async (message: TAnyMessageModel) => {
|
||||
const handleUnread = async (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_UNREAD);
|
||||
const { id: messageId, ts } = message;
|
||||
const { rid } = room;
|
||||
|
@ -213,7 +214,7 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handlePermalink = async (message: TAnyMessageModel) => {
|
||||
const handlePermalink = async (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_PERMALINK);
|
||||
try {
|
||||
const permalink = await getPermalink(message);
|
||||
|
@ -224,13 +225,13 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handleCopy = async (message: TAnyMessageModel) => {
|
||||
const handleCopy = async (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_COPY);
|
||||
await Clipboard.setString((message?.attachments?.[0]?.description || message.msg) ?? '');
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||
};
|
||||
|
||||
const handleShare = async (message: TAnyMessageModel) => {
|
||||
const handleShare = async (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_SHARE);
|
||||
try {
|
||||
const permalink = await getPermalink(message);
|
||||
|
@ -242,12 +243,12 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handleQuote = (message: TAnyMessageModel) => {
|
||||
const handleQuote = (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_QUOTE);
|
||||
replyInit(message, false);
|
||||
};
|
||||
|
||||
const handleReplyInDM = async (message: TAnyMessageModel) => {
|
||||
const handleReplyInDM = async (message: TAnyMessage) => {
|
||||
if (message?.u?.username) {
|
||||
const result = await Services.createDirectMessage(message.u.username);
|
||||
if (result.success) {
|
||||
|
@ -264,7 +265,7 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handleStar = async (message: TAnyMessageModel) => {
|
||||
const handleStar = async (message: TAnyMessage) => {
|
||||
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
||||
try {
|
||||
await Services.toggleStarMessage(message.id, message.starred as boolean); // TODO: reevaluate `message.starred` type on IMessage
|
||||
|
@ -275,7 +276,7 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handlePin = async (message: TAnyMessageModel) => {
|
||||
const handlePin = async (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_PIN);
|
||||
try {
|
||||
await Services.togglePinMessage(message.id, message.pinned as boolean); // TODO: reevaluate `message.pinned` type on IMessage
|
||||
|
@ -295,7 +296,7 @@ const MessageActions = React.memo(
|
|||
hideActionSheet();
|
||||
};
|
||||
|
||||
const handleReadReceipt = (message: TAnyMessageModel) => {
|
||||
const handleReadReceipt = (message: TAnyMessage) => {
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } });
|
||||
} else {
|
||||
|
@ -303,14 +304,18 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handleToggleTranslation = async (message: TAnyMessageModel) => {
|
||||
const handleToggleTranslation = async (message: TAnyMessage) => {
|
||||
try {
|
||||
if (!room.autoTranslateLanguage) {
|
||||
return;
|
||||
}
|
||||
const db = database.active;
|
||||
const messageRecord = await getMessageById(message.id);
|
||||
if (!messageRecord) {
|
||||
return;
|
||||
}
|
||||
await db.write(async () => {
|
||||
await message.update(m => {
|
||||
await messageRecord.update(m => {
|
||||
m.autoTranslate = !m.autoTranslate;
|
||||
m._updatedAt = new Date();
|
||||
});
|
||||
|
@ -324,7 +329,7 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handleReport = async (message: TAnyMessageModel) => {
|
||||
const handleReport = async (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_REPORT);
|
||||
try {
|
||||
await Services.reportMessage(message.id);
|
||||
|
@ -335,14 +340,14 @@ const MessageActions = React.memo(
|
|||
}
|
||||
};
|
||||
|
||||
const handleDelete = (message: TAnyMessageModel) => {
|
||||
const handleDelete = (message: TAnyMessage) => {
|
||||
showConfirmationAlert({
|
||||
message: I18n.t('You_will_not_be_able_to_recover_this_message'),
|
||||
confirmationText: I18n.t('Delete'),
|
||||
onPress: async () => {
|
||||
try {
|
||||
logEvent(events.ROOM_MSG_ACTION_DELETE);
|
||||
await Services.deleteMessage(message.id, message.subscription ? message.subscription.id : '');
|
||||
await Services.deleteMessage(message.id, message.rid);
|
||||
} catch (e) {
|
||||
logEvent(events.ROOM_MSG_ACTION_DELETE_F);
|
||||
log(e);
|
||||
|
@ -351,7 +356,7 @@ const MessageActions = React.memo(
|
|||
});
|
||||
};
|
||||
|
||||
const getOptions = (message: TAnyMessageModel) => {
|
||||
const getOptions = (message: TAnyMessage) => {
|
||||
const options: TActionSheetOptionsItem[] = [];
|
||||
const videoConfBlock = message.t === 'videoconf';
|
||||
|
||||
|
@ -487,7 +492,7 @@ const MessageActions = React.memo(
|
|||
return options;
|
||||
};
|
||||
|
||||
const showMessageActions = async (message: TAnyMessageModel) => {
|
||||
const showMessageActions = async (message: TAnyMessage) => {
|
||||
logEvent(events.ROOM_SHOW_MSG_ACTIONS);
|
||||
await getPermissions();
|
||||
showActionSheet({
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Alert, Keyboard, NativeModules, Text, View, BackHandler } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
// import { KeyboardTrackingView } from 'react-native-keyboard-tracking-view';
|
||||
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
|
||||
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
|
||||
import { dequal } from 'dequal';
|
||||
|
@ -169,7 +170,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
id: ''
|
||||
},
|
||||
sharing: false,
|
||||
iOSScrollBehavior: NativeModules.KeyboardTrackingViewTempManager?.KeyboardTrackingScrollBehaviorFixedOffset,
|
||||
iOSScrollBehavior: NativeModules.KeyboardTrackingViewTempManager?.KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly,
|
||||
isActionsEnabled: true,
|
||||
getCustomEmoji: () => {}
|
||||
};
|
||||
|
@ -302,7 +303,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (usedCannedResponse !== nextProps.usedCannedResponse) {
|
||||
this.onChangeText(nextProps.usedCannedResponse ?? '');
|
||||
}
|
||||
if (sharing && !replying) {
|
||||
if (sharing) {
|
||||
this.setInput(nextProps.message.msg ?? '');
|
||||
return;
|
||||
}
|
||||
|
@ -857,21 +858,14 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
};
|
||||
|
||||
openShareView = (attachments: any) => {
|
||||
const { message, replyCancel, replyWithMention, replying } = this.props;
|
||||
const { message, replyCancel, replyWithMention } = this.props;
|
||||
// Start a thread with an attachment
|
||||
let value: TThreadModel | IMessage = this.thread;
|
||||
if (replyWithMention) {
|
||||
value = message;
|
||||
replyCancel();
|
||||
}
|
||||
Navigation.navigate('ShareView', {
|
||||
room: this.room,
|
||||
thread: value,
|
||||
attachments,
|
||||
replying,
|
||||
replyingMessage: message,
|
||||
closeReply: replyCancel
|
||||
});
|
||||
Navigation.navigate('ShareView', { room: this.room, thread: value, attachments });
|
||||
};
|
||||
|
||||
createDiscussion = () => {
|
||||
|
@ -1049,7 +1043,16 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
|
||||
// Legacy reply or quote (quote is a reply without mention)
|
||||
} else {
|
||||
const msg = await this.formatReplyMessage(replyingMessage, message);
|
||||
const { user, roomType } = this.props;
|
||||
const permalink = await this.getPermalink(replyingMessage);
|
||||
let msg = `[ ](${permalink}) `;
|
||||
|
||||
// if original message wasn't sent by current user and neither from a direct room
|
||||
if (user.username !== replyingMessage?.u?.username && roomType !== 'd' && replyWithMention) {
|
||||
msg += `@${replyingMessage?.u?.username} `;
|
||||
}
|
||||
|
||||
msg = `${msg} ${message}`;
|
||||
onSubmit(msg);
|
||||
}
|
||||
replyCancel();
|
||||
|
@ -1061,20 +1064,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}
|
||||
};
|
||||
|
||||
formatReplyMessage = async (replyingMessage: IMessage, message = '') => {
|
||||
const { user, roomType, replyWithMention, serverVersion } = this.props;
|
||||
const permalink = await this.getPermalink(replyingMessage);
|
||||
let msg = `[ ](${permalink}) `;
|
||||
|
||||
// if original message wasn't sent by current user and neither from a direct room
|
||||
if (user.username !== replyingMessage?.u?.username && roomType !== 'd' && replyWithMention) {
|
||||
msg += `@${replyingMessage?.u?.username} `;
|
||||
}
|
||||
|
||||
const connectionString = compareServerVersion(serverVersion, 'lowerThan', '5.0.0') ? ' ' : '\n';
|
||||
return `${msg}${connectionString}${message}`;
|
||||
};
|
||||
|
||||
updateMentions = (keyword: any, type: string) => {
|
||||
if (type === MENTIONS_TRACKING_TYPE_USERS) {
|
||||
this.getUsers(keyword);
|
||||
|
@ -1302,7 +1291,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
render() {
|
||||
console.count(`${this.constructor.name}.render calls`);
|
||||
const { showEmojiKeyboard } = this.state;
|
||||
const { user, baseUrl, theme, iOSScrollBehavior } = this.props;
|
||||
const { user, baseUrl, theme, iOSScrollBehavior, tmid, rid } = this.props;
|
||||
return (
|
||||
<MessageboxContext.Provider
|
||||
value={{
|
||||
|
@ -1321,11 +1310,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
kbInitialProps={{ theme }}
|
||||
onKeyboardResigned={this.onKeyboardResigned}
|
||||
onItemSelected={this.onKeyboardItemSelected}
|
||||
trackInteractive
|
||||
requiresSameParentToManageScrollView
|
||||
addBottomView
|
||||
bottomViewColor={themes[theme].messageboxBackground}
|
||||
iOSScrollBehavior={iOSScrollBehavior}
|
||||
scrollViewNativeID={tmid || rid}
|
||||
/>
|
||||
</MessageboxContext.Provider>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { forwardRef, useImperativeHandle } from 'react';
|
||||
import Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import { getMessageById } from '../lib/database/services/Message';
|
||||
import { getThreadMessageById } from '../lib/database/services/ThreadMessage';
|
||||
import database from '../lib/database';
|
||||
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||
import { useActionSheet } from './ActionSheet';
|
||||
|
@ -27,16 +29,16 @@ const MessageErrorActions = forwardRef<IMessageErrorActions, { tmid?: string }>(
|
|||
const msgCollection = db.get('messages');
|
||||
const threadCollection = db.get('threads');
|
||||
|
||||
// Delete the object (it can be Message or ThreadMessage instance)
|
||||
deleteBatch.push(message.prepareDestroyPermanently());
|
||||
const msg = await getMessageById(message.id);
|
||||
if (msg) {
|
||||
deleteBatch.push(msg.prepareDestroyPermanently());
|
||||
}
|
||||
|
||||
// If it's a thread, we find and delete the whole tree, if necessary
|
||||
if (tmid) {
|
||||
try {
|
||||
const msg = await msgCollection.find(message.id);
|
||||
const msg = await getThreadMessageById(message.id);
|
||||
if (msg) {
|
||||
deleteBatch.push(msg.prepareDestroyPermanently());
|
||||
} catch {
|
||||
// Do nothing: message not found
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -46,7 +46,6 @@ const RoomHeaderContainer = React.memo(
|
|||
const connecting = useSelector((state: IApplicationState) => state.meteor.connecting || state.server.loading);
|
||||
const usersTyping = useSelector((state: IApplicationState) => state.usersTyping, shallowEqual);
|
||||
const connected = useSelector((state: IApplicationState) => state.meteor.connected);
|
||||
const presenceDisabled = useSelector((state: IApplicationState) => state.settings.Presence_broadcast_disabled);
|
||||
const activeUser = useSelector(
|
||||
(state: IApplicationState) => (roomUserId ? state.activeUsers?.[roomUserId] : undefined),
|
||||
shallowEqual
|
||||
|
@ -62,13 +61,9 @@ const RoomHeaderContainer = React.memo(
|
|||
|
||||
if (connected) {
|
||||
if ((type === 'd' || (tmid && roomUserId)) && activeUser) {
|
||||
if (presenceDisabled) {
|
||||
status = 'disabled';
|
||||
} else {
|
||||
const { status: statusActiveUser, statusText: statusTextActiveUser } = activeUser;
|
||||
status = statusActiveUser;
|
||||
statusText = statusTextActiveUser;
|
||||
}
|
||||
const { status: statusActiveUser, statusText: statusTextActiveUser } = activeUser;
|
||||
status = statusActiveUser;
|
||||
statusText = statusTextActiveUser;
|
||||
} else if (type === 'l' && visitor?.status) {
|
||||
const { status: statusVisitor } = visitor;
|
||||
status = statusVisitor;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import { dequal } from 'dequal';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import styles from './styles';
|
||||
|
@ -45,9 +44,7 @@ const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }
|
|||
return `${prefix}${lastMessage.msg}`;
|
||||
};
|
||||
|
||||
const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps);
|
||||
|
||||
const LastMessage = React.memo(({ lastMessage, type, showLastMessage, username, alert, useRealName }: ILastMessageProps) => {
|
||||
const LastMessage = ({ lastMessage, type, showLastMessage, username, alert, useRealName }: ILastMessageProps) => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<MarkdownPreview
|
||||
|
@ -63,6 +60,6 @@ const LastMessage = React.memo(({ lastMessage, type, showLastMessage, username,
|
|||
testID='room-item-last-message'
|
||||
/>
|
||||
);
|
||||
}, arePropsEqual);
|
||||
};
|
||||
|
||||
export default LastMessage;
|
||||
|
|
|
@ -65,7 +65,6 @@ export const UserStatus = () => (
|
|||
<RoomItem status='busy' />
|
||||
<RoomItem status='offline' />
|
||||
<RoomItem status='loading' />
|
||||
<RoomItem status='disabled' />
|
||||
<RoomItem status='wrong' />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -20,7 +20,6 @@ const RoomItem = ({
|
|||
prid,
|
||||
name,
|
||||
avatar,
|
||||
width,
|
||||
username,
|
||||
showLastMessage,
|
||||
status = 'offline',
|
||||
|
@ -52,12 +51,13 @@ const RoomItem = ({
|
|||
showAvatar,
|
||||
displayMode,
|
||||
sourceType,
|
||||
hideMentionStatus
|
||||
hideMentionStatus,
|
||||
touchableRef
|
||||
}: IRoomItemProps) => (
|
||||
<Touchable
|
||||
ref={touchableRef}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
width={width}
|
||||
favorite={favorite}
|
||||
toggleFav={toggleFav}
|
||||
isRead={isRead}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import Animated, {
|
||||
useAnimatedGestureHandler,
|
||||
useSharedValue,
|
||||
|
@ -13,227 +13,242 @@ import {
|
|||
HandlerStateChangeEventPayload,
|
||||
PanGestureHandlerEventPayload
|
||||
} from 'react-native-gesture-handler';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
|
||||
import Touch from '../Touch';
|
||||
import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles';
|
||||
import { LeftActions, RightActions } from './Actions';
|
||||
import { ITouchableProps } from './interfaces';
|
||||
import { ITouchableProps, ITouchableRef } from './interfaces';
|
||||
import { useTheme } from '../../theme';
|
||||
import I18n from '../../i18n';
|
||||
import { MAX_SIDEBAR_WIDTH } from '../../lib/constants';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
const Touchable = ({
|
||||
children,
|
||||
type,
|
||||
onPress,
|
||||
onLongPress,
|
||||
testID,
|
||||
width,
|
||||
favorite,
|
||||
isRead,
|
||||
rid,
|
||||
toggleFav,
|
||||
toggleRead,
|
||||
hideChannel,
|
||||
isFocused,
|
||||
swipeEnabled,
|
||||
displayMode
|
||||
}: ITouchableProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const Touchable = forwardRef<ITouchableRef, ITouchableProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
type,
|
||||
onPress,
|
||||
onLongPress,
|
||||
testID,
|
||||
favorite,
|
||||
isRead,
|
||||
rid,
|
||||
toggleFav,
|
||||
toggleRead,
|
||||
hideChannel,
|
||||
isFocused,
|
||||
swipeEnabled,
|
||||
displayMode
|
||||
},
|
||||
ref
|
||||
): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const { width: deviceWidth } = useWindowDimensions();
|
||||
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
|
||||
const width = isMasterDetail ? MAX_SIDEBAR_WIDTH : deviceWidth;
|
||||
|
||||
const rowOffSet = useSharedValue(0);
|
||||
const transX = useSharedValue(0);
|
||||
const rowState = useSharedValue(0); // 0: closed, 1: right opened, -1: left opened
|
||||
let _value = 0;
|
||||
const rowOffSet = useSharedValue(0);
|
||||
const transX = useSharedValue(0);
|
||||
const rowState = useSharedValue(0); // 0: closed, 1: right opened, -1: left opened
|
||||
let _value = 0;
|
||||
|
||||
const close = () => {
|
||||
rowState.value = 0;
|
||||
transX.value = withSpring(0, { overshootClamping: true });
|
||||
rowOffSet.value = 0;
|
||||
};
|
||||
const close = () => {
|
||||
console.log(`${rid} close`);
|
||||
rowState.value = 0;
|
||||
transX.value = withSpring(0, { overshootClamping: true });
|
||||
rowOffSet.value = 0;
|
||||
};
|
||||
|
||||
const handleToggleFav = () => {
|
||||
if (toggleFav) {
|
||||
toggleFav(rid, favorite);
|
||||
}
|
||||
close();
|
||||
};
|
||||
useImperativeHandle(ref, () => ({
|
||||
close
|
||||
}));
|
||||
|
||||
const handleToggleRead = () => {
|
||||
if (toggleRead) {
|
||||
toggleRead(rid, isRead);
|
||||
}
|
||||
};
|
||||
|
||||
const handleHideChannel = () => {
|
||||
if (hideChannel) {
|
||||
hideChannel(rid, type);
|
||||
}
|
||||
};
|
||||
|
||||
const onToggleReadPress = () => {
|
||||
handleToggleRead();
|
||||
close();
|
||||
};
|
||||
|
||||
const onHidePress = () => {
|
||||
handleHideChannel();
|
||||
close();
|
||||
};
|
||||
|
||||
const handlePress = () => {
|
||||
if (rowState.value !== 0) {
|
||||
const handleToggleFav = () => {
|
||||
if (toggleFav) {
|
||||
toggleFav(rid, favorite);
|
||||
}
|
||||
close();
|
||||
return;
|
||||
}
|
||||
if (onPress) {
|
||||
onPress();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleLongPress = () => {
|
||||
if (rowState.value !== 0) {
|
||||
const handleToggleRead = () => {
|
||||
if (toggleRead) {
|
||||
toggleRead(rid, isRead);
|
||||
}
|
||||
};
|
||||
|
||||
const handleHideChannel = () => {
|
||||
if (hideChannel) {
|
||||
hideChannel(rid, type);
|
||||
}
|
||||
};
|
||||
|
||||
const onToggleReadPress = () => {
|
||||
handleToggleRead();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if (onLongPress) {
|
||||
onLongPress();
|
||||
}
|
||||
};
|
||||
const onHidePress = () => {
|
||||
handleHideChannel();
|
||||
close();
|
||||
};
|
||||
|
||||
const onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
|
||||
if (nativeEvent.state === State.ACTIVE) {
|
||||
handleLongPress();
|
||||
}
|
||||
};
|
||||
const handlePress = () => {
|
||||
if (rowState.value !== 0) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
if (onPress) {
|
||||
onPress();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRelease = (event: PanGestureHandlerEventPayload) => {
|
||||
const { translationX } = event;
|
||||
_value += translationX;
|
||||
let toValue = 0;
|
||||
if (rowState.value === 0) {
|
||||
// if no option is opened
|
||||
if (translationX > 0 && translationX < LONG_SWIPE) {
|
||||
if (I18n.isRTL) {
|
||||
const handleLongPress = () => {
|
||||
if (rowState.value !== 0) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (onLongPress) {
|
||||
onLongPress();
|
||||
}
|
||||
};
|
||||
|
||||
const onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
|
||||
if (nativeEvent.state === State.ACTIVE) {
|
||||
handleLongPress();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRelease = (event: PanGestureHandlerEventPayload) => {
|
||||
const { translationX } = event;
|
||||
_value += translationX;
|
||||
let toValue = 0;
|
||||
if (rowState.value === 0) {
|
||||
// if no option is opened
|
||||
if (translationX > 0 && translationX < LONG_SWIPE) {
|
||||
if (I18n.isRTL) {
|
||||
toValue = 2 * ACTION_WIDTH;
|
||||
} else {
|
||||
toValue = ACTION_WIDTH;
|
||||
}
|
||||
rowState.value = -1;
|
||||
} else if (translationX >= LONG_SWIPE) {
|
||||
toValue = 0;
|
||||
if (I18n.isRTL) {
|
||||
handleHideChannel();
|
||||
} else {
|
||||
handleToggleRead();
|
||||
}
|
||||
} else if (translationX < 0 && translationX > -LONG_SWIPE) {
|
||||
// open trailing option if he swipe left
|
||||
if (I18n.isRTL) {
|
||||
toValue = -ACTION_WIDTH;
|
||||
} else {
|
||||
toValue = -2 * ACTION_WIDTH;
|
||||
}
|
||||
rowState.value = 1;
|
||||
} else if (translationX <= -LONG_SWIPE) {
|
||||
toValue = 0;
|
||||
rowState.value = 1;
|
||||
if (I18n.isRTL) {
|
||||
handleToggleRead();
|
||||
} else {
|
||||
handleHideChannel();
|
||||
}
|
||||
} else {
|
||||
toValue = 0;
|
||||
}
|
||||
} else if (rowState.value === -1) {
|
||||
// if left option is opened
|
||||
if (_value < SMALL_SWIPE) {
|
||||
toValue = 0;
|
||||
rowState.value = 0;
|
||||
} else if (_value > LONG_SWIPE) {
|
||||
toValue = 0;
|
||||
rowState.value = 0;
|
||||
if (I18n.isRTL) {
|
||||
handleHideChannel();
|
||||
} else {
|
||||
handleToggleRead();
|
||||
}
|
||||
} else if (I18n.isRTL) {
|
||||
toValue = 2 * ACTION_WIDTH;
|
||||
} else {
|
||||
toValue = ACTION_WIDTH;
|
||||
}
|
||||
rowState.value = -1;
|
||||
} else if (translationX >= LONG_SWIPE) {
|
||||
toValue = 0;
|
||||
if (I18n.isRTL) {
|
||||
handleHideChannel();
|
||||
} else {
|
||||
handleToggleRead();
|
||||
}
|
||||
} else if (translationX < 0 && translationX > -LONG_SWIPE) {
|
||||
// open trailing option if he swipe left
|
||||
if (I18n.isRTL) {
|
||||
} else if (rowState.value === 1) {
|
||||
// if right option is opened
|
||||
if (_value > -2 * SMALL_SWIPE) {
|
||||
toValue = 0;
|
||||
rowState.value = 0;
|
||||
} else if (_value < -LONG_SWIPE) {
|
||||
if (I18n.isRTL) {
|
||||
handleToggleRead();
|
||||
} else {
|
||||
handleHideChannel();
|
||||
}
|
||||
} else if (I18n.isRTL) {
|
||||
toValue = -ACTION_WIDTH;
|
||||
} else {
|
||||
toValue = -2 * ACTION_WIDTH;
|
||||
}
|
||||
rowState.value = 1;
|
||||
} else if (translationX <= -LONG_SWIPE) {
|
||||
toValue = 0;
|
||||
rowState.value = 1;
|
||||
if (I18n.isRTL) {
|
||||
handleToggleRead();
|
||||
} else {
|
||||
handleHideChannel();
|
||||
}
|
||||
} else {
|
||||
toValue = 0;
|
||||
}
|
||||
} else if (rowState.value === -1) {
|
||||
// if left option is opened
|
||||
if (_value < SMALL_SWIPE) {
|
||||
toValue = 0;
|
||||
rowState.value = 0;
|
||||
} else if (_value > LONG_SWIPE) {
|
||||
toValue = 0;
|
||||
rowState.value = 0;
|
||||
if (I18n.isRTL) {
|
||||
handleHideChannel();
|
||||
} else {
|
||||
handleToggleRead();
|
||||
}
|
||||
} else if (I18n.isRTL) {
|
||||
toValue = 2 * ACTION_WIDTH;
|
||||
} else {
|
||||
toValue = ACTION_WIDTH;
|
||||
transX.value = withSpring(toValue, { overshootClamping: true });
|
||||
rowOffSet.value = toValue;
|
||||
_value = toValue;
|
||||
};
|
||||
|
||||
const onGestureEvent = useAnimatedGestureHandler({
|
||||
onActive: event => {
|
||||
transX.value = event.translationX + rowOffSet.value;
|
||||
if (transX.value > 2 * width) transX.value = 2 * width;
|
||||
},
|
||||
onEnd: event => {
|
||||
runOnJS(handleRelease)(event);
|
||||
}
|
||||
} else if (rowState.value === 1) {
|
||||
// if right option is opened
|
||||
if (_value > -2 * SMALL_SWIPE) {
|
||||
toValue = 0;
|
||||
rowState.value = 0;
|
||||
} else if (_value < -LONG_SWIPE) {
|
||||
if (I18n.isRTL) {
|
||||
handleToggleRead();
|
||||
} else {
|
||||
handleHideChannel();
|
||||
}
|
||||
} else if (I18n.isRTL) {
|
||||
toValue = -ACTION_WIDTH;
|
||||
} else {
|
||||
toValue = -2 * ACTION_WIDTH;
|
||||
}
|
||||
}
|
||||
transX.value = withSpring(toValue, { overshootClamping: true });
|
||||
rowOffSet.value = toValue;
|
||||
_value = toValue;
|
||||
};
|
||||
});
|
||||
|
||||
const onGestureEvent = useAnimatedGestureHandler({
|
||||
onActive: event => {
|
||||
transX.value = event.translationX + rowOffSet.value;
|
||||
if (transX.value > 2 * width) transX.value = 2 * width;
|
||||
},
|
||||
onEnd: event => {
|
||||
runOnJS(handleRelease)(event);
|
||||
}
|
||||
});
|
||||
const animatedStyles = useAnimatedStyle(() => ({ transform: [{ translateX: transX.value }] }));
|
||||
|
||||
const animatedStyles = useAnimatedStyle(() => ({ transform: [{ translateX: transX.value }] }));
|
||||
|
||||
return (
|
||||
<LongPressGestureHandler onHandlerStateChange={onLongPressHandlerStateChange}>
|
||||
<Animated.View>
|
||||
<PanGestureHandler activeOffsetX={[-20, 20]} onGestureEvent={onGestureEvent} enabled={swipeEnabled}>
|
||||
<Animated.View>
|
||||
<LeftActions
|
||||
transX={transX}
|
||||
isRead={isRead}
|
||||
width={width}
|
||||
onToggleReadPress={onToggleReadPress}
|
||||
displayMode={displayMode}
|
||||
/>
|
||||
<RightActions
|
||||
transX={transX}
|
||||
favorite={favorite}
|
||||
width={width}
|
||||
toggleFav={handleToggleFav}
|
||||
onHidePress={onHidePress}
|
||||
displayMode={displayMode}
|
||||
/>
|
||||
<Animated.View style={animatedStyles}>
|
||||
<Touch
|
||||
onPress={handlePress}
|
||||
testID={testID}
|
||||
style={{
|
||||
backgroundColor: isFocused ? colors.chatComponentBackground : colors.backgroundColor
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Touch>
|
||||
return (
|
||||
<LongPressGestureHandler onHandlerStateChange={onLongPressHandlerStateChange}>
|
||||
<Animated.View>
|
||||
<PanGestureHandler activeOffsetX={[-20, 20]} onGestureEvent={onGestureEvent} enabled={swipeEnabled}>
|
||||
<Animated.View>
|
||||
<LeftActions
|
||||
transX={transX}
|
||||
isRead={isRead}
|
||||
width={width}
|
||||
onToggleReadPress={onToggleReadPress}
|
||||
displayMode={displayMode}
|
||||
/>
|
||||
<RightActions
|
||||
transX={transX}
|
||||
favorite={favorite}
|
||||
width={width}
|
||||
toggleFav={handleToggleFav}
|
||||
onHidePress={onHidePress}
|
||||
displayMode={displayMode}
|
||||
/>
|
||||
<Animated.View style={animatedStyles}>
|
||||
<Touch
|
||||
onPress={handlePress}
|
||||
testID={testID}
|
||||
style={{
|
||||
backgroundColor: isFocused ? colors.chatComponentBackground : colors.backgroundColor
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Touch>
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
</Animated.View>
|
||||
</LongPressGestureHandler>
|
||||
);
|
||||
};
|
||||
</PanGestureHandler>
|
||||
</Animated.View>
|
||||
</LongPressGestureHandler>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default Touchable;
|
||||
|
|
|
@ -1,134 +1,127 @@
|
|||
import React, { useEffect, useReducer, useRef } from 'react';
|
||||
import { Subscription } from 'rxjs';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { getUserPresence } from '../../lib/methods';
|
||||
import { isGroupChat } from '../../lib/methods/helpers';
|
||||
import { formatDate } from '../../lib/methods/helpers/room';
|
||||
import { IRoomItemContainerProps } from './interfaces';
|
||||
import { IRoomItemContainerProps, ITouchableRef } from './interfaces';
|
||||
import RoomItem from './RoomItem';
|
||||
import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles';
|
||||
import { useUserStatus } from './useUserStatus';
|
||||
|
||||
export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED };
|
||||
|
||||
const attrs = ['width', 'isFocused', 'showLastMessage', 'autoJoin', 'showAvatar', 'displayMode'];
|
||||
const RoomItemContainer = ({
|
||||
item,
|
||||
id,
|
||||
onPress,
|
||||
onLongPress,
|
||||
toggleFav,
|
||||
toggleRead,
|
||||
hideChannel,
|
||||
isFocused,
|
||||
showLastMessage,
|
||||
username,
|
||||
useRealName,
|
||||
autoJoin,
|
||||
showAvatar,
|
||||
displayMode,
|
||||
getRoomTitle = () => 'title',
|
||||
getRoomAvatar = () => '',
|
||||
getIsRead = () => false,
|
||||
swipeEnabled = true
|
||||
}: IRoomItemContainerProps): React.ReactElement => {
|
||||
const name = getRoomTitle(item);
|
||||
const testID = `rooms-list-view-item-${name}`;
|
||||
const avatar = getRoomAvatar(item);
|
||||
const isRead = getIsRead(item);
|
||||
const date = item.roomUpdatedAt && formatDate(item.roomUpdatedAt);
|
||||
const alert = item.alert || item.tunread?.length;
|
||||
const connected = useAppSelector(state => state.meteor.connected);
|
||||
const userStatus = useAppSelector(state => state.activeUsers[id || '']?.status);
|
||||
const isDirect = !!(item.t === 'd' && id && !isGroupChat(item));
|
||||
const touchableRef = useRef<ITouchableRef>(null);
|
||||
|
||||
const RoomItemContainer = React.memo(
|
||||
({
|
||||
item,
|
||||
id,
|
||||
onPress,
|
||||
onLongPress,
|
||||
width,
|
||||
toggleFav,
|
||||
toggleRead,
|
||||
hideChannel,
|
||||
isFocused,
|
||||
showLastMessage,
|
||||
username,
|
||||
useRealName,
|
||||
autoJoin,
|
||||
showAvatar,
|
||||
displayMode,
|
||||
getRoomTitle = () => 'title',
|
||||
getRoomAvatar = () => '',
|
||||
getIsRead = () => false,
|
||||
swipeEnabled = true
|
||||
}: IRoomItemContainerProps) => {
|
||||
const name = getRoomTitle(item);
|
||||
const testID = `rooms-list-view-item-${name}`;
|
||||
const avatar = getRoomAvatar(item);
|
||||
const isRead = getIsRead(item);
|
||||
const date = item.roomUpdatedAt && formatDate(item.roomUpdatedAt);
|
||||
const alert = item.alert || item.tunread?.length;
|
||||
const [_, forceUpdate] = useReducer(x => x + 1, 1);
|
||||
const roomSubscription = useRef<Subscription | null>(null);
|
||||
|
||||
const { connected, status } = useUserStatus(item.t, item?.visitor?.status, id);
|
||||
|
||||
useEffect(() => {
|
||||
const init = () => {
|
||||
if (item?.observe) {
|
||||
const observable = item.observe();
|
||||
roomSubscription.current = observable?.subscribe?.(() => {
|
||||
if (_) forceUpdate();
|
||||
});
|
||||
}
|
||||
};
|
||||
init();
|
||||
|
||||
return () => roomSubscription.current?.unsubscribe();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const isDirect = !!(item.t === 'd' && id && !isGroupChat(item));
|
||||
if (connected && isDirect) {
|
||||
getUserPresence(id);
|
||||
}
|
||||
}, [connected]);
|
||||
|
||||
const handleOnPress = () => onPress(item);
|
||||
|
||||
const handleOnLongPress = () => onLongPress && onLongPress(item);
|
||||
|
||||
let accessibilityLabel = '';
|
||||
if (item.unread === 1) {
|
||||
accessibilityLabel = `, ${item.unread} ${I18n.t('alert')}`;
|
||||
} else if (item.unread > 1) {
|
||||
accessibilityLabel = `, ${item.unread} ${I18n.t('alerts')}`;
|
||||
// When app reconnects, we need to fetch the rendered user's presence
|
||||
useEffect(() => {
|
||||
if (connected && isDirect) {
|
||||
getUserPresence(id);
|
||||
}
|
||||
if (item.userMentions > 0) {
|
||||
accessibilityLabel = `, ${I18n.t('you_were_mentioned')}`;
|
||||
}
|
||||
if (date) {
|
||||
accessibilityLabel = `, ${I18n.t('last_message')} ${date}`;
|
||||
}, [connected]);
|
||||
|
||||
/**
|
||||
* The component can be recycled by FlashList.
|
||||
* When rid changes and there's no user's status, we need to fetch it
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!userStatus && isDirect) {
|
||||
getUserPresence(id);
|
||||
}
|
||||
|
||||
return (
|
||||
<RoomItem
|
||||
name={name}
|
||||
avatar={avatar}
|
||||
isGroupChat={isGroupChat(item)}
|
||||
isRead={isRead}
|
||||
onPress={handleOnPress}
|
||||
onLongPress={handleOnLongPress}
|
||||
date={date}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
width={width}
|
||||
favorite={item.f}
|
||||
rid={item.rid}
|
||||
toggleFav={toggleFav}
|
||||
toggleRead={toggleRead}
|
||||
hideChannel={hideChannel}
|
||||
testID={testID}
|
||||
type={item.t}
|
||||
isFocused={isFocused}
|
||||
prid={item.prid}
|
||||
status={status}
|
||||
hideUnreadStatus={item.hideUnreadStatus}
|
||||
hideMentionStatus={item.hideMentionStatus}
|
||||
alert={alert}
|
||||
lastMessage={item.lastMessage}
|
||||
showLastMessage={showLastMessage}
|
||||
username={username}
|
||||
useRealName={useRealName}
|
||||
unread={item.unread}
|
||||
userMentions={item.userMentions}
|
||||
groupMentions={item.groupMentions}
|
||||
tunread={item.tunread}
|
||||
tunreadUser={item.tunreadUser}
|
||||
tunreadGroup={item.tunreadGroup}
|
||||
swipeEnabled={swipeEnabled}
|
||||
teamMain={item.teamMain}
|
||||
autoJoin={autoJoin}
|
||||
showAvatar={showAvatar}
|
||||
displayMode={displayMode}
|
||||
sourceType={item.source}
|
||||
/>
|
||||
);
|
||||
},
|
||||
(props, nextProps) => attrs.every(key => props[key] === nextProps[key])
|
||||
);
|
||||
// TODO: Remove this when we have a better way to close the swipeable
|
||||
touchableRef?.current?.close();
|
||||
}, [item.rid]);
|
||||
|
||||
const handleOnPress = () => onPress(item);
|
||||
|
||||
const handleOnLongPress = () => onLongPress && onLongPress(item);
|
||||
|
||||
let accessibilityLabel = '';
|
||||
if (item.unread === 1) {
|
||||
accessibilityLabel = `, ${item.unread} ${I18n.t('alert')}`;
|
||||
} else if (item.unread > 1) {
|
||||
accessibilityLabel = `, ${item.unread} ${I18n.t('alerts')}`;
|
||||
}
|
||||
if (item.userMentions > 0) {
|
||||
accessibilityLabel = `, ${I18n.t('you_were_mentioned')}`;
|
||||
}
|
||||
if (date) {
|
||||
accessibilityLabel = `, ${I18n.t('last_message')} ${date}`;
|
||||
}
|
||||
|
||||
const status = item.t === 'l' ? item.visitor?.status || item.v?.status : userStatus;
|
||||
|
||||
return (
|
||||
<RoomItem
|
||||
touchableRef={touchableRef}
|
||||
name={name}
|
||||
avatar={avatar}
|
||||
isGroupChat={isGroupChat(item)}
|
||||
isRead={isRead}
|
||||
onPress={handleOnPress}
|
||||
onLongPress={handleOnLongPress}
|
||||
date={date}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
favorite={item.f}
|
||||
rid={item.rid}
|
||||
toggleFav={toggleFav}
|
||||
toggleRead={toggleRead}
|
||||
hideChannel={hideChannel}
|
||||
testID={testID}
|
||||
type={item.t}
|
||||
isFocused={isFocused}
|
||||
prid={item.prid}
|
||||
status={status}
|
||||
hideUnreadStatus={item.hideUnreadStatus}
|
||||
hideMentionStatus={item.hideMentionStatus}
|
||||
alert={alert}
|
||||
lastMessage={item.lastMessage}
|
||||
showLastMessage={showLastMessage}
|
||||
username={username}
|
||||
useRealName={useRealName}
|
||||
unread={item.unread}
|
||||
userMentions={item.userMentions}
|
||||
groupMentions={item.groupMentions}
|
||||
tunread={item.tunread}
|
||||
tunreadUser={item.tunreadUser}
|
||||
tunreadGroup={item.tunreadGroup}
|
||||
swipeEnabled={swipeEnabled}
|
||||
teamMain={item.teamMain}
|
||||
autoJoin={autoJoin}
|
||||
showAvatar={showAvatar}
|
||||
displayMode={displayMode}
|
||||
sourceType={item.source}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default RoomItemContainer;
|
||||
|
|
|
@ -78,7 +78,6 @@ interface IBaseRoomItem extends IRoomItemTouchables {
|
|||
showAvatar: boolean;
|
||||
swipeEnabled: boolean;
|
||||
autoJoin?: boolean;
|
||||
width: number;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
|
@ -116,6 +115,7 @@ export interface IRoomItemProps extends IBaseRoomItem {
|
|||
size?: number;
|
||||
sourceType: IOmnichannelSource;
|
||||
hideMentionStatus?: boolean;
|
||||
touchableRef: React.RefObject<ITouchableRef>;
|
||||
}
|
||||
|
||||
export interface ILastMessageProps {
|
||||
|
@ -127,11 +127,14 @@ export interface ILastMessageProps {
|
|||
alert: boolean;
|
||||
}
|
||||
|
||||
export interface ITouchableRef {
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export interface ITouchableProps extends IRoomItemTouchables {
|
||||
children: JSX.Element;
|
||||
type: SubscriptionType;
|
||||
testID: string;
|
||||
width: number;
|
||||
favorite: boolean;
|
||||
isRead: boolean;
|
||||
rid: string;
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import { TUserStatus } from '../../definitions';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { RoomTypes } from '../../lib/methods';
|
||||
|
||||
export const useUserStatus = (
|
||||
type: RoomTypes,
|
||||
liveChatStatus?: TUserStatus,
|
||||
id?: string
|
||||
): { connected: boolean; status: TUserStatus } => {
|
||||
const connected = useAppSelector(state => state.meteor.connected);
|
||||
const presenceDisabled = useAppSelector(state => state.settings.Presence_broadcast_disabled);
|
||||
const userStatus = useAppSelector(state => state.activeUsers[id || '']?.status);
|
||||
|
||||
let status = 'loading';
|
||||
if (connected) {
|
||||
if (type === 'd') {
|
||||
if (presenceDisabled) {
|
||||
status = 'disabled';
|
||||
} else {
|
||||
status = userStatus || 'loading';
|
||||
}
|
||||
} else if (type === 'l' && liveChatStatus) {
|
||||
status = liveChatStatus;
|
||||
}
|
||||
}
|
||||
return {
|
||||
connected,
|
||||
status: status as TUserStatus
|
||||
};
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { StyleProp, ViewStyle } from 'react-native';
|
||||
import { SvgUri } from 'react-native-svg';
|
||||
|
||||
|
@ -29,12 +29,22 @@ interface 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 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
|
||||
name={iconMap[sourceType?.type || 'other']}
|
||||
size={size}
|
||||
|
@ -42,23 +52,4 @@ export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnich
|
|||
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';
|
||||
|
||||
const StatusContainer = ({ id, style, size = 32, ...props }: Omit<IStatus, 'status'>): React.ReactElement => {
|
||||
const status = useAppSelector(state => {
|
||||
if (state.settings.Presence_broadcast_disabled) {
|
||||
return 'disabled';
|
||||
}
|
||||
if (state.meteor.connected) {
|
||||
return state.activeUsers[id] && state.activeUsers[id].status;
|
||||
}
|
||||
return 'loading';
|
||||
}) as TUserStatus;
|
||||
const status = useAppSelector(state =>
|
||||
state.meteor.connected ? state.activeUsers[id] && state.activeUsers[id].status : 'loading'
|
||||
) as TUserStatus;
|
||||
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 { useAppSelector } from '../../../../lib/hooks';
|
||||
import { getRoomAvatar, getUidDirectMessage } from '../../../../lib/methods/helpers';
|
||||
import { videoConfStartAndJoin } from '../../../../lib/methods/videoConf';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import { useActionSheet } from '../../../ActionSheet';
|
||||
import AvatarContainer from '../../../Avatar';
|
||||
|
@ -15,12 +16,12 @@ import { BUTTON_HIT_SLOP } from '../../../message/utils';
|
|||
import StatusContainer from '../../../Status';
|
||||
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 { colors } = useTheme();
|
||||
const [user, setUser] = useState({ username: '', avatar: '', uid: '' });
|
||||
const [mic, setMic] = useState(true);
|
||||
const [cam, setCam] = useState(false);
|
||||
const [user, setUser] = useState({ username: '', avatar: '', uid: '', rid: '' });
|
||||
const [phone, setPhone] = useState(true);
|
||||
const [camera, setCamera] = useState(false);
|
||||
const username = useAppSelector(state => state.login.user.username);
|
||||
|
||||
const { hideActionSheet } = useActionSheet();
|
||||
|
@ -30,7 +31,7 @@ export default function StartACallActionSheet({ rid, initCall }: { rid: string;
|
|||
const room = await getSubscriptionByRoomId(rid);
|
||||
const uid = (await getUidDirectMessage(room)) as string;
|
||||
const avt = getRoomAvatar(room);
|
||||
setUser({ uid, username: room?.name || '', avatar: avt });
|
||||
setUser({ uid, username: room?.name || '', avatar: avt, rid: room?.id || '' });
|
||||
})();
|
||||
}, [rid]);
|
||||
|
||||
|
@ -42,27 +43,25 @@ export default function StartACallActionSheet({ rid, initCall }: { rid: string;
|
|||
<Text style={style.actionSheetHeaderTitle}>{i18n.t('Start_a_call')}</Text>
|
||||
<View style={style.actionSheetHeaderButtons}>
|
||||
<Touchable
|
||||
onPress={() => setCam(!cam)}
|
||||
style={[style.iconCallContainer, cam && style.enabledBackground, { marginRight: 6 }]}
|
||||
onPress={() => setCamera(!camera)}
|
||||
style={[style.iconCallContainer, camera && style.enabledBackground, { marginRight: 6 }]}
|
||||
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
|
||||
onPress={() => setMic(!mic)}
|
||||
style={[style.iconCallContainer, mic && style.enabledBackground]}
|
||||
onPress={() => setPhone(!phone)}
|
||||
style={[style.iconCallContainer, phone && style.enabledBackground]}
|
||||
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>
|
||||
</View>
|
||||
</View>
|
||||
<View style={style.actionSheetUsernameContainer}>
|
||||
<AvatarContainer text={user.avatar} size={36} />
|
||||
<StatusContainer size={16} id={user.uid} style={{ marginLeft: 8, marginRight: 6 }} />
|
||||
<Text style={style.actionSheetUsername} numberOfLines={1}>
|
||||
{user.username}
|
||||
</Text>
|
||||
<Text style={style.actionSheetUsername}>{user.username}</Text>
|
||||
</View>
|
||||
<View style={style.actionSheetPhotoContainer}>
|
||||
<AvatarContainer size={62} text={username} />
|
||||
|
@ -71,7 +70,7 @@ export default function StartACallActionSheet({ rid, initCall }: { rid: string;
|
|||
onPress={() => {
|
||||
hideActionSheet();
|
||||
setTimeout(() => {
|
||||
initCall({ cam, mic });
|
||||
videoConfStartAndJoin(user.rid, camera);
|
||||
}, 100);
|
||||
}}
|
||||
title={i18n.t('Call')}
|
|
@ -3,16 +3,17 @@ import { Text } from 'react-native';
|
|||
import Touchable from 'react-native-platform-touchable';
|
||||
|
||||
import i18n from '../../../../i18n';
|
||||
import { videoConfJoin } from '../../../../lib/methods/videoConf';
|
||||
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
||||
import useStyle from './styles';
|
||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||
|
||||
const VideoConferenceDirect = React.memo(({ blockId }: { blockId: string }) => {
|
||||
const style = useStyle();
|
||||
const { joinCall } = useVideoConf();
|
||||
|
||||
return (
|
||||
<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>
|
||||
</Touchable>
|
||||
<Text style={style.callBack}>{i18n.t('Waiting_for_answer')}</Text>
|
||||
|
|
|
@ -6,7 +6,9 @@ import { IUser } from '../../../../definitions';
|
|||
import { VideoConferenceType } from '../../../../definitions/IVideoConference';
|
||||
import i18n from '../../../../i18n';
|
||||
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 useStyle from './styles';
|
||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||
|
@ -24,7 +26,8 @@ export default function VideoConferenceEnded({
|
|||
}): React.ReactElement {
|
||||
const style = useStyle();
|
||||
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);
|
||||
|
||||
|
@ -32,7 +35,15 @@ export default function VideoConferenceEnded({
|
|||
<VideoConferenceBaseContainer variant='ended'>
|
||||
{type === 'direct' ? (
|
||||
<>
|
||||
<Touchable style={style.callToActionCallBack} onPress={showInitCallActionSheet}>
|
||||
<Touchable
|
||||
style={style.callToActionCallBack}
|
||||
onPress={() =>
|
||||
showActionSheet({
|
||||
children: <CallAgainActionSheet rid={rid} />,
|
||||
snaps
|
||||
})
|
||||
}
|
||||
>
|
||||
<Text style={style.callToActionCallBackText}>
|
||||
{createdBy.username === username ? i18n.t('Call_back') : i18n.t('Call_again')}
|
||||
</Text>
|
||||
|
|
|
@ -3,17 +3,18 @@ import { Text } from 'react-native';
|
|||
import Touchable from 'react-native-platform-touchable';
|
||||
|
||||
import i18n from '../../../../i18n';
|
||||
import { videoConfJoin } from '../../../../lib/methods/videoConf';
|
||||
import { useVideoConf } from '../../../../lib/hooks/useVideoConf';
|
||||
import { CallParticipants, TCallUsers } from './CallParticipants';
|
||||
import useStyle from './styles';
|
||||
import { VideoConferenceBaseContainer } from './VideoConferenceBaseContainer';
|
||||
|
||||
export default function VideoConferenceOutgoing({ users, blockId }: { users: TCallUsers; blockId: string }): React.ReactElement {
|
||||
const style = useStyle();
|
||||
const { joinCall } = useVideoConf();
|
||||
|
||||
return (
|
||||
<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>
|
||||
</Touchable>
|
||||
<CallParticipants users={users} />
|
||||
|
|
|
@ -100,8 +100,7 @@ export default function useStyle() {
|
|||
actionSheetUsername: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textBold,
|
||||
color: colors.passcodePrimary,
|
||||
flexShrink: 1
|
||||
color: colors.passcodePrimary
|
||||
},
|
||||
enabledBackground: {
|
||||
backgroundColor: colors.conferenceCallEnabledIconBackground
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||
import React, { useContext, useState } from 'react';
|
||||
|
||||
import { videoConfJoin } from '../../lib/methods/videoConf';
|
||||
import { useVideoConf } from '../../lib/hooks/useVideoConf';
|
||||
import { IText } from './interfaces';
|
||||
|
||||
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 { value = initialValue } = values[actionId] || {};
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { joinCall } = useVideoConf();
|
||||
|
||||
const error = errors && actionId && errors[actionId];
|
||||
|
||||
|
@ -57,7 +58,7 @@ export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUse
|
|||
try {
|
||||
if (appId === 'videoconf-core' && blockId) {
|
||||
setLoading(false);
|
||||
return videoConfJoin(blockId);
|
||||
return joinCall(blockId);
|
||||
}
|
||||
await action({
|
||||
blockId,
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import React from 'react';
|
||||
import { View, StyleSheet, Text, ViewStyle } from 'react-native';
|
||||
|
||||
import sharedStyles from '../views/Styles';
|
||||
import { useTheme } from '../theme';
|
||||
import openLink from '../lib/methods/helpers/openLink';
|
||||
import { useAppSelector } from '../lib/hooks';
|
||||
import I18n from '../i18n';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
bottomContainer: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
marginBottom: 32,
|
||||
marginHorizontal: 30
|
||||
},
|
||||
bottomContainerText: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 13,
|
||||
textAlign: 'center'
|
||||
},
|
||||
bottomContainerTextBold: {
|
||||
...sharedStyles.textSemibold,
|
||||
fontSize: 13,
|
||||
textAlign: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
const UGCRules = ({ styleContainer }: { styleContainer?: ViewStyle }) => {
|
||||
const { colors, theme } = useTheme();
|
||||
const { server } = useAppSelector(state => ({
|
||||
server: state.server.server
|
||||
}));
|
||||
|
||||
const openContract = (route: string) => {
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
openLink(`${server}/${route}`, theme);
|
||||
};
|
||||
return (
|
||||
<View style={[styles.bottomContainer, styleContainer]}>
|
||||
<Text style={[styles.bottomContainerText, { color: colors.auxiliaryText }]}>
|
||||
{`${I18n.t('Onboarding_agree_terms')}\n`}
|
||||
<Text
|
||||
style={[styles.bottomContainerTextBold, { color: colors.actionTintColor }]}
|
||||
onPress={() => openContract('terms-of-service')}
|
||||
>
|
||||
{I18n.t('Terms_of_Service')}
|
||||
</Text>{' '}
|
||||
{I18n.t('and')}
|
||||
<Text
|
||||
style={[styles.bottomContainerTextBold, { color: colors.actionTintColor }]}
|
||||
onPress={() => openContract('privacy-policy')}
|
||||
>
|
||||
{' '}
|
||||
{I18n.t('Privacy_Policy')}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default UGCRules;
|
|
@ -18,29 +18,13 @@ import MarkdownContext from './MarkdownContext';
|
|||
|
||||
interface IParagraphProps {
|
||||
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);
|
||||
return (
|
||||
<Text style={styles.inline}>
|
||||
{value.map((block, index) => {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
{value.map(block => {
|
||||
switch (block.type) {
|
||||
case 'IMAGE':
|
||||
return <Image value={block.value} />;
|
||||
|
|
|
@ -381,6 +381,27 @@ export const BlockQuote = () => (
|
|||
</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 = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
|
@ -466,6 +487,7 @@ const markdownLinkWithEmphasis = [
|
|||
|
||||
export const Links = () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={rocketChatLink} />
|
||||
<NewMarkdown tokens={markdownLink} />
|
||||
<NewMarkdown tokens={markdownLinkWithEmphasis} />
|
||||
</View>
|
||||
|
@ -784,128 +806,3 @@ export const InlineKatex = () => (
|
|||
<NewMarkdown tokens={inlineKatex} />
|
||||
</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) => {
|
||||
let forceTrim = false;
|
||||
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 (
|
||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>
|
||||
<Inline value={value} forceTrim={forceTrim} />
|
||||
<Inline value={value} />
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ interface IPlainProps {
|
|||
value: PlainProps['value'];
|
||||
}
|
||||
|
||||
const Plain = ({ value }: IPlainProps): React.ReactElement => (
|
||||
const Plain = ({ value }: IPlainProps) => (
|
||||
<Text accessibilityLabel={value} style={styles.plainText}>
|
||||
{value}
|
||||
</Text>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Tasks as TasksProps } from '@rocket.chat/message-parser';
|
|||
|
||||
import Inline from './Inline';
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../lib/constants';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
interface ITasksProps {
|
||||
|
@ -11,15 +12,13 @@ interface ITasksProps {
|
|||
}
|
||||
|
||||
const TaskList = ({ value = [] }: ITasksProps) => {
|
||||
const { colors } = useTheme();
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<View>
|
||||
{value.map(item => (
|
||||
<View style={styles.row}>
|
||||
<Text style={[styles.text, { color: colors.bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
|
||||
<Text style={[styles.inline, { color: colors.bodyText }]}>
|
||||
<Inline value={item.value} />
|
||||
</Text>
|
||||
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
|
||||
<Inline value={item.value} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
|
|
@ -68,7 +68,7 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
if (file && file.image_url) {
|
||||
return (
|
||||
<Image
|
||||
key={file.image_url}
|
||||
key={index}
|
||||
file={file}
|
||||
showAttachment={showAttachment}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
|
@ -81,7 +81,7 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
if (file && file.audio_url) {
|
||||
return (
|
||||
<Audio
|
||||
key={file.audio_url}
|
||||
key={index}
|
||||
file={file}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
isReply={isReply}
|
||||
|
@ -95,7 +95,7 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
if (file.video_url) {
|
||||
return (
|
||||
<Video
|
||||
key={file.video_url}
|
||||
key={index}
|
||||
file={file}
|
||||
showAttachment={showAttachment}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
|
|
|
@ -10,12 +10,12 @@ import { themes } from '../../lib/constants';
|
|||
import { IMessageCallButton } from './interfaces';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
const CallButton = React.memo(({ handleEnterCall }: IMessageCallButton) => {
|
||||
const CallButton = React.memo(({ callJitsi }: IMessageCallButton) => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<View style={styles.buttonContainer}>
|
||||
<Touchable
|
||||
onPress={handleEnterCall}
|
||||
onPress={callJitsi}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
|
|
|
@ -90,8 +90,8 @@ const Fields = React.memo(
|
|||
|
||||
return (
|
||||
<>
|
||||
{attachment.fields.map(field => (
|
||||
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
|
||||
{attachment.fields.map((field, index) => (
|
||||
<View key={index} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
|
||||
<Text testID='collapsibleQuoteTouchableFieldTitle' style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>
|
||||
{field.title}
|
||||
</Text>
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
import React from 'react';
|
||||
|
||||
import { themes } from '../../../../lib/constants';
|
||||
import { CustomIcon } from '../../../CustomIcon';
|
||||
import styles from '../../styles';
|
||||
import { useTheme } from '../../../../theme';
|
||||
|
||||
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread?: boolean }) => {
|
||||
const { colors } = useTheme();
|
||||
if (isReadReceiptEnabled) {
|
||||
return (
|
||||
<CustomIcon
|
||||
name='check'
|
||||
color={!unread && unread !== null ? colors.tintColor : colors.auxiliaryTintColor}
|
||||
size={16}
|
||||
style={styles.rightIcons}
|
||||
/>
|
||||
);
|
||||
const { theme } = useTheme();
|
||||
if (isReadReceiptEnabled && !unread && unread !== null) {
|
||||
return <CustomIcon name='check' color={themes[theme].tintColor} size={16} style={styles.rightIcons} />;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
|
|
@ -53,7 +53,7 @@ const Content = React.memo(
|
|||
content = (
|
||||
<Markdown
|
||||
msg={props.msg}
|
||||
md={props.type !== 'e2e' ? props.md : undefined}
|
||||
md={props.md}
|
||||
getCustomEmoji={props.getCustomEmoji}
|
||||
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
||||
username={user.username}
|
||||
|
|
|
@ -10,7 +10,7 @@ const Emoji = React.memo(
|
|||
const parsedContent = content.replace(/^:|:$/g, '');
|
||||
const emoji = getCustomEmoji(parsedContent);
|
||||
if (emoji) {
|
||||
return <CustomEmoji key={content} style={customEmojiStyle} emoji={emoji} />;
|
||||
return <CustomEmoji style={customEmojiStyle} emoji={emoji} />;
|
||||
}
|
||||
return <Text style={standardEmojiStyle}>{shortnameToUnicode(content)}</Text>;
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ const MessageAvatar = React.memo(({ isHeader, avatar, author, small, navToRoomIn
|
|||
style={small ? styles.avatarSmall : styles.avatar}
|
||||
text={avatar ? '' : author.username}
|
||||
size={small ? 20 : 36}
|
||||
borderRadius={4}
|
||||
borderRadius={small ? 2 : 4}
|
||||
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
avatar={avatar}
|
||||
|
|
|
@ -53,7 +53,6 @@ const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReacti
|
|||
<Touchable
|
||||
onPress={() => onReactionPress(reaction.emoji)}
|
||||
onLongPress={onReactionLongPress}
|
||||
key={reaction.emoji}
|
||||
testID={`message-reaction-${reaction.emoji}`}
|
||||
style={[
|
||||
styles.reactionButton,
|
||||
|
@ -83,8 +82,8 @@ const Reactions = React.memo(({ reactions, getCustomEmoji }: IMessageReactions)
|
|||
}
|
||||
return (
|
||||
<View style={styles.reactionsContainer}>
|
||||
{reactions.map(reaction => (
|
||||
<Reaction key={reaction.emoji} reaction={reaction} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
{reactions.map((reaction, index) => (
|
||||
<Reaction key={index} reaction={reaction} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
))}
|
||||
<AddReaction theme={theme} />
|
||||
</View>
|
||||
|
|
|
@ -184,8 +184,8 @@ const Fields = React.memo(
|
|||
|
||||
return (
|
||||
<View style={styles.fieldsContainer}>
|
||||
{attachment.fields.map(field => (
|
||||
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
|
||||
{attachment.fields.map((field, index) => (
|
||||
<View key={index} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
|
||||
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
|
||||
<Markdown msg={field?.value || ''} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
</View>
|
||||
|
@ -247,8 +247,6 @@ const Reply = React.memo(
|
|||
>
|
||||
<View style={styles.attachmentContainer}>
|
||||
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
|
||||
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
<UrlImage image={attachment.thumb_url} />
|
||||
<Attachments
|
||||
attachments={attachment.attachments}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
|
@ -257,6 +255,8 @@ const Reply = React.memo(
|
|||
isReply
|
||||
id={messageId}
|
||||
/>
|
||||
<UrlImage image={attachment.thumb_url} />
|
||||
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
{loading ? (
|
||||
<View style={[styles.backdrop]}>
|
||||
|
|
|
@ -141,7 +141,7 @@ const Urls = React.memo(
|
|||
return null;
|
||||
}
|
||||
|
||||
return urls.map((url: IUrl, index: number) => <Url url={url} key={url.url} index={index} theme={theme} />);
|
||||
return urls.map((url: IUrl, index: number) => <Url url={url} key={index} index={index} theme={theme} />);
|
||||
},
|
||||
(oldProps, newProps) => dequal(oldProps.urls, newProps.urls)
|
||||
);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Keyboard, ViewStyle } from 'react-native';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Keyboard } from 'react-native';
|
||||
|
||||
import Message from './Message';
|
||||
import MessageContext from './Context';
|
||||
|
@ -8,67 +7,15 @@ import { debounce } from '../../lib/methods/helpers';
|
|||
import { getMessageTranslation } from './utils';
|
||||
import { TSupportedThemes, withTheme } from '../../theme';
|
||||
import openLink from '../../lib/methods/helpers/openLink';
|
||||
import { IAttachment, TAnyMessageModel, TGetCustomEmoji } from '../../definitions';
|
||||
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||
import { IAttachment } from '../../definitions';
|
||||
import { E2E_MESSAGE_TYPE, E2E_STATUS, messagesStatus } from '../../lib/constants';
|
||||
import { IMessageContainerProps, TAnyMessageContainerState } from './interfaces';
|
||||
|
||||
interface IMessageContainerProps {
|
||||
item: TAnyMessageModel;
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
token: string;
|
||||
};
|
||||
msg?: string;
|
||||
rid: string;
|
||||
timeFormat?: string;
|
||||
style?: ViewStyle;
|
||||
archived?: boolean;
|
||||
broadcast?: boolean;
|
||||
previousItem?: TAnyMessageModel;
|
||||
baseUrl: string;
|
||||
Message_GroupingPeriod?: number;
|
||||
isReadReceiptEnabled?: boolean;
|
||||
isThreadRoom: boolean;
|
||||
isSystemMessage?: boolean;
|
||||
useRealName?: boolean;
|
||||
autoTranslateRoom?: boolean;
|
||||
autoTranslateLanguage?: string;
|
||||
status?: number;
|
||||
isIgnored?: boolean;
|
||||
highlighted?: boolean;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
onLongPress?: (item: TAnyMessageModel) => void;
|
||||
onReactionPress?: (emoji: string, id: string) => void;
|
||||
onEncryptedPress?: () => void;
|
||||
onDiscussionPress?: (item: TAnyMessageModel) => void;
|
||||
onThreadPress?: (item: TAnyMessageModel) => void;
|
||||
errorActionsShow?: (item: TAnyMessageModel) => void;
|
||||
replyBroadcast?: (item: TAnyMessageModel) => void;
|
||||
reactionInit?: (item: TAnyMessageModel) => void;
|
||||
fetchThreadName?: (tmid: string, id: string) => Promise<string | undefined>;
|
||||
showAttachment: (file: IAttachment) => void;
|
||||
onReactionLongPress?: (item: TAnyMessageModel) => void;
|
||||
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||
handleEnterCall?: () => void;
|
||||
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
||||
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
|
||||
threadBadgeColor?: string;
|
||||
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
|
||||
jumpToMessage?: (link: string) => void;
|
||||
onPress?: () => void;
|
||||
theme: TSupportedThemes;
|
||||
closeEmojiAndAction?: (action?: Function, params?: any) => void;
|
||||
}
|
||||
|
||||
interface IMessageContainerState {
|
||||
isManualUnignored: boolean;
|
||||
}
|
||||
|
||||
class MessageContainer extends React.Component<IMessageContainerProps, IMessageContainerState> {
|
||||
class MessageContainer extends React.Component<IMessageContainerProps, TAnyMessageContainerState> {
|
||||
static defaultProps = {
|
||||
getCustomEmoji: () => null,
|
||||
onLongPress: () => {},
|
||||
callJitsi: () => {},
|
||||
blockAction: () => {},
|
||||
archived: false,
|
||||
broadcast: false,
|
||||
|
@ -78,45 +25,6 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
|
||||
state = { isManualUnignored: false };
|
||||
|
||||
private subscription?: Subscription;
|
||||
|
||||
componentDidMount() {
|
||||
const { item } = this.props;
|
||||
if (item && item.observe) {
|
||||
const observable = item.observe();
|
||||
this.subscription = observable.subscribe(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: IMessageContainerProps, nextState: IMessageContainerState) {
|
||||
const { isManualUnignored } = this.state;
|
||||
const { threadBadgeColor, isIgnored, highlighted, previousItem } = this.props;
|
||||
if (nextProps.highlighted !== highlighted) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.threadBadgeColor !== threadBadgeColor) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.isIgnored !== isIgnored) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.isManualUnignored !== isManualUnignored) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.previousItem?._id !== previousItem?._id) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.subscription && this.subscription.unsubscribe) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
onPressAction = () => {
|
||||
const { closeEmojiAndAction } = this.props;
|
||||
|
||||
|
@ -226,11 +134,11 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
try {
|
||||
if (
|
||||
previousItem &&
|
||||
// @ts-ignore TODO: IMessage vs IMessageFromServer non-sense
|
||||
// @ts-ignore TODO: TAnyMessage vs TAnyMessageFromServer non-sense
|
||||
previousItem.ts.toDateString() === item.ts.toDateString() &&
|
||||
previousItem.u.username === item.u.username &&
|
||||
!(previousItem.groupable === false || item.groupable === false || broadcast === true) &&
|
||||
// @ts-ignore TODO: IMessage vs IMessageFromServer non-sense
|
||||
// @ts-ignore TODO: TAnyMessage vs TAnyMessageFromServer non-sense
|
||||
item.ts - previousItem.ts < Message_GroupingPeriod * 1000 &&
|
||||
previousItem.tmid === item.tmid &&
|
||||
item.t !== 'rm' &&
|
||||
|
@ -337,7 +245,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
navToRoomInfo,
|
||||
getCustomEmoji,
|
||||
isThreadRoom,
|
||||
handleEnterCall,
|
||||
callJitsi,
|
||||
blockAction,
|
||||
rid,
|
||||
threadBadgeColor,
|
||||
|
@ -408,7 +316,6 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
replies
|
||||
}}
|
||||
>
|
||||
{/* @ts-ignore*/}
|
||||
<Message
|
||||
id={id}
|
||||
msg={message}
|
||||
|
@ -455,7 +362,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
|||
showAttachment={showAttachment}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
navToRoomInfo={navToRoomInfo}
|
||||
handleEnterCall={handleEnterCall}
|
||||
callJitsi={callJitsi}
|
||||
blockAction={blockAction}
|
||||
highlighted={highlighted}
|
||||
comment={comment}
|
||||
|
|
|
@ -1,11 +1,65 @@
|
|||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
import { StyleProp, TextStyle } from 'react-native';
|
||||
import { StyleProp, TextStyle, ViewStyle } from 'react-native';
|
||||
import { ImageStyle } from 'react-native-fast-image';
|
||||
|
||||
import { IUserChannel } from '../markdown/interfaces';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IAttachment, IThread, IUrl, IUserMention, IUserMessage, MessageType, TAnyMessageModel } from '../../definitions';
|
||||
import { IAttachment, IThread, IUrl, IUserMention, IUserMessage, MessageType, TAnyMessage } from '../../definitions';
|
||||
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
|
||||
export interface IMessageContainerProps {
|
||||
item: TAnyMessage;
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
token: string;
|
||||
};
|
||||
msg?: string;
|
||||
rid: string;
|
||||
timeFormat?: string;
|
||||
style?: ViewStyle;
|
||||
archived?: boolean;
|
||||
broadcast?: boolean;
|
||||
previousItem?: TAnyMessage;
|
||||
baseUrl: string;
|
||||
Message_GroupingPeriod?: number;
|
||||
isReadReceiptEnabled?: boolean;
|
||||
isThreadRoom: boolean;
|
||||
isSystemMessage?: boolean;
|
||||
useRealName?: boolean;
|
||||
autoTranslateRoom?: boolean;
|
||||
autoTranslateLanguage?: string;
|
||||
status?: number;
|
||||
isIgnored?: boolean;
|
||||
highlighted?: boolean;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
onLongPress?: (item: TAnyMessage) => void;
|
||||
onReactionPress?: (emoji: string, id: string) => void;
|
||||
onEncryptedPress?: () => void;
|
||||
onDiscussionPress?: (item: TAnyMessage) => void;
|
||||
onThreadPress?: (item: TAnyMessage) => void;
|
||||
errorActionsShow?: (item: TAnyMessage) => void;
|
||||
replyBroadcast?: (item: TAnyMessage) => void;
|
||||
reactionInit?: (item: TAnyMessage) => void;
|
||||
fetchThreadName?: (tmid: string, id: string) => Promise<string | undefined>;
|
||||
showAttachment: (file: IAttachment) => void;
|
||||
onReactionLongPress?: (item: TAnyMessage) => void;
|
||||
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||
callJitsi?: () => void;
|
||||
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
||||
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
|
||||
threadBadgeColor?: string;
|
||||
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
|
||||
jumpToMessage?: (link: string) => void;
|
||||
onPress?: () => void;
|
||||
theme: TSupportedThemes;
|
||||
closeEmojiAndAction?: (action?: Function, params?: any) => void;
|
||||
}
|
||||
|
||||
export interface TAnyMessageContainerState {
|
||||
isManualUnignored: boolean;
|
||||
}
|
||||
|
||||
export interface IMessageAttachments {
|
||||
attachments?: IAttachment[];
|
||||
|
@ -40,11 +94,10 @@ export interface IMessageBroadcast {
|
|||
}
|
||||
|
||||
export interface IMessageCallButton {
|
||||
handleEnterCall?: () => void;
|
||||
callJitsi?: () => void;
|
||||
}
|
||||
|
||||
export interface IMessageContent {
|
||||
_id: string;
|
||||
isTemp: boolean;
|
||||
isInfo: string | boolean;
|
||||
tmid?: string;
|
||||
|
@ -119,7 +172,6 @@ export interface IMessage extends IMessageRepliedThread, IMessageInner, IMessage
|
|||
hasError: boolean;
|
||||
style: any;
|
||||
// style: ViewStyle;
|
||||
onLongPress?: (item: TAnyMessageModel) => void;
|
||||
isReadReceiptEnabled?: boolean;
|
||||
unread?: boolean;
|
||||
isIgnored: boolean;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable complexity */
|
||||
import { MessageTypesValues, TMessageModel } from '../../definitions/IMessage';
|
||||
import { MessageTypesValues, TAnyMessage } from '../../definitions/IMessage';
|
||||
import I18n from '../../i18n';
|
||||
import { DISCUSSION } from './constants';
|
||||
|
||||
|
@ -183,7 +183,7 @@ export const getInfoMessage = ({ type, role, msg, author, comment }: TInfoMessag
|
|||
}
|
||||
};
|
||||
|
||||
export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string): string | null => {
|
||||
export const getMessageTranslation = (message: TAnyMessage, autoTranslateLanguage: string): string | null => {
|
||||
if (!autoTranslateLanguage) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -4,15 +4,14 @@ import { MarkdownAST } from '@rocket.chat/message-parser';
|
|||
import { MessageTypeLoad } from '../lib/constants';
|
||||
import { IAttachment } from './IAttachment';
|
||||
import { IReaction } from './IReaction';
|
||||
import { TThreadMessageModel } from './IThreadMessage';
|
||||
import { TThreadModel } from './IThread';
|
||||
import { IThreadMessage } from './IThreadMessage';
|
||||
import { IThread } from './IThread';
|
||||
import { IUrl, IUrlFromServer } from './IUrl';
|
||||
|
||||
export type MessageType =
|
||||
| 'jitsi_call_started'
|
||||
| 'discussion-created'
|
||||
| 'e2e'
|
||||
| 'load_more'
|
||||
| 'rm'
|
||||
| 'uj'
|
||||
| MessageTypeLoad
|
||||
|
@ -147,9 +146,12 @@ export interface IMessage extends IMessageFromServer {
|
|||
editedAt?: string | Date;
|
||||
}
|
||||
|
||||
export type TMessageModel = IMessage & Model;
|
||||
export type TMessageModel = IMessage &
|
||||
Model & {
|
||||
asPlain: () => IMessage;
|
||||
};
|
||||
|
||||
export type TAnyMessageModel = TMessageModel | TThreadModel | TThreadMessageModel;
|
||||
export type TAnyMessage = IMessage | IThread | IThreadMessage;
|
||||
export type TTypeMessages = IMessageFromServer | ILoadMoreMessage | IMessage;
|
||||
|
||||
// Read receipts to ReadReceiptView and chat.getMessageReadReceipts
|
||||
|
|
|
@ -91,13 +91,11 @@ export interface ISubscription {
|
|||
livechatData?: any;
|
||||
tags?: string[];
|
||||
E2EKey?: string;
|
||||
E2ESuggestedKey?: string;
|
||||
encrypted?: boolean;
|
||||
e2eKeyId?: string;
|
||||
avatarETag?: string;
|
||||
teamId?: string;
|
||||
teamMain?: boolean;
|
||||
unsubscribe: () => Promise<any>;
|
||||
separator?: boolean;
|
||||
onHold?: boolean;
|
||||
source?: IOmnichannelSource;
|
||||
|
@ -109,7 +107,10 @@ export interface ISubscription {
|
|||
uploads: RelationModified<TUploadModel>;
|
||||
}
|
||||
|
||||
export type TSubscriptionModel = ISubscription & Model;
|
||||
export type TSubscriptionModel = ISubscription &
|
||||
Model & {
|
||||
asPlain: () => ISubscription;
|
||||
};
|
||||
export type TSubscription = TSubscriptionModel | ISubscription;
|
||||
|
||||
// https://github.com/RocketChat/Rocket.Chat/blob/a88a96fcadd925b678ff27ada37075e029f78b5e/definition/ISubscription.ts#L8
|
||||
|
@ -146,7 +147,6 @@ export interface IServerSubscription extends IRocketChatRecord {
|
|||
onHold?: boolean;
|
||||
encrypted?: boolean;
|
||||
E2EKey?: string;
|
||||
E2ESuggestedKey?: string;
|
||||
unreadAlert?: 'default' | 'all' | 'mentions' | 'nothing';
|
||||
|
||||
fname?: unknown;
|
||||
|
|
|
@ -38,4 +38,7 @@ export interface IThread extends IMessage {
|
|||
draftMessage?: string;
|
||||
}
|
||||
|
||||
export type TThreadModel = IThread & Model;
|
||||
export type TThreadModel = IThread &
|
||||
Model & {
|
||||
asPlain: () => IThread;
|
||||
};
|
||||
|
|
|
@ -6,4 +6,7 @@ export interface IThreadMessage extends IMessage {
|
|||
tmsg?: string;
|
||||
}
|
||||
|
||||
export type TThreadMessageModel = IThreadMessage & Model;
|
||||
export type TThreadMessageModel = IThreadMessage &
|
||||
Model & {
|
||||
asPlain: () => IThreadMessage;
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@ export interface IUpload {
|
|||
rid?: string;
|
||||
path: string;
|
||||
name?: string;
|
||||
tmid?: string;
|
||||
description?: string;
|
||||
size: number;
|
||||
type?: string;
|
||||
|
@ -13,7 +12,6 @@ export interface IUpload {
|
|||
progress?: number;
|
||||
error?: boolean;
|
||||
subscription?: { id: string };
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
export type TUploadModel = IUpload & Model;
|
||||
|
|
|
@ -4,34 +4,37 @@ import type { IRoom } from './IRoom';
|
|||
import type { IUser } from './IUser';
|
||||
import type { IMessage } from './IMessage';
|
||||
|
||||
export declare enum VideoConferenceStatus {
|
||||
export enum VideoConferenceStatus {
|
||||
CALLING = 0,
|
||||
STARTED = 1,
|
||||
EXPIRED = 2,
|
||||
ENDED = 3,
|
||||
DECLINED = 4
|
||||
}
|
||||
export declare type DirectCallInstructions = {
|
||||
|
||||
export type DirectCallInstructions = {
|
||||
type: 'direct';
|
||||
calleeId: IUser['_id'];
|
||||
callee: IUser['_id'];
|
||||
callId: string;
|
||||
};
|
||||
export declare type ConferenceInstructions = {
|
||||
|
||||
export type ConferenceInstructions = {
|
||||
type: 'videoconference';
|
||||
callId: string;
|
||||
rid: IRoom['_id'];
|
||||
};
|
||||
export declare type LivechatInstructions = {
|
||||
|
||||
export type LivechatInstructions = {
|
||||
type: 'livechat';
|
||||
callId: string;
|
||||
};
|
||||
export declare type VideoConferenceType =
|
||||
| DirectCallInstructions['type']
|
||||
| ConferenceInstructions['type']
|
||||
| LivechatInstructions['type'];
|
||||
|
||||
export type VideoConferenceType = DirectCallInstructions['type'] | ConferenceInstructions['type'] | LivechatInstructions['type'];
|
||||
|
||||
export interface IVideoConferenceUser extends Pick<Required<IUser>, '_id' | 'username' | 'name' | 'avatarETag'> {
|
||||
ts: Date;
|
||||
}
|
||||
|
||||
export interface IVideoConference extends IRocketChatRecord {
|
||||
type: VideoConferenceType;
|
||||
rid: string;
|
||||
|
@ -42,68 +45,51 @@ export interface IVideoConference extends IRocketChatRecord {
|
|||
ended?: IMessage['_id'];
|
||||
};
|
||||
url?: string;
|
||||
createdBy: Pick<Required<IUser>, '_id' | 'username' | 'name'>;
|
||||
|
||||
createdBy: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
createdAt: Date;
|
||||
endedBy?: Pick<Required<IUser>, '_id' | 'username' | 'name'>;
|
||||
|
||||
endedBy?: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
endedAt?: Date;
|
||||
|
||||
providerName: string;
|
||||
providerData?: Record<string, any>;
|
||||
|
||||
ringing?: boolean;
|
||||
}
|
||||
|
||||
export interface IDirectVideoConference extends IVideoConference {
|
||||
type: 'direct';
|
||||
}
|
||||
|
||||
export interface IGroupVideoConference extends IVideoConference {
|
||||
type: 'videoconference';
|
||||
anonymousUsers: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface ILivechatVideoConference extends IVideoConference {
|
||||
type: 'livechat';
|
||||
}
|
||||
export declare type VideoConference = IDirectVideoConference | IGroupVideoConference | ILivechatVideoConference;
|
||||
export declare type VideoConferenceInstructions = DirectCallInstructions | ConferenceInstructions | LivechatInstructions;
|
||||
export declare const isDirectVideoConference: (call: VideoConference | undefined | null) => call is IDirectVideoConference;
|
||||
export declare const isGroupVideoConference: (call: VideoConference | undefined | null) => call is IGroupVideoConference;
|
||||
export declare const isLivechatVideoConference: (call: VideoConference | undefined | null) => call is ILivechatVideoConference;
|
||||
declare type GroupVideoConferenceCreateData = Omit<IGroupVideoConference, 'createdBy'> & {
|
||||
createdBy: IUser['_id'];
|
||||
};
|
||||
declare type DirectVideoConferenceCreateData = Omit<IDirectVideoConference, 'createdBy'> & {
|
||||
createdBy: IUser['_id'];
|
||||
};
|
||||
declare type LivechatVideoConferenceCreateData = Omit<ILivechatVideoConference, 'createdBy'> & {
|
||||
createdBy: IUser['_id'];
|
||||
};
|
||||
export declare type VideoConferenceCreateData = AtLeast<
|
||||
|
||||
export type VideoConference = IDirectVideoConference | IGroupVideoConference | ILivechatVideoConference;
|
||||
|
||||
export type VideoConferenceInstructions = DirectCallInstructions | ConferenceInstructions | LivechatInstructions;
|
||||
|
||||
export const isDirectVideoConference = (call: VideoConference | undefined | null): call is IDirectVideoConference =>
|
||||
call?.type === 'direct';
|
||||
|
||||
export const isGroupVideoConference = (call: VideoConference | undefined | null): call is IGroupVideoConference =>
|
||||
call?.type === 'videoconference';
|
||||
|
||||
export const isLivechatVideoConference = (call: VideoConference | undefined | null): call is ILivechatVideoConference =>
|
||||
call?.type === 'livechat';
|
||||
|
||||
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,
|
||||
'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];
|
||||
|
|
|
@ -29,7 +29,6 @@ export * from './ISearch';
|
|||
export * from './TUserStatus';
|
||||
export * from './IProfile';
|
||||
export * from './IReaction';
|
||||
export * from './ERoomType';
|
||||
|
||||
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
|
||||
navigation: StackNavigationProp<T, S>;
|
||||
|
|
|
@ -12,12 +12,6 @@ export type E2eEndpoints = {
|
|||
'e2e.updateGroupKey': {
|
||||
POST: (params: { uid: string; rid: string; key: string }) => {};
|
||||
};
|
||||
'e2e.acceptSuggestedGroupKey': {
|
||||
POST: (params: { rid: string }) => {};
|
||||
};
|
||||
'e2e.rejectSuggestedGroupKey': {
|
||||
POST: (params: { rid: string }) => {};
|
||||
};
|
||||
'e2e.setRoomKeyID': {
|
||||
POST: (params: { rid: string; keyID: string }) => {};
|
||||
};
|
||||
|
|
|
@ -1,45 +1,27 @@
|
|||
import {
|
||||
VideoConfCancelProps,
|
||||
VideoConference,
|
||||
VideoConferenceCapabilities,
|
||||
VideoConferenceInstructions,
|
||||
VideoConfInfoProps,
|
||||
VideoConfJoinProps,
|
||||
VideoConfListProps,
|
||||
VideoConfStartProps
|
||||
} from '../../IVideoConference';
|
||||
import { PaginatedResult } from '../helpers/PaginatedResult';
|
||||
import { VideoConference } from '../../IVideoConference';
|
||||
|
||||
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': {
|
||||
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 'rn-root-view';
|
||||
declare module 'react-native-math-view';
|
||||
declare module '@env' {
|
||||
export const RUNNING_E2E_TESTS: string;
|
||||
}
|
||||
|
|
|
@ -876,20 +876,5 @@
|
|||
"Call": "Call",
|
||||
"Reply_in_direct_message": "Reply in Direct Message",
|
||||
"room_archived": "archived 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"
|
||||
"room_unarchived": "unarchived room"
|
||||
}
|
|
@ -12,7 +12,6 @@
|
|||
"error-could-not-change-email": "Não foi possível mudar e-mail",
|
||||
"error-could-not-change-name": "Não foi possível mudar o nome",
|
||||
"error-could-not-change-username": "Não foi possível alterar o nome de usuário",
|
||||
"error-could-not-change-status": "Não foi possível alterar o status",
|
||||
"error-delete-protected-role": "Não é possível remover um papel protegido",
|
||||
"error-department-not-found": "Departamento não encontrado",
|
||||
"error-direct-message-file-upload-not-allowed": "Compartilhamento de arquivos não está permitido em mensagens diretas",
|
||||
|
@ -20,7 +19,6 @@
|
|||
"error-email-domain-blacklisted": "O domínio de e-mail está na lista negra",
|
||||
"error-email-send-failed": "Erro ao tentar enviar e-mail: {{message}}",
|
||||
"error-save-image": "Erro ao salvar imagem",
|
||||
"error-save-video": "Erro ao salvar vídeo",
|
||||
"error-field-unavailable": "{{field}} já está sendo usado :(",
|
||||
"error-file-too-large": "Arquivo é muito grande",
|
||||
"error-not-permission-to-upload-file": "Você não tem permissão para enviar arquivos",
|
||||
|
@ -90,7 +88,6 @@
|
|||
"Add_Reaction": "Reagir",
|
||||
"Add_Server": "Adicionar servidor",
|
||||
"Add_users": "Adicionar usuário",
|
||||
"Admin_Panel": "Painel de admin",
|
||||
"Agent": "Agente",
|
||||
"Alert": "Alerta",
|
||||
"alert": "alerta",
|
||||
|
@ -99,7 +96,6 @@
|
|||
"All_users_in_the_team_can_write_new_messages": "Todos usuários no canal podem enviar mensagens novas",
|
||||
"A_meaningful_name_for_the_discussion_room": "Um nome significativo para o canal de discussão",
|
||||
"All": "Todos",
|
||||
"All_Messages": "Todas as mensagens",
|
||||
"Allow_Reactions": "Permitir reagir",
|
||||
"Alphabetical": "Alfabético",
|
||||
"and_more": "e mais",
|
||||
|
@ -167,10 +163,7 @@
|
|||
"Copied_to_clipboard": "Copiado para a área de transferência!",
|
||||
"Copy": "Copiar",
|
||||
"Conversation": "Conversação",
|
||||
"Certificate_password": "Senha do certificado",
|
||||
"Clear_cache": "Limpar cache da workspace",
|
||||
"Clear_cache_loading": "Limpando cache.",
|
||||
"Whats_the_password_for_your_certificate": "Qual é a senha para o seu certificado?",
|
||||
"Create_account": "Criar conta",
|
||||
"Create_Channel": "Criar Canal",
|
||||
"Create_Direct_Messages": "Criar Mensagens Diretas",
|
||||
|
@ -178,7 +171,6 @@
|
|||
"Created_snippet": "criou um snippet",
|
||||
"Create_a_new_workspace": "Criar nova área de trabalho",
|
||||
"Create": "Criar",
|
||||
"Custom_Status": "Status personalizado",
|
||||
"Dark": "Escuro",
|
||||
"Dark_level": "Nível escuro",
|
||||
"Default": "Padrão",
|
||||
|
@ -263,7 +255,6 @@
|
|||
"Has_left_the_team": "saiu da equipe",
|
||||
"Hide_System_Messages": "Esconder mensagens do sistema",
|
||||
"Hide_type_messages": "Esconder mensagens de \"{{type}}\"",
|
||||
"How_It_Works": "Como funciona",
|
||||
"Message_HideType_uj": "Utilizador Entrou",
|
||||
"Message_HideType_ul": "Utilizador Saiu",
|
||||
"Message_HideType_ru": "Utilizador Removido",
|
||||
|
@ -277,15 +268,11 @@
|
|||
"Message_HideType_subscription_role_removed": "Papel removido",
|
||||
"Message_HideType_room_archived": "Sala arquivada",
|
||||
"Message_HideType_room_unarchived": "Sala desarquivada",
|
||||
"I_Saved_My_E2E_Password": "Salvei minha senha ponta-a-ponta",
|
||||
"IP": "IP",
|
||||
"In_app": "No app",
|
||||
"In_App_And_Desktop": "In-app e área de trabalho",
|
||||
"In_App_and_Desktop_Alert_info": "Exibe um banner na parte superior da tela quando o aplicativo é aberto e exibe uma notificação na área de trabalho",
|
||||
"Invisible": "Invisível",
|
||||
"Invite": "Convidar",
|
||||
"is_a_valid_RocketChat_instance": "é uma instância Rocket.Chat",
|
||||
"is_not_a_valid_RocketChat_instance": "não é uma instância Rocket.Chat",
|
||||
"is_typing": "está digitando",
|
||||
"Invalid_or_expired_invite_token": "Token de convite inválido ou vencido",
|
||||
"Invalid_server_version": "O servidor que você está conectando não é suportado mais por esta versão do aplicativo: {{currentVersion}}.\n\nEsta versão do aplicativo requer a versão {{minVersion}} do servidor para funcionar corretamente.",
|
||||
|
@ -306,9 +293,7 @@
|
|||
"leave": "sair",
|
||||
"Legal": "Legal",
|
||||
"Light": "Claro",
|
||||
"License": "Licença",
|
||||
"Livechat": "Livechat",
|
||||
"Livechat_edit": "Editar livechat",
|
||||
"Livechat_transfer_return_to_the_queue": "retornou conversa para a fila",
|
||||
"Login": "Entrar",
|
||||
"Login_error": "Suas credenciais foram rejeitadas. Tente novamente por favor!",
|
||||
|
@ -317,7 +302,6 @@
|
|||
"Logout": "Sair",
|
||||
"Max_number_of_uses": "Número máximo de usos",
|
||||
"Max_number_of_users_allowed_is_number": "Número máximo de usuários é {{maxUsers}}",
|
||||
"members": "membros",
|
||||
"Members": "Membros",
|
||||
"Mentioned_Messages": "Mensagens mencionadas",
|
||||
"mentioned": "mencionado",
|
||||
|
@ -326,18 +310,14 @@
|
|||
"Message_actions": "Ações",
|
||||
"Message_pinned": "Fixou uma mensagem",
|
||||
"Message_removed": "mensagem removida",
|
||||
"Message_starred": "Mensagem adicionada aos favoritos",
|
||||
"Message_unstarred": "Mensagem removida dos favoritos",
|
||||
"message": "mensagem",
|
||||
"messages": "mensagens",
|
||||
"Message": "Mensagem",
|
||||
"Messages": "Mensagens",
|
||||
"Message_Reported": "Mensagem reportada",
|
||||
"Microphone_Permission_Message": "Rocket.Chat precisa de acesso ao seu microfone para enviar mensagens de áudio.",
|
||||
"Microphone_Permission": "Acesso ao Microfone",
|
||||
"Mute": "Mudo",
|
||||
"muted": "mudo",
|
||||
"My_servers": "Minhas workspaces",
|
||||
"N_people_reacted": "{{n}} pessoas reagiram",
|
||||
"N_users": "{{n}} usuários",
|
||||
"N_channels": "{{n}} canais",
|
||||
|
@ -346,7 +326,6 @@
|
|||
"New_chat_transfer": "Nova transferência de conversa: {{agent}} retornou conversa para a fila",
|
||||
"New_Message": "Nova Mensagem",
|
||||
"New_Password": "Nova Senha",
|
||||
"New_Server": "Nova workspace",
|
||||
"Next": "Próximo",
|
||||
"No_files": "Não há arquivos",
|
||||
"No_limit": "Sem limite",
|
||||
|
@ -360,8 +339,6 @@
|
|||
"No_Message": "Não há mensagens",
|
||||
"No_messages_yet": "Não há mensagens ainda",
|
||||
"No_Reactions": "Sem reações",
|
||||
"No_Read_Receipts": "Não lida",
|
||||
"Not_logged": "Desconectado",
|
||||
"Not_RC_Server": "Este não é um servidor Rocket.Chat.\n{{contact}}",
|
||||
"Nothing": "Nada",
|
||||
"Nothing_to_save": "Nada para salvar!",
|
||||
|
@ -482,7 +459,6 @@
|
|||
"Search_emoji": "Buscar emoji",
|
||||
"Search_global_users": "Busca por usuários globais",
|
||||
"Search_global_users_description": "Caso ativado, busca por usuários de outras empresas ou servidores.",
|
||||
"Seconds": "{{second}} segundos",
|
||||
"Security_and_privacy": "Segurança e privacidade",
|
||||
"Select_Avatar": "Selecionar Avatar",
|
||||
"Select_Server": "Selecionar Servidor",
|
||||
|
@ -497,20 +473,13 @@
|
|||
"Send_message": "Enviar mensagem",
|
||||
"Send_me_the_code_again": "Envie-me o código novamente",
|
||||
"Send_to": "Enviar para...",
|
||||
"Sending_to": "Envio para",
|
||||
"Sent_an_attachment": "Enviou um anexo",
|
||||
"Server": "Servidor",
|
||||
"Servers": "Workspaces",
|
||||
"Server_version": "Versão da workspace: {{version}}",
|
||||
"Set_username_subtitle": "O usuário é utilizado para permitir que você seja mencionado em mensagens",
|
||||
"Set_custom_status": "Definir status personalizado",
|
||||
"Set_status": "Definir status",
|
||||
"Status_saved_successfully": "Status salvo com sucesso!",
|
||||
"Settings": "Configurações",
|
||||
"Settings_succesfully_changed": "Configurações salvas com sucesso!",
|
||||
"Share": "Compartilhar",
|
||||
"Share_Link": "Share Link",
|
||||
"Share_this_app": "Compartilhar esse app",
|
||||
"Show_more": "Mostrar mais..",
|
||||
"Sign_in_your_server": "Entrar no seu servidor",
|
||||
"Sign_Up": "Registrar",
|
||||
|
@ -527,12 +496,9 @@
|
|||
"Started_call": "Chamada iniciada por {{userBy}}",
|
||||
"Submit": "Enviar",
|
||||
"Table": "Tabela",
|
||||
"Tags": "Tags",
|
||||
"Take_a_photo": "Tirar uma foto",
|
||||
"Take_a_video": "Gravar um vídeo",
|
||||
"Take_it": "Pegue!",
|
||||
"tap_to_change_status": "toque para mudar de status",
|
||||
"Tap_to_view_servers_list": "Toque para visualizar as workspaces",
|
||||
"Terms_of_Service": " Termos de Serviço ",
|
||||
"Theme": "Tema",
|
||||
"The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}",
|
||||
|
@ -577,14 +543,10 @@
|
|||
"User_has_been_removed": "removeu {{userRemoved}}",
|
||||
"User_sent_an_attachment": "{{user}} enviou um anexo",
|
||||
"User_has_been_unmuted": "permitiu que {{userUnmuted}} fale na sala",
|
||||
"Defined_user_as_role": "definiu {{user}} como {{role}}",
|
||||
"Removed_user_as_role": "removeu {{user}} como {{role}}",
|
||||
"Username_is_empty": "Usuário está vazio",
|
||||
"Username": "Usuário",
|
||||
"Username_or_email": "Usuário ou email",
|
||||
"Uses_server_configuration": "Usar configuração do servidor",
|
||||
"Validating": "Validando...",
|
||||
"Registration_Succeeded": "Registrado com sucesso!",
|
||||
"Verify": "Verificar",
|
||||
"Verify_email_title": "Registrado com sucesso!",
|
||||
"Verify_email_desc": "Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.",
|
||||
|
@ -613,9 +575,7 @@
|
|||
"You_were_removed_from_channel": "Você foi removido de {{channel}}",
|
||||
"you": "você",
|
||||
"You": "Você",
|
||||
"Logged_out_by_server": "Você foi desconectado pela workspace. Por favor entre novamente.",
|
||||
"Token_expired": "Sua sessão expirou. Por favor entre novamente.",
|
||||
"You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Você precisa acessar pelo menos uma workspace Rocket.Chat para compartilhar.",
|
||||
"You_need_to_verifiy_your_email_address_to_get_notications": "Você precisa confirmar seu endereço de e-mail para obter notificações",
|
||||
"Your_certificate": "Seu certificado",
|
||||
"Your_invite_link_will_expire_after__usesLeft__uses": "Seu link de convite irá vencer depois de {{usesLeft}} usos.",
|
||||
|
@ -623,8 +583,6 @@
|
|||
"Your_invite_link_will_expire_on__date__": "Seu link de convite irá vencer em {{date}}.",
|
||||
"Your_invite_link_will_never_expire": "Seu link de convite nunca irá vencer.",
|
||||
"Your_workspace": "Sua workspace",
|
||||
"Your_password_is": "Sua senha é",
|
||||
"Version_no": "Versão: {{version}}",
|
||||
"You_will_not_be_able_to_recover_this_message": "Você não será capaz de recuperar essa mensagem!",
|
||||
"You_will_unset_a_certificate_for_this_server": "Você cancelará a configuração de um certificado para este servidor",
|
||||
"Change_Language": "Alterar idioma",
|
||||
|
@ -725,10 +683,6 @@
|
|||
"Teams": "Times",
|
||||
"No_team_channels_found": "Nenhum canal encontrado",
|
||||
"Team_not_found": "Time não encontrado",
|
||||
"Create_Team": "Criar time",
|
||||
"Team_Name": "Nome do time",
|
||||
"creating_team": "criando time",
|
||||
"team-name-already-exists": "Um time com esse nome já existe",
|
||||
"Add_Channel_to_Team": "Adicionar Canal ao Time",
|
||||
"Left_The_Team_Successfully": "Saiu do time com sucesso",
|
||||
"Create_New": "Criar",
|
||||
|
@ -875,7 +829,5 @@
|
|||
"Call": "Ligar",
|
||||
"Reply_in_direct_message": "Responder por mensagem direta",
|
||||
"room_archived": "{{username}} arquivou 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."
|
||||
"room_unarchived": "{{username}} desarquivou a sala"
|
||||
}
|
|
@ -3,8 +3,7 @@ export const STATUS_COLORS: any = {
|
|||
busy: '#f5455c',
|
||||
away: '#ffd21f',
|
||||
offline: '#cbced1',
|
||||
loading: '#9ea2a8',
|
||||
disabled: '#F38C39'
|
||||
loading: '#9ea2a8'
|
||||
};
|
||||
|
||||
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 = {
|
||||
Accounts_AllowEmailChange: {
|
||||
type: 'valueAsBoolean'
|
||||
|
@ -230,8 +229,5 @@ export const defaultSettings = {
|
|||
},
|
||||
Number_of_users_autocomplete_suggestions: {
|
||||
type: 'valueAsNumber'
|
||||
},
|
||||
Presence_broadcast_disabled: {
|
||||
type: 'valueAsBoolean'
|
||||
}
|
||||
} as const;
|
||||
|
|
|
@ -8,6 +8,5 @@ export * from './localAuthentication';
|
|||
export * from './localPath';
|
||||
export * from './messagesStatus';
|
||||
export * from './messageTypeLoad';
|
||||
export * from './notifications';
|
||||
export * from './defaultSettings';
|
||||
export * from './tablet';
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export const NOTIFICATION_PRESENCE_CAP = 'NOTIFICATION_PRESENCE_CAP';
|
|
@ -85,4 +85,47 @@ export default class Message extends Model {
|
|||
@json('md', sanitizer) md;
|
||||
|
||||
@field('comment') comment;
|
||||
|
||||
asPlain() {
|
||||
return {
|
||||
id: this.id,
|
||||
rid: this.subscription.id,
|
||||
msg: this.msg,
|
||||
t: this.t,
|
||||
ts: this.ts,
|
||||
u: this.u,
|
||||
alias: this.alias,
|
||||
parseUrls: this.parseUrls,
|
||||
groupable: this.groupable,
|
||||
avatar: this.avatar,
|
||||
emoji: this.emoji,
|
||||
attachments: this.attachments,
|
||||
urls: this.urls,
|
||||
_updatedAt: this._updatedAt,
|
||||
status: this.status,
|
||||
pinned: this.pinned,
|
||||
starred: this.starred,
|
||||
editedBy: this.editedBy,
|
||||
reactions: this.reactions,
|
||||
role: this.role,
|
||||
drid: this.drid,
|
||||
dcount: this.dcount,
|
||||
dlm: this.dlm,
|
||||
tmid: this.tmid,
|
||||
tcount: this.tcount,
|
||||
tlm: this.tlm,
|
||||
replies: this.replies,
|
||||
mentions: this.mentions,
|
||||
channels: this.channels,
|
||||
unread: this.unread,
|
||||
autoTranslate: this.autoTranslate,
|
||||
translations: this.translations,
|
||||
tmsg: this.tmsg,
|
||||
blocks: this.blocks,
|
||||
e2e: this.e2e,
|
||||
tshow: this.tshow,
|
||||
md: this.md,
|
||||
comment: this.comment
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,8 +123,6 @@ export default class Subscription extends Model {
|
|||
|
||||
@field('e2e_key') E2EKey;
|
||||
|
||||
@field('e2e_suggested_key') E2ESuggestedKey;
|
||||
|
||||
@field('encrypted') encrypted;
|
||||
|
||||
@field('e2e_key_id') e2eKeyId;
|
||||
|
@ -138,4 +136,68 @@ export default class Subscription extends Model {
|
|||
@field('on_hold') onHold;
|
||||
|
||||
@json('source', sanitizer) source;
|
||||
|
||||
// TODO: if this is proven to be the best way to do it, we should use TS to map through the properties
|
||||
asPlain() {
|
||||
return {
|
||||
_id: this._id,
|
||||
f: this.f,
|
||||
t: this.t,
|
||||
ts: this.ts,
|
||||
ls: this.ls,
|
||||
name: this.name,
|
||||
fname: this.fname,
|
||||
rid: this.rid,
|
||||
open: this.open,
|
||||
alert: this.alert,
|
||||
unread: this.unread,
|
||||
userMentions: this.userMentions,
|
||||
groupMentions: this.groupMentions,
|
||||
roomUpdatedAt: this.roomUpdatedAt,
|
||||
ro: this.ro,
|
||||
lastOpen: this.lastOpen,
|
||||
description: this.description,
|
||||
announcement: this.announcement,
|
||||
bannerClosed: this.bannerClosed,
|
||||
topic: this.topic,
|
||||
blocked: this.blocked,
|
||||
blocker: this.blocker,
|
||||
reactWhenReadOnly: this.reactWhenReadOnly,
|
||||
archived: this.archived,
|
||||
joinCodeRequired: this.joinCodeRequired,
|
||||
notifications: this.notifications,
|
||||
broadcast: this.broadcast,
|
||||
prid: this.prid,
|
||||
draftMessage: this.draftMessage,
|
||||
lastThreadSync: this.lastThreadSync,
|
||||
jitsiTimeout: this.jitsiTimeout,
|
||||
autoTranslate: this.autoTranslate,
|
||||
autoTranslateLanguage: this.autoTranslateLanguage,
|
||||
hideUnreadStatus: this.hideUnreadStatus,
|
||||
hideMentionStatus: this.hideMentionStatus,
|
||||
departmentId: this.departmentId,
|
||||
E2EKey: this.E2EKey,
|
||||
encrypted: this.encrypted,
|
||||
e2eKeyId: this.e2eKeyId,
|
||||
avatarETag: this.avatarETag,
|
||||
teamId: this.teamId,
|
||||
teamMain: this.teamMain,
|
||||
onHold: this.onHold,
|
||||
roles: this.roles,
|
||||
tunread: this.tunread,
|
||||
tunreadUser: this.tunreadUser,
|
||||
tunreadGroup: this.tunreadGroup,
|
||||
muted: this.muted,
|
||||
ignored: this.ignored,
|
||||
lastMessage: this.lastMessage,
|
||||
sysMes: this.sysMes,
|
||||
uids: this.uids,
|
||||
usernames: this.usernames,
|
||||
visitor: this.visitor,
|
||||
servedBy: this.servedBy,
|
||||
livechatData: this.livechatData,
|
||||
tags: this.tags,
|
||||
source: this.source
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,4 +77,42 @@ export default class Thread extends Model {
|
|||
@field('e2e') e2e;
|
||||
|
||||
@field('draft_message') draftMessage;
|
||||
|
||||
asPlain() {
|
||||
return {
|
||||
id: this.id,
|
||||
msg: this.msg,
|
||||
t: this.t,
|
||||
ts: this.ts,
|
||||
u: this.u,
|
||||
alias: this.alias,
|
||||
parseUrls: this.parseUrls,
|
||||
groupable: this.groupable,
|
||||
avatar: this.avatar,
|
||||
emoji: this.emoji,
|
||||
attachments: this.attachments,
|
||||
urls: this.urls,
|
||||
_updatedAt: this._updatedAt,
|
||||
status: this.status,
|
||||
pinned: this.pinned,
|
||||
starred: this.starred,
|
||||
editedBy: this.editedBy,
|
||||
reactions: this.reactions,
|
||||
role: this.role,
|
||||
drid: this.drid,
|
||||
dcount: this.dcount,
|
||||
dlm: this.dlm,
|
||||
tmid: this.tmid,
|
||||
tcount: this.tcount,
|
||||
tlm: this.tlm,
|
||||
replies: this.replies,
|
||||
mentions: this.mentions,
|
||||
channels: this.channels,
|
||||
unread: this.unread,
|
||||
autoTranslate: this.autoTranslate,
|
||||
translations: this.translations,
|
||||
e2e: this.e2e,
|
||||
draftMessage: this.draftMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,4 +77,42 @@ export default class ThreadMessage extends Model {
|
|||
@field('draft_message') draftMessage;
|
||||
|
||||
@field('e2e') e2e;
|
||||
|
||||
asPlain() {
|
||||
return {
|
||||
id: this.id,
|
||||
msg: this.msg,
|
||||
t: this.t,
|
||||
ts: this.ts,
|
||||
u: this.u,
|
||||
rid: this.rid,
|
||||
alias: this.alias,
|
||||
parseUrls: this.parseUrls,
|
||||
groupable: this.groupable,
|
||||
avatar: this.avatar,
|
||||
emoji: this.emoji,
|
||||
attachments: this.attachments,
|
||||
urls: this.urls,
|
||||
_updatedAt: this._updatedAt,
|
||||
status: this.status,
|
||||
pinned: this.pinned,
|
||||
starred: this.starred,
|
||||
editedBy: this.editedBy,
|
||||
reactions: this.reactions,
|
||||
role: this.role,
|
||||
drid: this.drid,
|
||||
dcount: this.dcount,
|
||||
dlm: this.dlm,
|
||||
tcount: this.tcount,
|
||||
tlm: this.tlm,
|
||||
replies: this.replies,
|
||||
mentions: this.mentions,
|
||||
channels: this.channels,
|
||||
unread: this.unread,
|
||||
autoTranslate: this.autoTranslate,
|
||||
translations: this.translations,
|
||||
draftMessage: this.draftMessage,
|
||||
e2e: this.e2e
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@ export default class Upload extends Model {
|
|||
|
||||
@field('name') name;
|
||||
|
||||
@field('tmid') tmid;
|
||||
|
||||
@field('description') description;
|
||||
|
||||
@field('size') size;
|
||||
|
|
|
@ -239,24 +239,6 @@ export default schemaMigrations({
|
|||
columns: [{ name: 'hide_mention_status', type: 'boolean', isOptional: true }]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 19,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'uploads',
|
||||
columns: [{ name: 'tmid', type: 'string', isOptional: true }]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 20,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'subscriptions',
|
||||
columns: [{ name: 'e2e_suggested_key', type: 'string', isOptional: true }]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 20,
|
||||
version: 18,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'subscriptions',
|
||||
|
@ -55,7 +55,6 @@ export default appSchema({
|
|||
{ name: 'livechat_data', type: 'string', isOptional: true },
|
||||
{ name: 'tags', type: 'string', isOptional: true },
|
||||
{ name: 'e2e_key', type: 'string', isOptional: true },
|
||||
{ name: 'e2e_suggested_key', type: 'string', isOptional: true },
|
||||
{ name: 'encrypted', type: 'boolean', isOptional: true },
|
||||
{ name: 'e2e_key_id', type: 'string', isOptional: true },
|
||||
{ name: 'avatar_etag', type: 'string', isOptional: true },
|
||||
|
@ -223,7 +222,6 @@ export default appSchema({
|
|||
{ name: 'path', type: 'string', isOptional: true },
|
||||
{ name: 'rid', type: 'string', isIndexed: true },
|
||||
{ name: 'name', type: 'string', isOptional: true },
|
||||
{ name: 'tmid', type: 'string', isOptional: true },
|
||||
{ name: 'description', type: 'string', isOptional: true },
|
||||
{ name: 'size', type: 'number' },
|
||||
{ name: 'type', type: 'string', isOptional: true },
|
||||
|
|
|
@ -34,7 +34,6 @@ class Encryption {
|
|||
handshake: Function;
|
||||
decrypt: Function;
|
||||
encrypt: Function;
|
||||
importRoomKey: Function;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -98,10 +97,6 @@ class Encryption {
|
|||
});
|
||||
};
|
||||
|
||||
stopRoom = (rid: string) => {
|
||||
delete this.roomInstances[rid];
|
||||
};
|
||||
|
||||
// When a new participant join and request a new room encryption key
|
||||
provideRoomKeyToUser = async (keyId: string, rid: string) => {
|
||||
// If the client is not ready
|
||||
|
@ -225,19 +220,6 @@ class Encryption {
|
|||
return roomE2E;
|
||||
};
|
||||
|
||||
evaluateSuggestedKey = async (rid: string, E2ESuggestedKey: string) => {
|
||||
try {
|
||||
if (this.privateKey) {
|
||||
const roomE2E = await this.getRoomInstance(rid);
|
||||
await roomE2E.importRoomKey(E2ESuggestedKey, this.privateKey);
|
||||
delete this.roomInstances[rid];
|
||||
await Services.e2eAcceptSuggestedGroupKey(rid);
|
||||
}
|
||||
} catch (e) {
|
||||
await Services.e2eRejectSuggestedGroupKey(rid);
|
||||
}
|
||||
};
|
||||
|
||||
// Logic to decrypt all pending messages/threads/threadMessages
|
||||
// after initialize the encryption client
|
||||
decryptPendingMessages = async (roomId?: string) => {
|
||||
|
|
|
@ -74,10 +74,7 @@ export default class EncryptionRoom {
|
|||
if (E2EKey && Encryption.privateKey) {
|
||||
// We're establishing a new room encryption client
|
||||
this.establishing = true;
|
||||
const { keyID, roomKey, sessionKeyExportedString } = await this.importRoomKey(E2EKey, Encryption.privateKey);
|
||||
this.keyID = keyID;
|
||||
this.roomKey = roomKey;
|
||||
this.sessionKeyExportedString = sessionKeyExportedString;
|
||||
await this.importRoomKey(E2EKey, Encryption.privateKey);
|
||||
this.readyPromise.resolve();
|
||||
return;
|
||||
}
|
||||
|
@ -99,33 +96,20 @@ export default class EncryptionRoom {
|
|||
};
|
||||
|
||||
// Import roomKey as an AES Decrypt key
|
||||
importRoomKey = async (
|
||||
E2EKey: string,
|
||||
privateKey: string
|
||||
): Promise<{ sessionKeyExportedString: string | ByteBuffer; roomKey: ArrayBuffer; keyID: string }> => {
|
||||
try {
|
||||
const roomE2EKey = E2EKey.slice(12);
|
||||
importRoomKey = async (E2EKey: string, privateKey: string) => {
|
||||
const roomE2EKey = E2EKey.slice(12);
|
||||
|
||||
const decryptedKey = await SimpleCrypto.RSA.decrypt(roomE2EKey, privateKey);
|
||||
const sessionKeyExportedString = toString(decryptedKey);
|
||||
const decryptedKey = await SimpleCrypto.RSA.decrypt(roomE2EKey, privateKey);
|
||||
this.sessionKeyExportedString = toString(decryptedKey);
|
||||
|
||||
const keyID = Base64.encode(sessionKeyExportedString as string).slice(0, 12);
|
||||
this.keyID = Base64.encode(this.sessionKeyExportedString as string).slice(0, 12);
|
||||
|
||||
// Extract K from Web Crypto Secret Key
|
||||
// K is a base64URL encoded array of bytes
|
||||
// Web Crypto API uses this as a private key to decrypt/encrypt things
|
||||
// Reference: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/5.1/com/nimbusds/jose/jwk/OctetSequenceKey.html
|
||||
const { k } = EJSON.parse(sessionKeyExportedString as string);
|
||||
const roomKey = b64ToBuffer(k);
|
||||
|
||||
return {
|
||||
sessionKeyExportedString,
|
||||
roomKey,
|
||||
keyID
|
||||
};
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
// Extract K from Web Crypto Secret Key
|
||||
// K is a base64URL encoded array of bytes
|
||||
// Web Crypto API uses this as a private key to decrypt/encrypt things
|
||||
// Reference: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/5.1/com/nimbusds/jose/jwk/OctetSequenceKey.html
|
||||
const { k } = EJSON.parse(this.sessionKeyExportedString as string);
|
||||
this.roomKey = b64ToBuffer(k);
|
||||
};
|
||||
|
||||
// Create a key to a room
|
||||
|
|
|
@ -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 };
|
||||
};
|
|
@ -4,17 +4,12 @@ import { sanitizeLikeString } from '../database/utils';
|
|||
import { store } from '../store/auxStore';
|
||||
import log from './helpers/log';
|
||||
|
||||
const DEFAULT_EXTENSION = 'mp3';
|
||||
|
||||
const sanitizeString = (value: string) => sanitizeLikeString(value.substring(value.lastIndexOf('/') + 1));
|
||||
|
||||
const getExtension = (value: string) => {
|
||||
let extension = DEFAULT_EXTENSION;
|
||||
const filename = value.split('/').pop();
|
||||
if (filename?.includes('.')) {
|
||||
extension = value.substring(value.lastIndexOf('.') + 1);
|
||||
}
|
||||
return extension;
|
||||
const parseFilename = (value: string) => {
|
||||
const extension = value.substring(value.lastIndexOf('.') + 1);
|
||||
const filename = sanitizeString(value.substring(value.lastIndexOf('/') + 1).split('.')[0]);
|
||||
return `${filename}.${extension}`;
|
||||
};
|
||||
|
||||
const ensureDirAsync = async (dir: string, intermediates = true): Promise<void> => {
|
||||
|
@ -32,7 +27,7 @@ export const downloadAudioFile = async (url: string, fileUrl: string, messageId:
|
|||
const serverUrl = store.getState().server.server;
|
||||
const serverUrlParsed = sanitizeString(serverUrl);
|
||||
const folderPath = `${FileSystem.documentDirectory}audios/${serverUrlParsed}`;
|
||||
const filename = `${messageId}.${getExtension(fileUrl)}`;
|
||||
const filename = `${messageId}_${parseFilename(fileUrl)}`;
|
||||
const filePath = `${folderPath}/${filename}`;
|
||||
await ensureDirAsync(folderPath);
|
||||
const file = await FileSystem.getInfoAsync(filePath);
|
||||
|
@ -52,7 +47,7 @@ export const deleteAllAudioFiles = async (serverUrl: string): Promise<void> => {
|
|||
try {
|
||||
const serverUrlParsed = sanitizeString(serverUrl);
|
||||
const path = `${FileSystem.documentDirectory}audios/${serverUrlParsed}`;
|
||||
await FileSystem.deleteAsync(path, { idempotent: true });
|
||||
await FileSystem.deleteAsync(path);
|
||||
} catch (error) {
|
||||
log(error);
|
||||
}
|
||||
|
|
|
@ -46,8 +46,8 @@ export function callJitsiWithoutServer(path: string): void {
|
|||
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false });
|
||||
}
|
||||
|
||||
export async function callJitsi({ room, cam = false }: { room: ISubscription; cam?: boolean }): Promise<void> {
|
||||
logEvent(cam ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||
export async function callJitsi(room: ISubscription, onlyAudio = false): Promise<void> {
|
||||
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||
const url = await jitsiURL({ room });
|
||||
Navigation.navigate('JitsiMeetView', { url, onlyAudio: cam, rid: room?.rid });
|
||||
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid: room?.rid });
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import log from './helpers/log';
|
||||
import { TMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { IMessage, TSubscriptionModel } from '../../definitions';
|
||||
import { store } from '../store/auxStore';
|
||||
import { isGroupChat } from './helpers';
|
||||
import { getRoom } from './getRoom';
|
||||
|
||||
type TRoomType = 'p' | 'c' | 'd';
|
||||
|
||||
export async function getPermalinkMessage(message: TMessageModel): Promise<string | null> {
|
||||
if (!message.subscription) return null;
|
||||
export async function getPermalinkMessage(message: IMessage): Promise<string | null> {
|
||||
let room: TSubscriptionModel;
|
||||
try {
|
||||
room = await getRoom(message.subscription.id);
|
||||
room = await getRoom(message.rid);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
return null;
|
||||
|
|
|
@ -11,7 +11,6 @@ import database from '../database';
|
|||
import sdk from '../services/sdk';
|
||||
import protectedFunction from './helpers/protectedFunction';
|
||||
import { parseSettings, _prepareSettings } from './parseSettings';
|
||||
import { setPresenceCap } from './getUsersPresence';
|
||||
|
||||
const serverInfoKeys = [
|
||||
'Site_Name',
|
||||
|
@ -158,11 +157,8 @@ export async function getSettings(): Promise<void> {
|
|||
const data: IData[] = result.settings || [];
|
||||
const filteredSettings: IPreparedSettings[] = _prepareSettings(data);
|
||||
const filteredSettingsIds = filteredSettings.map(s => s._id);
|
||||
const parsedSettings = parseSettings(filteredSettings);
|
||||
|
||||
reduxStore.dispatch(addSettings(parsedSettings));
|
||||
|
||||
setPresenceCap(parsedSettings.Presence_broadcast_disabled);
|
||||
reduxStore.dispatch(addSettings(parseSettings(filteredSettings)));
|
||||
|
||||
// filter server info
|
||||
const serverInfo = filteredSettings.filter(i1 => serverInfoKeys.includes(i1._id));
|
||||
|
|
|
@ -9,9 +9,6 @@ import database from '../database';
|
|||
import { IUser } from '../../definitions';
|
||||
import sdk from '../services/sdk';
|
||||
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 } = {
|
||||
activeUsersSubTimeout: false
|
||||
|
@ -127,16 +124,3 @@ export function getUserPresence(uid: string) {
|
|||
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));
|
||||
}
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue