Compare commits
154 Commits
improve.au
...
develop
Author | SHA1 | Date |
---|---|---|
Gleidson Daniel Silva | 4dfc9c70f3 | |
Piyush Gupta | 513e8ee636 | |
Mister-H | aa57237004 | |
Diego Mello | 58391964cb | |
Diego Mello | 412f62eb2a | |
Reinaldo Neto | 4526b7f871 | |
Diego Mello | 27efa89dac | |
Gleidson Daniel Silva | 3fbb7b5720 | |
Reinaldo Neto | 4d5ff8aba1 | |
Reinaldo Neto | 4336f0db40 | |
Gleidson Daniel Silva | 4686d4f6f8 | |
Gleidson Daniel Silva | 58f28fb488 | |
lingohub[bot] | 6bb4adaecd | |
Diego Mello | 197616b94c | |
Diego Mello | edd0333be1 | |
Diego Mello | 8c47187f70 | |
Gleidson Daniel Silva | c4a2ce20c6 | |
Reinaldo Neto | 68f6eb40de | |
Diego Mello | 59c76d3956 | |
Reinaldo Neto | 9396b08ead | |
Reinaldo Neto | a927746d7f | |
Reinaldo Neto | 21e4818af7 | |
Diego Mello | 4de7c83e80 | |
Gleidson Daniel Silva | 18cc16beb7 | |
Reinaldo Neto | 3cd1a5f0a6 | |
lingohub[bot] | 1abff18e75 | |
Diego Mello | 26c8931563 | |
Gleidson Daniel Silva | 192a6b055a | |
Diego Mello | 7363d9d742 | |
Umang Patel | 21f64b08e5 | |
Gleidson Daniel Silva | 38511d2f2d | |
Reinaldo Neto | 593d12b129 | |
Diego Mello | ad3bfa830c | |
Diego Mello | af70849b5e | |
Diego Mello | 91831c8f33 | |
Diego Mello | 447a2b9344 | |
Gleidson Daniel Silva | fbb05fa0d3 | |
Gleidson Daniel Silva | 41b54d6d87 | |
Diego Mello | 5387d31a68 | |
Diego Mello | a1580811ed | |
Reinaldo Neto | 3a1b06b86c | |
Reinaldo Neto | 9525ef25d8 | |
Gleidson Daniel Silva | d208373b3a | |
Reinaldo Neto | 24ad69346a | |
Reinaldo Neto | 8102bef1d0 | |
Reinaldo Neto | b6b5e7294f | |
Diego Mello | 73557038a2 | |
Diego Mello | 4174aef4f8 | |
dependabot[bot] | 67f41a712c | |
dependabot[bot] | c13e0af110 | |
dependabot[bot] | d10a77ba3d | |
Reinaldo Neto | 68066d487f | |
Reinaldo Neto | 0b5dab3ddf | |
Reinaldo Neto | 23f716c280 | |
Diego Mello | 4b1f1d9074 | |
Diego Mello | 09cdb3e7a4 | |
lingohub[bot] | 46c18fb20d | |
Diego Mello | 0290d8e0a9 | |
Gleidson Daniel Silva | a2722f07b0 | |
Reinaldo Neto | 00661174a3 | |
Reinaldo Neto | cbd9b27cf7 | |
Diego Mello | 87a3f85feb | |
Gleidson Daniel Silva | a8b3a3b7de | |
Reinaldo Neto | 057ca8afac | |
Gleidson Daniel Silva | 92ae597b76 | |
Reinaldo Neto | 7def88a7e2 | |
Reinaldo Neto | aa26f7251e | |
Gleidson Daniel Silva | 21ee25e818 | |
Gleidson Daniel Silva | 362df65bbe | |
Diego Mello | 61dc320a68 | |
Diego Mello | 62c2d876eb | |
Diego Mello | 44f062f9b9 | |
GitStart | 438b69b371 | |
Diego Mello | 988c6931ec | |
Reinaldo Neto | b0ea5e31f8 | |
dependabot[bot] | 82c922fa4d | |
dependabot[bot] | 9b48ce0264 | |
dependabot[bot] | 994892abb2 | |
Reinaldo Neto | 251cc9843b | |
Diego Mello | 316bc80876 | |
Diego Mello | 94f0981b49 | |
Diego Mello | 05a04f7f29 | |
Reinaldo Neto | cd9924361e | |
Reinaldo Neto | 440e745901 | |
Reinaldo Neto | 612405cfae | |
Diego Mello | 6b6bb1df08 | |
Reinaldo Neto | 640d83c9df | |
Reinaldo Neto | a5e538c13f | |
Duda Nogueira | 0ad5545a4c | |
lingohub[bot] | faf79bb59e | |
Gleidson Daniel Silva | 3a9a98bdfe | |
Diego Mello | 4f960e9fb4 | |
Diego Mello | 80d53d3876 | |
Reinaldo Neto | 8940883dac | |
Reinaldo Neto | 711cac5876 | |
Reinaldo Neto | 816f43b828 | |
Reinaldo Neto | 57620ff40d | |
Reinaldo Neto | 2b53898066 | |
Diego Mello | 1aef030d75 | |
Danish Ahmed Mirza | 1486204546 | |
Reinaldo Neto | ec03f49b2d | |
Gleidson Daniel Silva | 16e11c7304 | |
Reinaldo Neto | 761aef2c04 | |
Gleidson Daniel Silva | 525f84885a | |
Luciano Marcos Pierdona Junior | 6dd9578413 | |
Diego Mello | ef753086a9 | |
Reinaldo Neto | 531f3d0147 | |
Reinaldo Neto | 300e5fc793 | |
Reinaldo Neto | 63e2a3a85c | |
Diego Mello | 5828a04aeb | |
Diego Mello | 5344f2f7c1 | |
Gleidson Daniel Silva | cda998942b | |
Gleidson Daniel Silva | 2d68faff3f | |
Gleidson Daniel Silva | fe04faac64 | |
Danish Ahmed Mirza | 2f03ca52c5 | |
Reinaldo Neto | 43ebae9b19 | |
Diego Mello | f213151db0 | |
Diego Mello | 6df0149210 | |
Reinaldo Neto | e48788c828 | |
Reinaldo Neto | d2d1d787f2 | |
Diego Mello | e883795c5f | |
Reinaldo Neto | cbf9ae3d37 | |
Reinaldo Neto | ee1b09d5ec | |
Gleidson Daniel Silva | d1e2c3fe6d | |
Reinaldo Neto | 5771dbd066 | |
Reinaldo Neto | e10de8b209 | |
Reinaldo Neto | 9d69c10bbd | |
Reinaldo Neto | b3a286329c | |
Reinaldo Neto | 214e1cc009 | |
Diego Mello | dec5c4470c | |
Diego Mello | 18f359a8ef | |
Reinaldo Neto | 05c1798a58 | |
Gleidson Daniel Silva | e146cbae9f | |
Reinaldo Neto | aefb60b5d2 | |
Diego Mello | 92a63e6a39 | |
Diego Mello | ac01acac20 | |
Diego Mello | f0a1266e21 | |
Diego Mello | cc28acd6ed | |
Reinaldo Neto | bb880f01ad | |
Gleidson Daniel Silva | 989ced89bc | |
Gleidson Daniel Silva | 07f0618ab4 | |
Gleidson Daniel Silva | 6484edddb6 | |
Gleidson Daniel Silva | cf7b9a09f4 | |
Reinaldo Neto | 1bc3f542f2 | |
Gleidson Daniel Silva | f9f13e7fe3 | |
Reinaldo Neto | c1a4ddc001 | |
Reinaldo Neto | c9ce52958c | |
Reinaldo Neto | eabb9f157c | |
Gleidson Daniel Silva | e3a99a93e4 | |
Gleidson Daniel Silva | 8d26230e4f | |
Reinaldo Neto | 991d7156a9 | |
Reinaldo Neto | 9cbffff248 | |
Gleidson Daniel Silva | cbc6892084 | |
Reinaldo Neto | ccbc84f9a8 |
|
@ -1,9 +1,12 @@
|
||||||
defaults: &defaults
|
defaults: &defaults
|
||||||
working_directory: ~/repo
|
working_directory: ~/repo
|
||||||
|
|
||||||
|
orbs:
|
||||||
|
android: circleci/android@2.1.2
|
||||||
|
|
||||||
macos: &macos
|
macos: &macos
|
||||||
macos:
|
macos:
|
||||||
xcode: "13.3.0"
|
xcode: "14.2.0"
|
||||||
resource_class: large
|
resource_class: large
|
||||||
|
|
||||||
bash-env: &bash-env
|
bash-env: &bash-env
|
||||||
|
@ -51,14 +54,14 @@ save-gems-cache: &save-gems-cache
|
||||||
update-fastlane-ios: &update-fastlane-ios
|
update-fastlane-ios: &update-fastlane-ios
|
||||||
name: Update Fastlane
|
name: Update Fastlane
|
||||||
command: |
|
command: |
|
||||||
echo "ruby-2.6.4" > ~/.ruby-version
|
echo "ruby-2.7.7" > ~/.ruby-version
|
||||||
bundle install
|
bundle install
|
||||||
working_directory: ios
|
working_directory: ios
|
||||||
|
|
||||||
update-fastlane-android: &update-fastlane-android
|
update-fastlane-android: &update-fastlane-android
|
||||||
name: Update Fastlane
|
name: Update Fastlane
|
||||||
command: |
|
command: |
|
||||||
echo "ruby-2.6.4" > ~/.ruby-version
|
echo "ruby-2.7.7" > ~/.ruby-version
|
||||||
bundle install
|
bundle install
|
||||||
working_directory: android
|
working_directory: android
|
||||||
|
|
||||||
|
@ -118,26 +121,26 @@ commands:
|
||||||
if [[ $CIRCLE_JOB == "android-build-official" ]]; then
|
if [[ $CIRCLE_JOB == "android-build-official" ]]; then
|
||||||
echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties
|
echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties
|
||||||
echo -e "BugsnagAPIKey=$BUGSNAG_KEY_OFFICIAL" >> ./gradle.properties
|
echo -e "BugsnagAPIKey=$BUGSNAG_KEY_OFFICIAL" >> ./gradle.properties
|
||||||
echo $CHAT_ROCKET_ANDROID_STORE_FILE_BASE64_JKS | base64 --decode > ./app/$KEYSTORE_OFFICIAL
|
echo $KEYSTORE_OFFICIAL_BASE64 | base64 --decode > ./app/$KEYSTORE_OFFICIAL
|
||||||
echo -e "KEYSTORE=$KEYSTORE_OFFICIAL" >> ./gradle.properties
|
echo -e "KEYSTORE=$KEYSTORE_OFFICIAL" >> ./gradle.properties
|
||||||
echo -e "KEYSTORE_PASSWORD=$CHAT_ROCKET_ANDROID_STORE_PASSWORD" >> ./gradle.properties
|
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_OFFICIAL_PASSWORD" >> ./gradle.properties
|
||||||
echo -e "KEY_ALIAS=$CHAT_ROCKET_ANDROID_KEY_ALIAS" >> ./gradle.properties
|
echo -e "KEY_ALIAS=$KEYSTORE_OFFICIAL_ALIAS" >> ./gradle.properties
|
||||||
echo -e "KEY_PASSWORD=$CHAT_ROCKET_ANDROID_KEY_PASSWORD" >> ./gradle.properties
|
echo -e "KEY_PASSWORD=$KEYSTORE_OFFICIAL_PASSWORD" >> ./gradle.properties
|
||||||
else
|
else
|
||||||
echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
|
echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
|
||||||
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
|
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
|
||||||
echo $KEYSTORE_BASE64 | base64 --decode > ./app/$KEYSTORE
|
echo $KEYSTORE_EXPERIMENTAL_BASE64 | base64 --decode > ./app/$KEYSTORE_EXPERIMENTAL
|
||||||
echo -e "KEYSTORE=$KEYSTORE" >> ./gradle.properties
|
echo -e "KEYSTORE=$KEYSTORE_EXPERIMENTAL" >> ./gradle.properties
|
||||||
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
|
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
|
||||||
echo -e "KEY_ALIAS=$KEY_ALIAS" >> ./gradle.properties
|
echo -e "KEY_ALIAS=$KEYSTORE_EXPERIMENTAL_ALIAS" >> ./gradle.properties
|
||||||
echo -e "KEY_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
|
echo -e "KEY_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
|
||||||
fi
|
fi
|
||||||
working_directory: android
|
working_directory: android
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Set Google Services
|
name: Set Google Services
|
||||||
command: |
|
command: |
|
||||||
if [[ $KEYSTORE ]]; then
|
if [[ $GOOGLE_SERVICES_ANDROID ]]; then
|
||||||
echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json
|
echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json
|
||||||
fi
|
fi
|
||||||
working_directory: android/app
|
working_directory: android/app
|
||||||
|
@ -151,7 +154,7 @@ commands:
|
||||||
if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
|
if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
|
||||||
./gradlew bundleExperimentalPlayRelease
|
./gradlew bundleExperimentalPlayRelease
|
||||||
fi
|
fi
|
||||||
if [[ ! $KEYSTORE ]]; then
|
if [[ ! $GOOGLE_SERVICES_ANDROID ]]; then
|
||||||
./gradlew assembleExperimentalPlayDebug
|
./gradlew assembleExperimentalPlayDebug
|
||||||
fi
|
fi
|
||||||
working_directory: android
|
working_directory: android
|
||||||
|
@ -200,8 +203,12 @@ commands:
|
||||||
- run:
|
- run:
|
||||||
name: Set Google Services
|
name: Set Google Services
|
||||||
command: |
|
command: |
|
||||||
if [[ $KEYSTORE ]]; then
|
if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
|
||||||
|
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
|
||||||
echo $GOOGLE_SERVICES_IOS | base64 --decode > GoogleService-Info.plist
|
echo $GOOGLE_SERVICES_IOS | base64 --decode > GoogleService-Info.plist
|
||||||
|
else
|
||||||
|
echo $GOOGLE_SERVICES_IOS_EXPERIMENTAL | base64 --decode > GoogleService-Info.plist
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
working_directory: ios
|
working_directory: ios
|
||||||
- run:
|
- run:
|
||||||
|
@ -223,12 +230,12 @@ commands:
|
||||||
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist
|
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $APP_STORE_CONNECT_API_BASE64 ]]; then
|
if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
|
||||||
echo $APP_STORE_CONNECT_API_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
|
echo $APP_STORE_CONNECT_API_KEY_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
|
||||||
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
|
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
|
||||||
bundle exec fastlane ios build_official
|
bundle exec fastlane ios build_official
|
||||||
else
|
else
|
||||||
if [[ $KEYSTORE ]]; then
|
if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
|
||||||
bundle exec fastlane ios build_experimental
|
bundle exec fastlane ios build_experimental
|
||||||
else
|
else
|
||||||
bundle exec fastlane ios build_fork
|
bundle exec fastlane ios build_fork
|
||||||
|
@ -318,11 +325,19 @@ commands:
|
||||||
- run:
|
- run:
|
||||||
name: Fastlane Tesflight Upload
|
name: Fastlane Tesflight Upload
|
||||||
command: |
|
command: |
|
||||||
echo $APP_STORE_CONNECT_API_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
|
echo $APP_STORE_CONNECT_API_KEY_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
|
||||||
bundle exec fastlane ios beta official:<< parameters.official >>
|
bundle exec fastlane ios beta official:<< parameters.official >>
|
||||||
working_directory: ios
|
working_directory: ios
|
||||||
- save_cache: *save-gems-cache
|
- save_cache: *save-gems-cache
|
||||||
|
|
||||||
|
create-e2e-account-file:
|
||||||
|
description: "Create e2e account file"
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
command: |
|
||||||
|
echo $E2E_ACCOUNT | base64 --decode > ./e2e_account.ts
|
||||||
|
working_directory: e2e
|
||||||
|
|
||||||
version: 2.1
|
version: 2.1
|
||||||
|
|
||||||
# EXECUTORS
|
# EXECUTORS
|
||||||
|
@ -434,6 +449,94 @@ jobs:
|
||||||
- upload-to-google-play-beta:
|
- upload-to-google-play-beta:
|
||||||
official: true
|
official: true
|
||||||
|
|
||||||
|
e2e-build-android:
|
||||||
|
<<: *defaults
|
||||||
|
executor:
|
||||||
|
name: android/android-machine
|
||||||
|
resource-class: xlarge
|
||||||
|
tag: 2022.12.1
|
||||||
|
environment:
|
||||||
|
<<: *android-env
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache: *restore-npm-cache-linux
|
||||||
|
- run: *install-npm-modules
|
||||||
|
- save_cache: *save-npm-cache-linux
|
||||||
|
- restore_cache: *restore-gradle-cache
|
||||||
|
- run:
|
||||||
|
name: Configure Gradle
|
||||||
|
command: |
|
||||||
|
echo -e "" > ./gradle.properties
|
||||||
|
# echo -e "android.enableAapt2=false" >> ./gradle.properties
|
||||||
|
echo -e "android.useAndroidX=true" >> ./gradle.properties
|
||||||
|
echo -e "android.enableJetifier=true" >> ./gradle.properties
|
||||||
|
echo -e "newArchEnabled=false" >> ./gradle.properties
|
||||||
|
echo -e "FLIPPER_VERSION=0.125.0" >> ./gradle.properties
|
||||||
|
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
|
||||||
|
echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
|
||||||
|
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
|
||||||
|
echo $KEYSTORE_EXPERIMENTAL_BASE64 | base64 --decode > ./app/$KEYSTORE_EXPERIMENTAL
|
||||||
|
echo -e "KEYSTORE=$KEYSTORE_EXPERIMENTAL" >> ./gradle.properties
|
||||||
|
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
|
||||||
|
echo -e "KEY_ALIAS=$KEYSTORE_EXPERIMENTAL_ALIAS" >> ./gradle.properties
|
||||||
|
echo -e "KEY_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
|
||||||
|
working_directory: android
|
||||||
|
- run:
|
||||||
|
name: Build Android
|
||||||
|
command: |
|
||||||
|
echo "RUNNING_E2E_TESTS=true" > ./.env
|
||||||
|
yarn e2e:android-build
|
||||||
|
- save_cache: *save-gradle-cache
|
||||||
|
- store_artifacts:
|
||||||
|
path: android/app/build/outputs/apk/experimentalPlay/release/app-experimental-play-release.apk
|
||||||
|
- store_artifacts:
|
||||||
|
path: android/app/build/outputs/apk/androidTest/experimentalPlay/release/app-experimental-play-release-androidTest.apk
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: /home/circleci/repo
|
||||||
|
paths:
|
||||||
|
- android/app/build/outputs/apk/
|
||||||
|
|
||||||
|
e2e-test-android:
|
||||||
|
<<: *defaults
|
||||||
|
executor:
|
||||||
|
name: android/android-machine
|
||||||
|
resource-class: xlarge
|
||||||
|
tag: 2022.12.1
|
||||||
|
parallelism: 4
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- attach_workspace:
|
||||||
|
at: /home/circleci/repo
|
||||||
|
- restore_cache: *restore-npm-cache-linux
|
||||||
|
- run: *install-npm-modules
|
||||||
|
- save_cache: *save-npm-cache-linux
|
||||||
|
- run: mkdir ~/junit
|
||||||
|
- create-e2e-account-file
|
||||||
|
- android/create-avd:
|
||||||
|
avd-name: Pixel_API_31_AOSP
|
||||||
|
install: true
|
||||||
|
system-image: system-images;android-31;default;x86_64
|
||||||
|
- run:
|
||||||
|
name: Setup emulator
|
||||||
|
command: |
|
||||||
|
echo "hw.lcd.density = 440" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
|
||||||
|
echo "hw.lcd.height = 2280" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
|
||||||
|
echo "hw.lcd.width = 1080" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
|
||||||
|
- run:
|
||||||
|
name: Run Detox Tests
|
||||||
|
command: |
|
||||||
|
TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
|
||||||
|
yarn e2e:android-test $TEST
|
||||||
|
- store_artifacts:
|
||||||
|
path: artifacts
|
||||||
|
- run:
|
||||||
|
command: cp junit.xml ~/junit/
|
||||||
|
when: always
|
||||||
|
- store_test_results:
|
||||||
|
path: ~/junit
|
||||||
|
- store_artifacts:
|
||||||
|
path: ~/junit
|
||||||
|
|
||||||
# iOS builds
|
# iOS builds
|
||||||
ios-build-experimental:
|
ios-build-experimental:
|
||||||
executor: mac-env
|
executor: mac-env
|
||||||
|
@ -457,11 +560,89 @@ jobs:
|
||||||
- upload-to-testflight:
|
- upload-to-testflight:
|
||||||
official: true
|
official: true
|
||||||
|
|
||||||
|
e2e-build-ios:
|
||||||
|
executor: mac-env
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache: *restore-gems-cache
|
||||||
|
- restore_cache: *restore-npm-cache-mac
|
||||||
|
- run: *install-npm-modules
|
||||||
|
- run: *update-fastlane-ios
|
||||||
|
- save_cache: *save-npm-cache-mac
|
||||||
|
- save_cache: *save-gems-cache
|
||||||
|
- manage-pods
|
||||||
|
- run:
|
||||||
|
name: Configure Detox
|
||||||
|
command: |
|
||||||
|
brew tap wix/brew
|
||||||
|
brew install applesimutils
|
||||||
|
- run:
|
||||||
|
name: Build
|
||||||
|
command: |
|
||||||
|
/usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/RocketChatRN/Info.plist
|
||||||
|
/usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/ShareRocketChatRN/Info.plist
|
||||||
|
yarn detox clean-framework-cache && yarn detox build-framework-cache
|
||||||
|
echo "RUNNING_E2E_TESTS=true" > ./.env
|
||||||
|
yarn e2e:ios-build
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: /Users/distiller/project
|
||||||
|
paths:
|
||||||
|
- ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app
|
||||||
|
|
||||||
|
e2e-test-ios:
|
||||||
|
executor: mac-env
|
||||||
|
parallelism: 5
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- attach_workspace:
|
||||||
|
at: /Users/distiller/project
|
||||||
|
- restore_cache: *restore-npm-cache-mac
|
||||||
|
- run: *install-npm-modules
|
||||||
|
- save_cache: *save-npm-cache-mac
|
||||||
|
- run: mkdir ~/junit
|
||||||
|
- run:
|
||||||
|
name: Configure Detox
|
||||||
|
command: |
|
||||||
|
brew tap wix/brew
|
||||||
|
brew install applesimutils
|
||||||
|
yarn detox clean-framework-cache && yarn detox build-framework-cache
|
||||||
|
- create-e2e-account-file
|
||||||
|
- run:
|
||||||
|
name: Run tests
|
||||||
|
command: |
|
||||||
|
TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
|
||||||
|
yarn e2e:ios-test $TEST
|
||||||
|
- store_artifacts:
|
||||||
|
path: artifacts
|
||||||
|
- run:
|
||||||
|
command: cp junit.xml ~/junit/
|
||||||
|
when: always
|
||||||
|
- store_test_results:
|
||||||
|
path: ~/junit
|
||||||
|
- store_artifacts:
|
||||||
|
path: ~/junit
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
jobs:
|
jobs:
|
||||||
- lint-testunit
|
- lint-testunit
|
||||||
|
|
||||||
|
# E2E tests
|
||||||
|
- e2e-hold:
|
||||||
|
type: approval
|
||||||
|
- e2e-build-ios:
|
||||||
|
requires:
|
||||||
|
- e2e-hold
|
||||||
|
- e2e-test-ios:
|
||||||
|
requires:
|
||||||
|
- e2e-build-ios
|
||||||
|
- e2e-build-android:
|
||||||
|
requires:
|
||||||
|
- e2e-hold
|
||||||
|
- e2e-test-android:
|
||||||
|
requires:
|
||||||
|
- e2e-build-android
|
||||||
|
|
||||||
# iOS Experimental
|
# iOS Experimental
|
||||||
- ios-hold-build-experimental:
|
- ios-hold-build-experimental:
|
||||||
type: approval
|
type: approval
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/** @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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
24
.eslintrc.js
24
.eslintrc.js
|
@ -2,7 +2,7 @@ module.exports = {
|
||||||
settings: {
|
settings: {
|
||||||
'import/resolver': {
|
'import/resolver': {
|
||||||
node: {
|
node: {
|
||||||
extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js', '.native.js']
|
extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js', '.native.js', '.ios.tsx', '.android.tsx']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -156,22 +156,6 @@ module.exports = {
|
||||||
__DEV__: true
|
__DEV__: true
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
|
||||||
files: ['e2e/**'],
|
|
||||||
globals: {
|
|
||||||
by: true,
|
|
||||||
detox: true,
|
|
||||||
device: true,
|
|
||||||
element: true,
|
|
||||||
expect: true,
|
|
||||||
waitFor: true
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'import/no-extraneous-dependencies': 0,
|
|
||||||
'no-await-in-loop': 0,
|
|
||||||
'no-restricted-syntax': 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
extends: [
|
extends: [
|
||||||
|
@ -253,6 +237,12 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['e2e/**'],
|
||||||
|
rules: {
|
||||||
|
'no-await-in-loop': 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,5 +3,5 @@ updates:
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
open-pull-requests-limit: 25
|
open-pull-requests-limit: 25
|
|
@ -66,5 +66,7 @@ artifacts
|
||||||
e2e/docker/rc_test_env/docker-compose.yml
|
e2e/docker/rc_test_env/docker-compose.yml
|
||||||
e2e/docker/data/db
|
e2e/docker/data/db
|
||||||
e2e/e2e_account.js
|
e2e/e2e_account.js
|
||||||
|
e2e/e2e_account.ts
|
||||||
|
junit.xml
|
||||||
|
|
||||||
*.p8
|
*.p8
|
|
@ -1 +1 @@
|
||||||
2.7.4
|
2.7.7
|
||||||
|
|
|
@ -6,11 +6,13 @@ import RNBootSplash from 'react-native-bootsplash';
|
||||||
import { selectServerRequest } from '../app/actions/server';
|
import { selectServerRequest } from '../app/actions/server';
|
||||||
import { mockedStore as store } from '../app/reducers/mockedStore';
|
import { mockedStore as store } from '../app/reducers/mockedStore';
|
||||||
import database from '../app/lib/database';
|
import database from '../app/lib/database';
|
||||||
|
import { setUser } from '../app/actions/login';
|
||||||
|
|
||||||
RNBootSplash.hide();
|
RNBootSplash.hide();
|
||||||
|
|
||||||
const baseUrl = 'https://open.rocket.chat';
|
const baseUrl = 'https://open.rocket.chat';
|
||||||
store.dispatch(selectServerRequest(baseUrl));
|
store.dispatch(selectServerRequest(baseUrl));
|
||||||
|
store.dispatch(setUser({ id: 'abc', username: 'rocket.cat', name: 'Rocket Cat' }));
|
||||||
database.setActiveDB(baseUrl);
|
database.setActiveDB(baseUrl);
|
||||||
|
|
||||||
const StorybookUIRoot = getStorybookUI({});
|
const StorybookUIRoot = getStorybookUI({});
|
||||||
|
|
|
@ -3,7 +3,13 @@ import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { themes } from '../app/lib/constants';
|
import { themes } from '../app/lib/constants';
|
||||||
import MessageContext from '../app/containers/message/Context';
|
import MessageContext from '../app/containers/message/Context';
|
||||||
|
import { selectServerRequest } from '../app/actions/server';
|
||||||
import { mockedStore as store } from '../app/reducers/mockedStore';
|
import { mockedStore as store } from '../app/reducers/mockedStore';
|
||||||
|
import { setUser } from '../app/actions/login';
|
||||||
|
|
||||||
|
const baseUrl = 'https://open.rocket.chat';
|
||||||
|
store.dispatch(selectServerRequest(baseUrl));
|
||||||
|
store.dispatch(setUser({ id: 'abc', username: 'rocket.cat', name: 'Rocket Cat' }));
|
||||||
|
|
||||||
export const decorators = [
|
export const decorators = [
|
||||||
Story => (
|
Story => (
|
||||||
|
@ -15,7 +21,7 @@ export const decorators = [
|
||||||
username: 'diego.mello',
|
username: 'diego.mello',
|
||||||
token: 'abc'
|
token: 'abc'
|
||||||
},
|
},
|
||||||
baseUrl: 'https://open.rocket.chat',
|
baseUrl,
|
||||||
onPress: () => {},
|
onPress: () => {},
|
||||||
onLongPress: () => {},
|
onLongPress: () => {},
|
||||||
reactionInit: () => {},
|
reactionInit: () => {},
|
||||||
|
|
|
@ -22,6 +22,7 @@ const getStories = () => {
|
||||||
require("../app/containers/Avatar/Avatar.stories.tsx"),
|
require("../app/containers/Avatar/Avatar.stories.tsx"),
|
||||||
require("../app/containers/BackgroundContainer/index.stories.tsx"),
|
require("../app/containers/BackgroundContainer/index.stories.tsx"),
|
||||||
require("../app/containers/Button/Button.stories.tsx"),
|
require("../app/containers/Button/Button.stories.tsx"),
|
||||||
|
require("../app/containers/Chip/Chip.stories.tsx"),
|
||||||
require("../app/containers/HeaderButton/HeaderButtons.stories.tsx"),
|
require("../app/containers/HeaderButton/HeaderButtons.stories.tsx"),
|
||||||
require("../app/containers/List/List.stories.tsx"),
|
require("../app/containers/List/List.stories.tsx"),
|
||||||
require("../app/containers/LoginServices/LoginServices.stories.tsx"),
|
require("../app/containers/LoginServices/LoginServices.stories.tsx"),
|
||||||
|
@ -29,6 +30,7 @@ const getStories = () => {
|
||||||
require("../app/containers/markdown/new/NewMarkdown.stories.tsx"),
|
require("../app/containers/markdown/new/NewMarkdown.stories.tsx"),
|
||||||
require("../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx"),
|
require("../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx"),
|
||||||
require("../app/containers/message/Message.stories.tsx"),
|
require("../app/containers/message/Message.stories.tsx"),
|
||||||
|
require("../app/containers/ReactionsList/ReactionsList.stories.tsx"),
|
||||||
require("../app/containers/RoomHeader/RoomHeader.stories.tsx"),
|
require("../app/containers/RoomHeader/RoomHeader.stories.tsx"),
|
||||||
require("../app/containers/RoomItem/RoomItem.stories.tsx"),
|
require("../app/containers/RoomItem/RoomItem.stories.tsx"),
|
||||||
require("../app/containers/SearchBox/SearchBox.stories.tsx"),
|
require("../app/containers/SearchBox/SearchBox.stories.tsx"),
|
||||||
|
@ -38,6 +40,7 @@ const getStories = () => {
|
||||||
require("../app/containers/UIKit/UiKitModal.stories.tsx"),
|
require("../app/containers/UIKit/UiKitModal.stories.tsx"),
|
||||||
require("../app/containers/UnreadBadge/UnreadBadge.stories.tsx"),
|
require("../app/containers/UnreadBadge/UnreadBadge.stories.tsx"),
|
||||||
require("../app/views/CannedResponsesListView/CannedResponseItem.stories.tsx"),
|
require("../app/views/CannedResponsesListView/CannedResponseItem.stories.tsx"),
|
||||||
|
require("../app/views/CreateChannelView/RoomSettings/SwitchItem.stories.tsx"),
|
||||||
require("../app/views/DiscussionsView/Item.stories.tsx"),
|
require("../app/views/DiscussionsView/Item.stories.tsx"),
|
||||||
require("../app/views/RoomView/LoadMore/LoadMore.stories.tsx"),
|
require("../app/views/RoomView/LoadMore/LoadMore.stories.tsx"),
|
||||||
require("../app/views/ThreadMessagesView/Item.stories.tsx"),
|
require("../app/views/ThreadMessagesView/Item.stories.tsx"),
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -1,4 +1,4 @@
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
||||||
ruby '2.7.4'
|
ruby '2.7.7'
|
||||||
gem 'cocoapods', '~> 1.11', '>= 1.11.2'
|
gem 'cocoapods', '~> 1.11', '>= 1.11.2'
|
|
@ -1,13 +1,13 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||||
|
|
||||||
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||||
|
|
||||||
exports[`Storyshots Button Disabled Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
|
exports[`Storyshots Button Disabled Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
|
||||||
|
|
||||||
exports[`Storyshots Button Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
|
exports[`Storyshots Button Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
|
||||||
|
|
||||||
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||||
|
|
||||||
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Storyshots Chip Chip Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Rocket.Cat\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||||
|
|
||||||
|
exports[`Storyshots Chip Chip With Short Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Short\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||||
|
|
||||||
|
exports[`Storyshots Chip Chip Without Avatar 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||||
|
|
||||||
|
exports[`Storyshots Chip Chip Without Avatar And Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar and Icon\\"]}]}]}]}]}"`;
|
||||||
|
|
||||||
|
exports[`Storyshots Chip Chip Without Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Icon\\"]}]}]}]}]}"`;
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
|
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
|
||||||
|
|
||||||
exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]"`;
|
exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]"`;
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,3 +1,3 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\",\\"top\\":14},{\\"right\\":15}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\",\\"style\\":{\\"backgroundColor\\":\\"#ffffff\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\",\\"justifyContent\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"paddingHorizontal\\":16,\\"paddingVertical\\":10,\\"borderWidth\\":1,\\"borderRadius\\":4},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\"},{\\"right\\":12}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||||
|
|
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
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,3 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Storyshots SwitchItem Switch 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"minHeight\\":54,\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"space-between\\",\\"flexDirection\\":\\"row\\",\\"maxHeight\\":80,\\"marginBottom\\":12},{\\"backgroundColor\\":\\"#ffffff\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"marginRight\\":8}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Welcome to Rocket.Chat\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"testID\\":\\"create-channel-switch-id-hint\\",\\"style\\":[{\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"Only authorized users can write new messages\\"]}]},{\\"type\\":\\"RCTSwitch\\",\\"props\\":{\\"testID\\":\\"create-channel-switch-id\\",\\"disabled\\":false,\\"onTintColor\\":\\"#2de0a5\\",\\"style\\":{\\"height\\":31,\\"width\\":51},\\"tintColor\\":\\"#f5455c\\",\\"value\\":false,\\"accessibilityRole\\":\\"switch\\"},\\"children\\":null}]}]}"`;
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "4.30.0"
|
versionName "4.37.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
if (!isFoss) {
|
if (!isFoss) {
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
|
@ -250,6 +250,7 @@ android {
|
||||||
release {
|
release {
|
||||||
minifyEnabled enableProguardInReleaseBuilds
|
minifyEnabled enableProguardInReleaseBuilds
|
||||||
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
|
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
|
||||||
|
proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
if (!isFoss) {
|
if (!isFoss) {
|
||||||
firebaseCrashlytics {
|
firebaseCrashlytics {
|
||||||
|
@ -268,6 +269,11 @@ android {
|
||||||
// pickFirst '**/x86_64/libc++_shared.so'
|
// pickFirst '**/x86_64/libc++_shared.so'
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// FIXME: Remove when we update RN
|
||||||
|
packagingOptions {
|
||||||
|
pickFirst '**/*.so'
|
||||||
|
}
|
||||||
|
|
||||||
// applicationVariants are e.g. debug, release
|
// applicationVariants are e.g. debug, release
|
||||||
|
|
||||||
flavorDimensions "app", "type"
|
flavorDimensions "app", "type"
|
||||||
|
@ -280,10 +286,6 @@ android {
|
||||||
dimension = "app"
|
dimension = "app"
|
||||||
buildConfigField "boolean", "IS_OFFICIAL", "false"
|
buildConfigField "boolean", "IS_OFFICIAL", "false"
|
||||||
}
|
}
|
||||||
e2e {
|
|
||||||
dimension = "app"
|
|
||||||
buildConfigField "boolean", "IS_OFFICIAL", "false"
|
|
||||||
}
|
|
||||||
foss {
|
foss {
|
||||||
dimension = "type"
|
dimension = "type"
|
||||||
buildConfigField "boolean", "FDROID_BUILD", "true"
|
buildConfigField "boolean", "FDROID_BUILD", "true"
|
||||||
|
@ -311,16 +313,6 @@ android {
|
||||||
java.srcDirs = ['src/main/java', 'src/play/java']
|
java.srcDirs = ['src/main/java', 'src/play/java']
|
||||||
manifest.srcFile 'src/play/AndroidManifest.xml'
|
manifest.srcFile 'src/play/AndroidManifest.xml'
|
||||||
}
|
}
|
||||||
e2ePlayDebug {
|
|
||||||
java.srcDirs = ['src/main/java', 'src/play/java']
|
|
||||||
res.srcDirs = ['src/experimental/res']
|
|
||||||
manifest.srcFile 'src/play/AndroidManifest.xml'
|
|
||||||
}
|
|
||||||
e2ePlayRelease {
|
|
||||||
java.srcDirs = ['src/main/java', 'src/play/java']
|
|
||||||
res.srcDirs = ['src/experimental/res']
|
|
||||||
manifest.srcFile 'src/play/AndroidManifest.xml'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
|
@ -351,14 +343,12 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':@react-native-community_viewpager')
|
implementation project(':@react-native-community_viewpager')
|
||||||
|
implementation "androidx.core:core-splashscreen:1.0.0"
|
||||||
playImplementation project(':react-native-notifications')
|
playImplementation project(':react-native-notifications')
|
||||||
playImplementation 'com.google.firebase:firebase-core:16.0.0'
|
playImplementation 'com.google.firebase:firebase-core:16.0.0'
|
||||||
playImplementation project(':@react-native-firebase_app')
|
playImplementation project(':@react-native-firebase_app')
|
||||||
playImplementation project(':@react-native-firebase_analytics')
|
playImplementation project(':@react-native-firebase_analytics')
|
||||||
playImplementation project(':@react-native-firebase_crashlytics')
|
playImplementation project(':@react-native-firebase_crashlytics')
|
||||||
implementation(project(':react-native-jitsi-meet')) { // https://github.com/skrafft/react-native-jitsi-meet#side-note
|
|
||||||
exclude group: 'com.facebook.react',module:'react-native-svg'
|
|
||||||
}
|
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
|
|
||||||
//noinspection GradleDynamicVersion
|
//noinspection GradleDynamicVersion
|
||||||
|
@ -387,8 +377,9 @@ dependencies {
|
||||||
implementation "com.github.bumptech.glide:glide:4.9.0"
|
implementation "com.github.bumptech.glide:glide:4.9.0"
|
||||||
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
|
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
|
||||||
implementation "com.tencent:mmkv-static:1.2.10"
|
implementation "com.tencent:mmkv-static:1.2.10"
|
||||||
androidTestImplementation('com.wix:detox:+') { transitive = true }
|
androidTestImplementation('com.wix:detox:+')
|
||||||
androidTestImplementation 'junit:junit:4.12'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
|
implementation 'com.facebook.soloader:soloader:0.10.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNewArchitectureEnabled()) {
|
if (isNewArchitectureEnabled()) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ public class DetoxTest {
|
||||||
@Rule
|
@Rule
|
||||||
// Replace 'MainActivity' with the value of android:name entry in
|
// Replace 'MainActivity' with the value of android:name entry in
|
||||||
// <activity> in AndroidManifest.xml
|
// <activity> in AndroidManifest.xml
|
||||||
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
|
public ActivityTestRule<chat.rocket.reactnative.MainActivity> mActivityRule = new ActivityTestRule<>(chat.rocket.reactnative.MainActivity.class, false, false);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runDetoxTests() {
|
public void runDetoxTests() {
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
package chat.rocket.reactnative;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.facebook.react.ReactInstanceManager;
|
|
||||||
|
|
||||||
public class MainDebugApplication extends MainApplication {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
|
|
||||||
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @param reactInstanceManager
|
|
||||||
*/
|
|
||||||
private static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
|
||||||
ReactNativeFlipper.initializeFlipper(context, reactInstanceManager);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?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>
|
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
|
@ -5,6 +5,15 @@
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
<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
|
<application
|
||||||
android:name="chat.rocket.reactnative.MainApplication"
|
android:name="chat.rocket.reactnative.MainApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
@ -13,17 +22,9 @@
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
|
||||||
tools:replace="android:allowBackup">
|
|
||||||
<activity
|
|
||||||
android:name="com.zoontek.rnbootsplash.RNBootSplashActivity"
|
|
||||||
android:theme="@style/BootTheme"
|
android:theme="@style/BootTheme"
|
||||||
android:exported="true">
|
android:hardwareAccelerated="true"
|
||||||
<intent-filter>
|
tools:replace="android:allowBackup">
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name="chat.rocket.reactnative.MainActivity"
|
android:name="chat.rocket.reactnative.MainActivity"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||||
|
@ -56,6 +57,10 @@
|
||||||
android:host="jitsi.rocket.chat"
|
android:host="jitsi.rocket.chat"
|
||||||
android:scheme="rocketchat" />
|
android:scheme="rocketchat" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="chat.rocket.reactnative.share.ShareActivity"
|
android:name="chat.rocket.reactnative.share.ShareActivity"
|
||||||
|
@ -63,7 +68,7 @@
|
||||||
android:label="@string/share_extension_name"
|
android:label="@string/share_extension_name"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/ShareTheme"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
@ -74,5 +79,10 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
<queries>
|
||||||
|
<package android:name="org.jitsi.meet" />
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
Binary file not shown.
|
@ -1,24 +1,23 @@
|
||||||
package chat.rocket.reactnative;
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.facebook.react.ReactRootView;
|
|
||||||
import com.facebook.react.ReactActivityDelegate;
|
|
||||||
import com.facebook.react.ReactActivity;
|
import com.facebook.react.ReactActivity;
|
||||||
|
import com.facebook.react.ReactActivityDelegate;
|
||||||
|
import com.facebook.react.ReactRootView;
|
||||||
|
import com.zoontek.rnbootsplash.RNBootSplash;
|
||||||
|
|
||||||
import expo.modules.ReactActivityDelegateWrapper;
|
import expo.modules.ReactActivityDelegateWrapper;
|
||||||
|
|
||||||
import com.zoontek.rnbootsplash.RNBootSplash;
|
|
||||||
|
|
||||||
public class MainActivity extends ReactActivity {
|
public class MainActivity extends ReactActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
RNBootSplash.init(this);
|
||||||
// https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067
|
// https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067
|
||||||
super.onCreate(null);
|
super.onCreate(null);
|
||||||
RNBootSplash.init(R.drawable.launch_screen, MainActivity.this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -9,13 +9,16 @@ import com.facebook.react.ReactApplication;
|
||||||
import com.facebook.react.ReactNativeHost;
|
import com.facebook.react.ReactNativeHost;
|
||||||
import com.facebook.react.ReactPackage;
|
import com.facebook.react.ReactPackage;
|
||||||
import com.facebook.react.config.ReactFeatureFlags;
|
import com.facebook.react.config.ReactFeatureFlags;
|
||||||
|
import com.facebook.react.ReactInstanceManager;
|
||||||
import com.facebook.soloader.SoLoader;
|
import com.facebook.soloader.SoLoader;
|
||||||
import com.reactnativecommunity.viewpager.RNCViewPagerPackage;
|
import com.reactnativecommunity.viewpager.RNCViewPagerPackage;
|
||||||
import com.facebook.react.bridge.JSIModulePackage;
|
import com.facebook.react.bridge.JSIModulePackage;
|
||||||
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
|
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import expo.modules.ApplicationLifecycleDispatcher;
|
import expo.modules.ApplicationLifecycleDispatcher;
|
||||||
import expo.modules.ReactNativeHostWrapper;
|
import expo.modules.ReactNativeHostWrapper;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -76,6 +79,7 @@ public class MainApplication extends Application implements ReactApplication {
|
||||||
// If you opted-in for the New Architecture, we enable the TurboModule system
|
// If you opted-in for the New Architecture, we enable the TurboModule system
|
||||||
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
||||||
SoLoader.init(this, /* native exopackage */ false);
|
SoLoader.init(this, /* native exopackage */ false);
|
||||||
|
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||||
ApplicationLifecycleDispatcher.onApplicationCreate(this);
|
ApplicationLifecycleDispatcher.onApplicationCreate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,4 +88,35 @@ public class MainApplication extends Application implements ReactApplication {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
|
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
|
||||||
|
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param reactInstanceManager
|
||||||
|
*/
|
||||||
|
private static void initializeFlipper(
|
||||||
|
Context context, ReactInstanceManager reactInstanceManager) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
try {
|
||||||
|
/*
|
||||||
|
We use reflection here to pick up the class that initializes Flipper,
|
||||||
|
since Flipper library is not available in release mode
|
||||||
|
*/
|
||||||
|
Class<?> aClass = Class.forName("chat.rocket.reactnative.ReactNativeFlipper");
|
||||||
|
aClass
|
||||||
|
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
|
||||||
|
.invoke(null, context, reactInstanceManager);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package chat.rocket.reactnative.share;
|
package chat.rocket.reactnative.share;
|
||||||
|
|
||||||
import com.facebook.react.ReactActivity;
|
import com.facebook.react.ReactActivity;
|
||||||
import com.facebook.react.ReactActivityDelegate;
|
|
||||||
import com.facebook.react.ReactRootView;
|
|
||||||
|
|
||||||
public class ShareActivity extends ReactActivity {
|
public class ShareActivity extends ReactActivity {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
|
||||||
<!-- the background color. it can be a system color or a custom one defined in colors.xml -->
|
|
||||||
<item android:drawable="@color/splashBackground" />
|
|
||||||
<item>
|
|
||||||
<!-- the app logo, centered horizontally and vertically -->
|
|
||||||
<bitmap
|
|
||||||
android:src="@drawable/splash"
|
|
||||||
android:gravity="center" />
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
|
@ -8,25 +8,15 @@
|
||||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Share.Window" parent="android:Theme">
|
<style name="ShareTheme" parent="AppTheme">
|
||||||
<item name="android:windowEnterAnimation">@null</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
<item name="android:windowExitAnimation">@null</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Share.Transparent" parent="android:Theme">
|
<style name="BootTheme" parent="Theme.SplashScreen">
|
||||||
<item name="android:windowIsTranslucent">true</item>
|
<item name="windowSplashScreenBackground">@color/splashBackground</item>
|
||||||
<item name="android:windowBackground">@color/primary_dark</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
|
||||||
<item name="android:windowContentOverlay">@null</item>
|
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
|
||||||
<item name="android:windowIsFloating">true</item>
|
|
||||||
<item name="android:backgroundDimEnabled">true</item>
|
|
||||||
<item name="android:windowAnimationStyle">@style/Share.Window</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="BootTheme" parent="AppTheme">
|
|
||||||
<item name="android:background">@drawable/launch_screen</item>
|
|
||||||
<item name="colorPrimaryDark">@color/splashBackground</item>
|
|
||||||
<item name="android:navigationBarColor">@color/splashBackground</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- https://github.com/facebook/react-native/blob/d1ab03235cb4b93304150878d2b9057ab45bba77/ReactAndroid/src/main/res/views/modal/values/themes.xml#L5 -->
|
<!-- https://github.com/facebook/react-native/blob/d1ab03235cb4b93304150878d2b9057ab45bba77/ReactAndroid/src/main/res/views/modal/values/themes.xml#L5 -->
|
||||||
|
|
|
@ -7,4 +7,8 @@
|
||||||
tools:ignore="AcceptsUserCertificates" />
|
tools:ignore="AcceptsUserCertificates" />
|
||||||
</trust-anchors>
|
</trust-anchors>
|
||||||
</base-config>
|
</base-config>
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||||
|
<domain includeSubdomains="true">localhost</domain>
|
||||||
|
</domain-config>
|
||||||
</network-security-config>
|
</network-security-config>
|
Binary file not shown.
Before Width: | Height: | Size: 7.5 KiB |
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
|
||||||
<!-- the background color. it can be a system color or a custom one defined in colors.xml -->
|
|
||||||
<item android:drawable="@color/splashBackground" />
|
|
||||||
<item>
|
|
||||||
<!-- the app logo, centered horizontally and vertically -->
|
|
||||||
<bitmap
|
|
||||||
android:src="@drawable/splash"
|
|
||||||
android:gravity="center" />
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
|
@ -6,7 +6,6 @@
|
||||||
android:name="chat.rocket.reactnative.MainPlayApplication"
|
android:name="chat.rocket.reactnative.MainPlayApplication"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:theme="@style/AppTheme"
|
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
tools:replace="android:name"
|
tools:replace="android:name"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package chat.rocket.reactnative;
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
|
@ -35,8 +37,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
|
||||||
|
|
||||||
public class CustomPushNotification extends PushNotification {
|
public class CustomPushNotification extends PushNotification {
|
||||||
public static ReactApplicationContext reactApplicationContext;
|
public static ReactApplicationContext reactApplicationContext;
|
||||||
final NotificationManager notificationManager;
|
final NotificationManager notificationManager;
|
||||||
|
@ -322,7 +322,12 @@ public class CustomPushNotification extends PushNotification {
|
||||||
replyIntent.setAction(KEY_REPLY);
|
replyIntent.setAction(KEY_REPLY);
|
||||||
replyIntent.putExtra("pushNotification", bundle);
|
replyIntent.putExtra("pushNotification", bundle);
|
||||||
|
|
||||||
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent replyPendingIntent;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||||
|
} else {
|
||||||
|
replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
|
RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
|
||||||
.setLabel(label)
|
.setLabel(label)
|
||||||
|
@ -343,12 +348,13 @@ public class CustomPushNotification extends PushNotification {
|
||||||
Intent intent = new Intent(mContext, DismissNotification.class);
|
Intent intent = new Intent(mContext, DismissNotification.class);
|
||||||
intent.putExtra(NOTIFICATION_ID, notificationId);
|
intent.putExtra(NOTIFICATION_ID, notificationId);
|
||||||
|
|
||||||
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, 0);
|
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
notification.setDeleteIntent(dismissPendingIntent);
|
notification.setDeleteIntent(dismissPendingIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notificationLoad(Ejson ejson, Callback callback) {
|
private void notificationLoad(Ejson ejson, Callback callback) {
|
||||||
LoadNotification.load(reactApplicationContext, ejson, callback);
|
LoadNotification loadNotification = new LoadNotification();
|
||||||
|
loadNotification.load(reactApplicationContext, ejson, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
package chat.rocket.reactnative;
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import okhttp3.Call;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import okhttp3.Interceptor;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
class JsonResponse {
|
class JsonResponse {
|
||||||
Data data;
|
Data data;
|
||||||
|
@ -49,11 +43,11 @@ class JsonResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LoadNotification {
|
public class LoadNotification {
|
||||||
private static int RETRY_COUNT = 0;
|
private int RETRY_COUNT = 0;
|
||||||
private static int[] TIMEOUT = new int[]{0, 1, 3, 5, 10};
|
private int[] TIMEOUT = new int[]{0, 1, 3, 5, 10};
|
||||||
private static String TOKEN_KEY = "reactnativemeteor_usertoken-";
|
private String TOKEN_KEY = "reactnativemeteor_usertoken-";
|
||||||
|
|
||||||
public static void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) {
|
public void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) {
|
||||||
final OkHttpClient client = new OkHttpClient();
|
final OkHttpClient client = new OkHttpClient();
|
||||||
HttpUrl.Builder url = HttpUrl.parse(ejson.serverURL().concat("/api/v1/push.get")).newBuilder();
|
HttpUrl.Builder url = HttpUrl.parse(ejson.serverURL().concat("/api/v1/push.get")).newBuilder();
|
||||||
|
|
||||||
|
@ -73,7 +67,7 @@ public class LoadNotification {
|
||||||
runRequest(client, request, callback);
|
runRequest(client, request, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runRequest(OkHttpClient client, Request request, Callback callback) {
|
private void runRequest(OkHttpClient client, Request request, Callback callback) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(TIMEOUT[RETRY_COUNT] * 1000);
|
Thread.sleep(TIMEOUT[RETRY_COUNT] * 1000);
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
import org.apache.tools.ant.taskdefs.condition.Os
|
||||||
|
|
||||||
def safeExtGet(prop, fallback) {
|
|
||||||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase()
|
def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase()
|
||||||
|
@ -13,8 +9,7 @@ buildscript {
|
||||||
buildToolsVersion = "31.0.0"
|
buildToolsVersion = "31.0.0"
|
||||||
minSdkVersion = 23
|
minSdkVersion = 23
|
||||||
compileSdkVersion = 31
|
compileSdkVersion = 31
|
||||||
// TODO: Fix "android:exported" issue and target 31 again
|
targetSdkVersion = 31
|
||||||
targetSdkVersion = 30
|
|
||||||
if (System.properties['os.arch'] == "aarch64") {
|
if (System.properties['os.arch'] == "aarch64") {
|
||||||
// For M1 Users we need to use the NDK 24 which added support for aarch64
|
// For M1 Users we need to use the NDK 24 which added support for aarch64
|
||||||
ndkVersion = "24.0.8215888"
|
ndkVersion = "24.0.8215888"
|
||||||
|
@ -27,8 +22,6 @@ buildscript {
|
||||||
kotlinVersion = '1.6.10'
|
kotlinVersion = '1.6.10'
|
||||||
supportLibVersion = "28.0.0"
|
supportLibVersion = "28.0.0"
|
||||||
libre_build = !(isPlay.toBoolean())
|
libre_build = !(isPlay.toBoolean())
|
||||||
jitsi_url = isPlay ? "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases" : "https://github.com/RocketChat/jitsi-maven-repository/raw/libre/releases"
|
|
||||||
jitsi_version = isPlay ? "3.6.0" : "3.6.0-libre"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -69,9 +62,6 @@ allprojects {
|
||||||
url "$rootDir/../node_modules/detox/Detox-android"
|
url "$rootDir/../node_modules/detox/Detox-android"
|
||||||
}
|
}
|
||||||
|
|
||||||
maven {
|
|
||||||
url jitsi_url
|
|
||||||
}
|
|
||||||
mavenCentral {
|
mavenCentral {
|
||||||
content {
|
content {
|
||||||
excludeGroup "com.facebook.react"
|
excludeGroup "com.facebook.react"
|
||||||
|
@ -81,5 +71,38 @@ allprojects {
|
||||||
google()
|
google()
|
||||||
maven { url 'https://maven.google.com' }
|
maven { url 'https://maven.google.com' }
|
||||||
maven { url 'https://www.jitpack.io' }
|
maven { url 'https://www.jitpack.io' }
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/74333788/5447468
|
||||||
|
// TODO: remove once we update RN
|
||||||
|
exclusiveContent {
|
||||||
|
// We get React Native's Android binaries exclusively through npm,
|
||||||
|
// from a local Maven repo inside node_modules/react-native/.
|
||||||
|
// (The use of exclusiveContent prevents looking elsewhere like Maven Central
|
||||||
|
// and potentially getting a wrong version.)
|
||||||
|
filter {
|
||||||
|
includeGroup "com.facebook.react"
|
||||||
|
}
|
||||||
|
forRepository {
|
||||||
|
maven {
|
||||||
|
// NOTE: if you are in a monorepo, you may have "$rootDir/../../../node_modules/react-native/android"
|
||||||
|
url "$rootDir/../node_modules/react-native/android"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects { subproject ->
|
||||||
|
afterEvaluate {
|
||||||
|
if (!project.name.equalsIgnoreCase("app") && project.hasProperty("android")) {
|
||||||
|
android {
|
||||||
|
compileSdkVersion 31
|
||||||
|
buildToolsVersion "31.0.0"
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 23
|
||||||
|
targetSdkVersion 31
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,7 +23,7 @@ android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
||||||
# Version of flipper SDK to use with React Native
|
# Version of flipper SDK to use with React Native
|
||||||
FLIPPER_VERSION=0.125.0
|
FLIPPER_VERSION=0.175.0
|
||||||
|
|
||||||
# Use this property to specify which architecture you want to build.
|
# Use this property to specify which architecture you want to build.
|
||||||
# You can also override it from the CLI using
|
# You can also override it from the CLI using
|
||||||
|
|
|
@ -28,7 +28,9 @@ export const ROOM = createRequestTypes('ROOM', [
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'REMOVED',
|
'REMOVED',
|
||||||
'FORWARD',
|
'FORWARD',
|
||||||
'USER_TYPING'
|
'USER_TYPING',
|
||||||
|
'HISTORY_REQUEST',
|
||||||
|
'HISTORY_FINISHED'
|
||||||
]);
|
]);
|
||||||
export const INQUIRY = createRequestTypes('INQUIRY', [
|
export const INQUIRY = createRequestTypes('INQUIRY', [
|
||||||
...defaultTypes,
|
...defaultTypes,
|
||||||
|
@ -38,7 +40,14 @@ export const INQUIRY = createRequestTypes('INQUIRY', [
|
||||||
'QUEUE_UPDATE',
|
'QUEUE_UPDATE',
|
||||||
'QUEUE_REMOVE'
|
'QUEUE_REMOVE'
|
||||||
]);
|
]);
|
||||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS', 'SET_MASTER_DETAIL']);
|
export const APP = createRequestTypes('APP', [
|
||||||
|
'START',
|
||||||
|
'READY',
|
||||||
|
'INIT',
|
||||||
|
'INIT_LOCAL_SETTINGS',
|
||||||
|
'SET_MASTER_DETAIL',
|
||||||
|
'SET_NOTIFICATION_PRESENCE_CAP'
|
||||||
|
]);
|
||||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||||
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);
|
export const CREATE_DISCUSSION = createRequestTypes('CREATE_DISCUSSION', [...defaultTypes]);
|
||||||
|
|
|
@ -12,7 +12,11 @@ interface ISetMasterDetail extends Action {
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TActionApp = IAppStart & ISetMasterDetail;
|
interface ISetNotificationPresenceCap extends Action {
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TActionApp = IAppStart & ISetMasterDetail & ISetNotificationPresenceCap;
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
root: RootEnum;
|
root: RootEnum;
|
||||||
|
@ -51,3 +55,10 @@ export function setMasterDetail(isMasterDetail: boolean): ISetMasterDetail {
|
||||||
isMasterDetail
|
isMasterDetail
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setNotificationPresenceCap(show: boolean): ISetNotificationPresenceCap {
|
||||||
|
return {
|
||||||
|
type: APP.SET_NOTIFICATION_PRESENCE_CAP,
|
||||||
|
show
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ interface ILoginRequest extends Action {
|
||||||
credentials: any;
|
credentials: any;
|
||||||
logoutOnError?: boolean;
|
logoutOnError?: boolean;
|
||||||
isFromWebView?: boolean;
|
isFromWebView?: boolean;
|
||||||
|
registerCustomFields?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ILoginSuccess extends Action {
|
interface ILoginSuccess extends Action {
|
||||||
|
@ -56,13 +57,15 @@ export type TActionsLogin = ILoginRequest &
|
||||||
export function loginRequest(
|
export function loginRequest(
|
||||||
credentials: Partial<ICredentials>,
|
credentials: Partial<ICredentials>,
|
||||||
logoutOnError?: boolean,
|
logoutOnError?: boolean,
|
||||||
isFromWebView?: boolean
|
isFromWebView?: boolean,
|
||||||
|
registerCustomFields?: any
|
||||||
): ILoginRequest {
|
): ILoginRequest {
|
||||||
return {
|
return {
|
||||||
type: types.LOGIN.REQUEST,
|
type: types.LOGIN.REQUEST,
|
||||||
credentials,
|
credentials,
|
||||||
logoutOnError,
|
logoutOnError,
|
||||||
isFromWebView
|
isFromWebView,
|
||||||
|
registerCustomFields
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
|
|
||||||
import { ERoomType } from '../definitions/ERoomType';
|
import { ERoomType, RoomType } from '../definitions';
|
||||||
import { ROOM } from './actionsTypes';
|
import { ROOM } from './actionsTypes';
|
||||||
|
|
||||||
// TYPE RETURN RELATED
|
// TYPE RETURN RELATED
|
||||||
|
@ -44,7 +44,24 @@ interface IUserTyping extends Action {
|
||||||
status: boolean;
|
status: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping;
|
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 function subscribeRoom(rid: string): TSubscribeRoom {
|
export function subscribeRoom(rid: string): TSubscribeRoom {
|
||||||
return {
|
return {
|
||||||
|
@ -99,3 +116,19 @@ export function userTyping(rid: string, status = true): IUserTyping {
|
||||||
status
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@ const HANDLE_HEIGHT = isIOS ? 40 : 56;
|
||||||
const MIN_SNAP_HEIGHT = 16;
|
const MIN_SNAP_HEIGHT = 16;
|
||||||
const CANCEL_HEIGHT = 64;
|
const CANCEL_HEIGHT = 64;
|
||||||
|
|
||||||
const ANIMATION_DURATION = 250;
|
export const ACTION_SHEET_ANIMATION_DURATION = 250;
|
||||||
|
|
||||||
const ANIMATION_CONFIG = {
|
const ANIMATION_CONFIG = {
|
||||||
duration: ANIMATION_DURATION,
|
duration: ACTION_SHEET_ANIMATION_DURATION,
|
||||||
// https://easings.net/#easeInOutCubic
|
// https://easings.net/#easeInOutCubic
|
||||||
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0)
|
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0)
|
||||||
};
|
};
|
||||||
|
@ -140,7 +140,7 @@ const ActionSheet = React.memo(
|
||||||
style={{ ...styles.container, ...bottomSheet }}
|
style={{ ...styles.container, ...bottomSheet }}
|
||||||
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
||||||
onChange={index => index === -1 && onClose()}
|
onChange={index => index === -1 && onClose()}
|
||||||
// We need this to allow horizontal swipe gestures inside bottom sheet like in reaction picker
|
// We need this to allow horizontal swipe gesture inside the bottom sheet like in reaction picker
|
||||||
enableContentPanningGesture={data?.enableContentPanningGesture ?? true}
|
enableContentPanningGesture={data?.enableContentPanningGesture ?? true}
|
||||||
{...androidTablet}
|
{...androidTablet}
|
||||||
>
|
>
|
||||||
|
|
|
@ -122,7 +122,6 @@ const ActionSheetContentWithInputAndSubmit = ({
|
||||||
}}
|
}}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
secureTextEntry={secureTextEntry}
|
secureTextEntry={secureTextEntry}
|
||||||
inputStyle={{ borderWidth: 2 }}
|
|
||||||
bottomSheet={isIOS}
|
bottomSheet={isIOS}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -52,7 +52,11 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: I
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <BottomSheetView style={styles.contentContainer}>{children}</BottomSheetView>;
|
return (
|
||||||
|
<BottomSheetView testID='action-sheet' style={styles.contentContainer}>
|
||||||
|
{children}
|
||||||
|
</BottomSheetView>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default BottomSheetContent;
|
export default BottomSheetContent;
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
export * from './Provider';
|
export * from './Provider';
|
||||||
|
export * from './ActionSheet';
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default StyleSheet.create({
|
||||||
paddingHorizontal: 14,
|
paddingHorizontal: 14,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
height: ITEM_HEIGHT,
|
height: ITEM_HEIGHT,
|
||||||
borderRadius: 2,
|
borderRadius: 4,
|
||||||
marginBottom: 12
|
marginBottom: 12
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
|
|
|
@ -43,9 +43,7 @@ const Avatar = React.memo(
|
||||||
|
|
||||||
let image;
|
let image;
|
||||||
if (emoji) {
|
if (emoji) {
|
||||||
image = (
|
image = <Emoji getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />;
|
||||||
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let uri = avatar;
|
let uri = avatar;
|
||||||
if (!isStatic) {
|
if (!isStatic) {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { ViewStyle } from 'react-native';
|
||||||
|
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
|
|
||||||
export interface IAvatar {
|
export interface IAvatar {
|
||||||
server?: string;
|
server?: string;
|
||||||
style?: any;
|
style?: ViewStyle;
|
||||||
text?: string;
|
text?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
emoji?: string;
|
emoji?: string;
|
||||||
|
|
|
@ -22,7 +22,7 @@ const styles = StyleSheet.create({
|
||||||
paddingHorizontal: 14,
|
paddingHorizontal: 14,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
height: 48,
|
height: 48,
|
||||||
borderRadius: 2,
|
borderRadius: 4,
|
||||||
marginBottom: 12
|
marginBottom: 12
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
|
import Chip, { IChip } from './index';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
padding: 16
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Chip'
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChipWrapped = ({ avatar, text, onPress, testID, style }: IChip) => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Chip avatar={avatar} text={text} onPress={onPress} testID={testID} style={style} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ChipText = () => <ChipWrapped avatar='rocket.cat' text={'Rocket.Cat'} onPress={() => {}} />;
|
||||||
|
|
||||||
|
export const ChipWithShortText = () => <ChipWrapped avatar='rocket.cat' text={'Short'} onPress={() => {}} />;
|
||||||
|
|
||||||
|
export const ChipWithoutAvatar = () => <ChipWrapped text={'Without Avatar'} onPress={() => {}} />;
|
||||||
|
|
||||||
|
export const ChipWithoutIcon = () => <ChipWrapped avatar='rocket.cat' text='Without Icon' />;
|
||||||
|
|
||||||
|
export const ChipWithoutAvatarAndIcon = () => <ChipWrapped text='Without Avatar and Icon' />;
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { fireEvent, render } from '@testing-library/react-native';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import Chip, { IChip } from '.';
|
||||||
|
import { ISelectedUser } from '../../reducers/selectedUsers';
|
||||||
|
import { mockedStore as store } from '../../reducers/mockedStore';
|
||||||
|
|
||||||
|
const onPressMock = jest.fn((item: any) => item);
|
||||||
|
|
||||||
|
const testChip = {
|
||||||
|
testID: 'test-chip-id',
|
||||||
|
item: { fname: 'rocket.chat', name: 'rocket.chat' } as ISelectedUser,
|
||||||
|
onPress: onPressMock
|
||||||
|
};
|
||||||
|
|
||||||
|
const Render = ({ testID, text, avatar, onPress }: IChip) => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<Chip testID={testID} text={text} onPress={onPress} avatar={avatar} />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('Chips', () => {
|
||||||
|
it('should render the Chip component', () => {
|
||||||
|
const { findByTestId } = render(
|
||||||
|
<Render
|
||||||
|
text={testChip.item.fname}
|
||||||
|
avatar={testChip.item.name}
|
||||||
|
testID={testChip.testID}
|
||||||
|
onPress={() => testChip.onPress(testChip.item)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(findByTestId(testChip.testID)).toBeTruthy();
|
||||||
|
});
|
||||||
|
it("should not call onPress if it's not passed", async () => {
|
||||||
|
const { findByTestId } = render(<Render text={testChip.item.fname} avatar={testChip.item.name} testID={testChip.testID} />);
|
||||||
|
|
||||||
|
const component = await findByTestId(testChip.testID);
|
||||||
|
fireEvent.press(component);
|
||||||
|
expect(onPressMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it('should tap Chip and return item', async () => {
|
||||||
|
const { findByTestId } = render(
|
||||||
|
<Render
|
||||||
|
text={testChip.item.fname}
|
||||||
|
avatar={testChip.item.name}
|
||||||
|
testID={testChip.testID}
|
||||||
|
onPress={() => testChip.onPress(testChip.item)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const component = await findByTestId(testChip.testID);
|
||||||
|
fireEvent.press(component);
|
||||||
|
expect(onPressMock).toHaveBeenCalled();
|
||||||
|
expect(onPressMock).toHaveReturnedWith(testChip.item);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Pressable, StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import { CustomIcon } from '../CustomIcon';
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import Avatar from '../Avatar';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
pressable: {
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
justifyContent: 'center',
|
||||||
|
maxWidth: 192
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
marginRight: 8,
|
||||||
|
marginVertical: 8
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
marginRight: 8,
|
||||||
|
maxWidth: 120
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
fontSize: 16,
|
||||||
|
...sharedStyles.textMedium
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface IChip {
|
||||||
|
avatar?: string;
|
||||||
|
text: string;
|
||||||
|
onPress?: Function;
|
||||||
|
testID?: string;
|
||||||
|
style?: StyleProp<ViewStyle>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Chip = ({ avatar, text, onPress, testID, style }: IChip) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
testID={testID}
|
||||||
|
style={({ pressed }) => [
|
||||||
|
styles.pressable,
|
||||||
|
{
|
||||||
|
backgroundColor: pressed ? colors.bannerBackground : colors.auxiliaryBackground
|
||||||
|
},
|
||||||
|
style
|
||||||
|
]}
|
||||||
|
disabled={!onPress}
|
||||||
|
onPress={() => onPress?.()}
|
||||||
|
android_ripple={{
|
||||||
|
color: colors.bannerBackground
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={styles.container}>
|
||||||
|
{avatar ? <Avatar text={avatar} size={28} style={styles.avatar} /> : null}
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<Text style={[styles.name, { color: colors.bodyText }]} numberOfLines={1}>
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{onPress ? <CustomIcon name='close' size={16} color={colors.auxiliaryTintColor} /> : null}
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chip;
|
|
@ -1,7 +1,10 @@
|
||||||
export const mappedIcons = {
|
export const mappedIcons = {
|
||||||
'lamp-bulb': 59812,
|
'status-disabled': 59837,
|
||||||
|
'lamp-bulb': 59836,
|
||||||
|
'phone-in': 59835,
|
||||||
'basketball': 59776,
|
'basketball': 59776,
|
||||||
'percentage': 59777,
|
'percentage': 59777,
|
||||||
|
'glasses': 59812,
|
||||||
'burger': 59813,
|
'burger': 59813,
|
||||||
'leaf': 59814,
|
'leaf': 59814,
|
||||||
'airplane': 59815,
|
'airplane': 59815,
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,24 +1,30 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import FastImage from 'react-native-fast-image';
|
import { StyleProp } from 'react-native';
|
||||||
|
import FastImage, { ImageStyle } from 'react-native-fast-image';
|
||||||
|
|
||||||
import { ICustomEmoji } from '../../definitions/IEmoji';
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
import { ICustomEmoji } from '../../definitions';
|
||||||
|
|
||||||
|
interface ICustomEmojiProps {
|
||||||
|
emoji: ICustomEmoji;
|
||||||
|
style: StyleProp<ImageStyle>;
|
||||||
|
}
|
||||||
|
|
||||||
const CustomEmoji = React.memo(
|
const CustomEmoji = React.memo(
|
||||||
({ baseUrl, emoji, style }: ICustomEmoji) => (
|
({ emoji, style }: ICustomEmojiProps) => {
|
||||||
|
const baseUrl = useAppSelector(state => state.share.server.server || state.server.server);
|
||||||
|
return (
|
||||||
<FastImage
|
<FastImage
|
||||||
style={style}
|
style={style}
|
||||||
source={{
|
source={{
|
||||||
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
|
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.name)}.${emoji.extension}`,
|
||||||
priority: FastImage.priority.high
|
priority: FastImage.priority.high
|
||||||
}}
|
}}
|
||||||
resizeMode={FastImage.resizeMode.contain}
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
/>
|
/>
|
||||||
),
|
);
|
||||||
(prevProps, nextProps) => {
|
},
|
||||||
const prevEmoji = prevProps.emoji.content || prevProps.emoji.name;
|
() => true
|
||||||
const nextEmoji = nextProps.emoji.content || nextProps.emoji.name;
|
|
||||||
return prevEmoji === nextEmoji;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default CustomEmoji;
|
export default CustomEmoji;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
|
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||||
|
import styles from './styles';
|
||||||
|
import CustomEmoji from './CustomEmoji';
|
||||||
|
import { IEmoji } from '../../definitions/IEmoji';
|
||||||
|
|
||||||
|
interface IEmojiProps {
|
||||||
|
emoji: IEmoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Emoji = ({ emoji }: IEmojiProps): React.ReactElement => {
|
||||||
|
if (typeof emoji === 'string') {
|
||||||
|
return <Text style={styles.categoryEmoji}>{shortnameToUnicode(`:${emoji}:`)}</Text>;
|
||||||
|
}
|
||||||
|
return <CustomEmoji style={styles.customCategoryEmoji} emoji={emoji} />;
|
||||||
|
};
|
|
@ -1,75 +1,42 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FlatList, Text, TouchableOpacity } from 'react-native';
|
import { FlatList } from 'react-native-gesture-handler';
|
||||||
|
|
||||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
import { IEmoji } from '../../definitions/IEmoji';
|
||||||
import styles from './styles';
|
|
||||||
import CustomEmoji from './CustomEmoji';
|
|
||||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||||
import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
|
import { PressableEmoji } from './PressableEmoji';
|
||||||
|
import { EMOJI_BUTTON_SIZE } from './styles';
|
||||||
|
|
||||||
const EMOJI_SIZE = 50;
|
interface IEmojiCategoryProps {
|
||||||
|
emojis: IEmoji[];
|
||||||
|
onEmojiSelected: (emoji: IEmoji) => void;
|
||||||
|
tabLabel?: string; // needed for react-native-scrollable-tab-view only
|
||||||
|
parentWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
|
const EmojiCategory = ({ onEmojiSelected, emojis, parentWidth }: IEmojiCategoryProps): React.ReactElement | null => {
|
||||||
if (emoji && emoji.isCustom) {
|
if (!parentWidth) {
|
||||||
return (
|
|
||||||
<CustomEmoji
|
|
||||||
style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]}
|
|
||||||
emoji={emoji}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
|
|
||||||
{shortnameToUnicode(`:${emoji}:`)}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
class EmojiCategory extends React.Component<IEmojiCategory> {
|
|
||||||
renderItem(emoji: IEmoji) {
|
|
||||||
const { baseUrl, onEmojiSelected } = this.props;
|
|
||||||
return (
|
|
||||||
<TouchableOpacity
|
|
||||||
activeOpacity={0.7}
|
|
||||||
// @ts-ignore
|
|
||||||
key={emoji && emoji.isCustom ? emoji.content : emoji}
|
|
||||||
onPress={() => onEmojiSelected(emoji)}
|
|
||||||
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}
|
|
||||||
>
|
|
||||||
{renderEmoji(emoji, EMOJI_SIZE, baseUrl)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { emojis, width } = this.props;
|
|
||||||
|
|
||||||
if (!width) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const numColumns = Math.trunc(width / EMOJI_SIZE);
|
const numColumns = Math.trunc(parentWidth / EMOJI_BUTTON_SIZE);
|
||||||
const marginHorizontal = (width - numColumns * EMOJI_SIZE) / 2;
|
const marginHorizontal = (parentWidth % EMOJI_BUTTON_SIZE) / 2;
|
||||||
|
|
||||||
|
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={onEmojiSelected} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
contentContainerStyle={{ marginHorizontal }}
|
key={`emoji-category-${parentWidth}`}
|
||||||
// rerender FlatList in case of width changes
|
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||||
key={`emoji-category-${width}`}
|
|
||||||
// @ts-ignore
|
|
||||||
keyExtractor={item => (item && item.isCustom && item.content) || item}
|
|
||||||
data={emojis}
|
data={emojis}
|
||||||
extraData={this.props}
|
renderItem={renderItem}
|
||||||
renderItem={({ item }) => this.renderItem(item)}
|
|
||||||
numColumns={numColumns}
|
numColumns={numColumns}
|
||||||
initialNumToRender={45}
|
initialNumToRender={45}
|
||||||
removeClippedSubviews
|
removeClippedSubviews
|
||||||
|
contentContainerStyle={{ marginHorizontal }}
|
||||||
{...scrollPersistTaps}
|
{...scrollPersistTaps}
|
||||||
keyboardDismissMode={'none'}
|
keyboardDismissMode={'none'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default EmojiCategory;
|
export default EmojiCategory;
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { StyleSheet, TextInputProps } from 'react-native';
|
||||||
|
|
||||||
|
import { FormTextInput } from '../TextInput/FormTextInput';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import { isIOS } from '../../lib/methods/helpers';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
input: {
|
||||||
|
height: 32,
|
||||||
|
borderWidth: 0,
|
||||||
|
paddingVertical: 0,
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
textInputContainer: {
|
||||||
|
marginBottom: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IEmojiSearchBarProps {
|
||||||
|
onBlur?: TextInputProps['onBlur'];
|
||||||
|
onChangeText: TextInputProps['onChangeText'];
|
||||||
|
bottomSheet?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmojiSearch = ({ onBlur, onChangeText, bottomSheet }: IEmojiSearchBarProps): React.ReactElement => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
|
|
||||||
|
const handleTextChange = (text: string) => {
|
||||||
|
setSearchText(text);
|
||||||
|
if (onChangeText) {
|
||||||
|
onChangeText(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormTextInput
|
||||||
|
autoCapitalize='none'
|
||||||
|
autoCorrect={false}
|
||||||
|
autoComplete='off'
|
||||||
|
returnKeyType='search'
|
||||||
|
textContentType='none'
|
||||||
|
blurOnSubmit
|
||||||
|
placeholder={I18n.t('Search_emoji')}
|
||||||
|
placeholderTextColor={colors.auxiliaryText}
|
||||||
|
underlineColorAndroid='transparent'
|
||||||
|
onChangeText={handleTextChange}
|
||||||
|
inputStyle={[styles.input, { backgroundColor: colors.textInputSecondaryBackground }]}
|
||||||
|
containerStyle={styles.textInputContainer}
|
||||||
|
value={searchText}
|
||||||
|
onClearInput={() => handleTextChange('')}
|
||||||
|
onBlur={onBlur}
|
||||||
|
iconRight={'search'}
|
||||||
|
testID='emoji-searchbar-input'
|
||||||
|
bottomSheet={bottomSheet && isIOS}
|
||||||
|
autoFocus={!bottomSheet} // focus on input when not in reaction picker
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Pressable } from 'react-native';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import { CustomIcon } from '../CustomIcon';
|
||||||
|
import styles from './styles';
|
||||||
|
import { IFooterProps } from './interfaces';
|
||||||
|
|
||||||
|
const BUTTON_HIT_SLOP = { top: 15, right: 15, bottom: 15, left: 15 };
|
||||||
|
|
||||||
|
const Footer = ({ onSearchPressed, onBackspacePressed }: IFooterProps): React.ReactElement => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
return (
|
||||||
|
<View style={[styles.footerContainer, { borderTopColor: colors.borderColor }]}>
|
||||||
|
<Pressable
|
||||||
|
onPress={onSearchPressed}
|
||||||
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
style={({ pressed }) => [styles.footerButtonsContainer, { opacity: pressed ? 0.7 : 1 }]}
|
||||||
|
testID='emoji-picker-search'
|
||||||
|
>
|
||||||
|
<CustomIcon color={colors.auxiliaryTintColor} size={24} name='search' />
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
onPress={onBackspacePressed}
|
||||||
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
style={({ pressed }) => [styles.footerButtonsContainer, { opacity: pressed ? 0.7 : 1 }]}
|
||||||
|
testID='emoji-picker-backspace'
|
||||||
|
>
|
||||||
|
<CustomIcon color={colors.auxiliaryTintColor} size={24} name='backspace' />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Pressable } from 'react-native';
|
||||||
|
|
||||||
|
import styles, { EMOJI_BUTTON_SIZE } from './styles';
|
||||||
|
import { IEmoji } from '../../definitions/IEmoji';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import { isIOS } from '../../lib/methods/helpers';
|
||||||
|
import { Emoji } from './Emoji';
|
||||||
|
|
||||||
|
export const PressableEmoji = ({ emoji, onPress }: { emoji: IEmoji; onPress: (emoji: IEmoji) => void }): React.ReactElement => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
key={typeof emoji === 'string' ? emoji : emoji.name}
|
||||||
|
onPress={() => onPress(emoji)}
|
||||||
|
testID={`emoji-${typeof emoji === 'string' ? emoji : emoji.name}`}
|
||||||
|
android_ripple={{ color: colors.bannerBackground, borderless: true, radius: EMOJI_BUTTON_SIZE / 2 }}
|
||||||
|
style={({ pressed }: { pressed: boolean }) => [
|
||||||
|
styles.emojiButton,
|
||||||
|
{
|
||||||
|
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Emoji emoji={emoji} />
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,56 +1,42 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleProp, Text, TextStyle, TouchableOpacity, View } from 'react-native';
|
import { Pressable, View } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { useTheme } from '../../theme';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { ITabBarProps } from './interfaces';
|
||||||
|
import { isIOS } from '../../lib/methods/helpers';
|
||||||
|
import { CustomIcon } from '../CustomIcon';
|
||||||
|
|
||||||
interface ITabBarProps {
|
const TabBar = ({ activeTab, tabs, goToPage }: ITabBarProps): React.ReactElement => {
|
||||||
goToPage?: (page: number) => void;
|
const { colors } = useTheme();
|
||||||
activeTab?: number;
|
|
||||||
tabs?: string[];
|
|
||||||
tabEmojiStyle: StyleProp<TextStyle>;
|
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TabBar extends React.Component<ITabBarProps> {
|
|
||||||
shouldComponentUpdate(nextProps: ITabBarProps) {
|
|
||||||
const { activeTab, theme } = this.props;
|
|
||||||
if (nextProps.activeTab !== activeTab) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.theme !== theme) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { tabs, goToPage, tabEmojiStyle, activeTab, theme } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.tabsContainer}>
|
<View style={styles.tabsContainer}>
|
||||||
{tabs?.map((tab, i) => (
|
{tabs?.map((tab, i) => (
|
||||||
<TouchableOpacity
|
<Pressable
|
||||||
activeOpacity={0.7}
|
|
||||||
key={tab}
|
key={tab}
|
||||||
onPress={() => {
|
onPress={() => goToPage?.(i)}
|
||||||
if (goToPage) {
|
testID={`emoji-picker-tab-${tab}`}
|
||||||
goToPage(i);
|
android_ripple={{ color: colors.bannerBackground }}
|
||||||
|
style={({ pressed }: { pressed: boolean }) => [
|
||||||
|
styles.tab,
|
||||||
|
{
|
||||||
|
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
|
||||||
}
|
}
|
||||||
}}
|
]}
|
||||||
style={styles.tab}
|
|
||||||
testID={`reaction-picker-${tab}`}
|
|
||||||
>
|
>
|
||||||
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
|
<CustomIcon name={tab} size={24} color={activeTab === i ? colors.tintColor : colors.auxiliaryTintColor} />
|
||||||
{activeTab === i ? (
|
<View
|
||||||
<View style={[styles.activeTabLine, { backgroundColor: themes[theme].tintColor }]} />
|
style={
|
||||||
) : (
|
activeTab === i
|
||||||
<View style={styles.tabLine} />
|
? [styles.activeTabLine, { backgroundColor: colors.tintColor }]
|
||||||
)}
|
: [styles.tabLine, { backgroundColor: colors.borderColor }]
|
||||||
</TouchableOpacity>
|
}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
export default TabBar;
|
||||||
|
|
|
@ -1,155 +1,46 @@
|
||||||
import React, { Component } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { StyleProp, TextStyle, View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||||
import { dequal } from 'dequal';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import orderBy from 'lodash/orderBy';
|
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
|
||||||
import { ImageStyle } from 'react-native-fast-image';
|
|
||||||
|
|
||||||
import TabBar from './TabBar';
|
import TabBar from './TabBar';
|
||||||
import EmojiCategory from './EmojiCategory';
|
import EmojiCategory from './EmojiCategory';
|
||||||
|
import Footer from './Footer';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import categories from './categories';
|
import { categories, emojisByCategory } from '../../lib/constants';
|
||||||
import database from '../../lib/database';
|
import { useTheme } from '../../theme';
|
||||||
import { emojisByCategory } from './emojis';
|
import { IEmoji, ICustomEmojis } from '../../definitions';
|
||||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
import { useAppSelector, useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
import { addFrequentlyUsed } from '../../lib/methods';
|
||||||
import log from '../../lib/methods/helpers/log';
|
import { IEmojiPickerProps, EventTypes } from './interfaces';
|
||||||
import { themes } from '../../lib/constants';
|
|
||||||
import { TSupportedThemes } from '../../theme';
|
|
||||||
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
|
|
||||||
|
|
||||||
interface IEmojiPickerProps {
|
const EmojiPicker = ({
|
||||||
isMessageContainsOnlyEmoji?: boolean;
|
onItemClicked,
|
||||||
getCustomEmoji?: TGetCustomEmoji;
|
isEmojiKeyboard = false,
|
||||||
baseUrl: string;
|
searching = false,
|
||||||
customEmojis: ICustomEmojis;
|
searchedEmojis = []
|
||||||
style?: StyleProp<ImageStyle>;
|
}: IEmojiPickerProps): React.ReactElement | null => {
|
||||||
theme: TSupportedThemes;
|
const { colors } = useTheme();
|
||||||
onEmojiSelected: (emoji: string, shortname?: string) => void;
|
const [parentWidth, setParentWidth] = useState(0);
|
||||||
tabEmojiStyle?: StyleProp<TextStyle>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IEmojiPickerState {
|
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
|
||||||
frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[];
|
|
||||||
customEmojis: any;
|
|
||||||
show: boolean;
|
|
||||||
width: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
const allCustomEmojis: ICustomEmojis = useAppSelector(
|
||||||
constructor(props: IEmojiPickerProps) {
|
state => state.customEmojis,
|
||||||
super(props);
|
() => true
|
||||||
const customEmojis = Object.keys(props.customEmojis)
|
);
|
||||||
.filter(item => item === props.customEmojis[item].name)
|
const customEmojis = Object.keys(allCustomEmojis)
|
||||||
|
.filter(item => item === allCustomEmojis[item].name)
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
content: props.customEmojis[item].name,
|
name: allCustomEmojis[item].name,
|
||||||
extension: props.customEmojis[item].extension,
|
extension: allCustomEmojis[item].extension
|
||||||
isCustom: true
|
|
||||||
}));
|
}));
|
||||||
this.state = {
|
|
||||||
frequentlyUsed: [],
|
|
||||||
customEmojis,
|
|
||||||
show: false,
|
|
||||||
width: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
const handleEmojiSelect = (emoji: IEmoji) => {
|
||||||
await this.updateFrequentlyUsed();
|
onItemClicked(EventTypes.EMOJI_PRESSED, emoji);
|
||||||
this.setState({ show: true });
|
addFrequentlyUsed(emoji);
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: IEmojiPickerProps, nextState: IEmojiPickerState) {
|
|
||||||
const { frequentlyUsed, show, width } = this.state;
|
|
||||||
const { theme } = this.props;
|
|
||||||
if (nextProps.theme !== theme) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextState.show !== show) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextState.width !== width) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!dequal(nextState.frequentlyUsed, frequentlyUsed)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onEmojiSelected = (emoji: IEmoji) => {
|
|
||||||
try {
|
|
||||||
const { onEmojiSelected } = this.props;
|
|
||||||
if (emoji.isCustom) {
|
|
||||||
this._addFrequentlyUsed({
|
|
||||||
content: emoji.content,
|
|
||||||
extension: emoji.extension,
|
|
||||||
isCustom: true
|
|
||||||
});
|
|
||||||
onEmojiSelected(`:${emoji.content}:`);
|
|
||||||
} else {
|
|
||||||
const content = emoji;
|
|
||||||
this._addFrequentlyUsed({ content, isCustom: false });
|
|
||||||
const shortname = `:${emoji}:`;
|
|
||||||
onEmojiSelected(shortnameToUnicode(shortname), shortname);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => {
|
const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string) => {
|
||||||
const db = database.active;
|
|
||||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
|
||||||
let freqEmojiRecord: TFrequentlyUsedEmojiModel;
|
|
||||||
try {
|
|
||||||
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
|
|
||||||
} catch (error) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.write(async () => {
|
|
||||||
if (freqEmojiRecord) {
|
|
||||||
await freqEmojiRecord.update(f => {
|
|
||||||
if (f.count) {
|
|
||||||
f.count += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await freqEmojiCollection.create(f => {
|
|
||||||
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
|
|
||||||
Object.assign(f, emoji);
|
|
||||||
f.count = 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
updateFrequentlyUsed = async () => {
|
|
||||||
const db = database.active;
|
|
||||||
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
|
|
||||||
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
|
|
||||||
const frequentlyUsed = frequentlyUsedOrdered.map(item => {
|
|
||||||
if (item.isCustom) {
|
|
||||||
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
|
|
||||||
}
|
|
||||||
return shortnameToUnicode(`${item.content}`);
|
|
||||||
});
|
|
||||||
this.setState({ frequentlyUsed });
|
|
||||||
};
|
|
||||||
|
|
||||||
onLayout = ({
|
|
||||||
nativeEvent: {
|
|
||||||
layout: { width }
|
|
||||||
}
|
|
||||||
}: any) => this.setState({ width });
|
|
||||||
|
|
||||||
renderCategory(category: keyof typeof emojisByCategory, i: number, label: string) {
|
|
||||||
const { frequentlyUsed, customEmojis, width } = this.state;
|
|
||||||
const { baseUrl } = this.props;
|
|
||||||
|
|
||||||
let emojis = [];
|
let emojis = [];
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
emojis = frequentlyUsed;
|
emojis = frequentlyUsed;
|
||||||
|
@ -158,49 +49,51 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
} else {
|
} else {
|
||||||
emojis = emojisByCategory[category];
|
emojis = emojisByCategory[category];
|
||||||
}
|
}
|
||||||
return (
|
if (!emojis.length) {
|
||||||
<EmojiCategory
|
|
||||||
emojis={emojis}
|
|
||||||
onEmojiSelected={(emoji: IEmoji) => this.onEmojiSelected(emoji)}
|
|
||||||
style={styles.categoryContainer}
|
|
||||||
width={width}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
tabLabel={label}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { show, frequentlyUsed } = this.state;
|
|
||||||
const { tabEmojiStyle, theme } = this.props;
|
|
||||||
|
|
||||||
if (!show) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View onLayout={this.onLayout} style={{ flex: 1 }}>
|
<EmojiCategory
|
||||||
|
parentWidth={parentWidth}
|
||||||
|
emojis={emojis}
|
||||||
|
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
|
||||||
|
tabLabel={label}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.emojiPickerContainer} onLayout={e => setParentWidth(e.nativeEvent.layout.width)}>
|
||||||
|
{searching ? (
|
||||||
|
<EmojiCategory
|
||||||
|
emojis={searchedEmojis}
|
||||||
|
onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)}
|
||||||
|
parentWidth={parentWidth}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<ScrollableTabView
|
<ScrollableTabView
|
||||||
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
|
renderTabBar={() => <TabBar />}
|
||||||
contentProps={{
|
contentProps={{
|
||||||
keyboardShouldPersistTaps: 'always',
|
keyboardShouldPersistTaps: 'always',
|
||||||
keyboardDismissMode: 'none'
|
keyboardDismissMode: 'none'
|
||||||
}}
|
}}
|
||||||
style={{ backgroundColor: themes[theme].focusedBackground }}
|
style={{ backgroundColor: colors.messageboxBackground }}
|
||||||
>
|
>
|
||||||
{categories.tabs.map((tab: any, i) =>
|
{categories.tabs.map((tab: any, i) => renderCategory(tab.category, i, tab.tabLabel))}
|
||||||
i === 0 && frequentlyUsed.length === 0
|
|
||||||
? null // when no frequentlyUsed don't show the tab
|
|
||||||
: this.renderCategory(tab.category, i, tab.tabLabel)
|
|
||||||
)}
|
|
||||||
</ScrollableTabView>
|
</ScrollableTabView>
|
||||||
|
)}
|
||||||
|
{isEmojiKeyboard && (
|
||||||
|
<Footer
|
||||||
|
onSearchPressed={() => onItemClicked(EventTypes.SEARCH_PRESSED)}
|
||||||
|
onBackspacePressed={() => onItemClicked(EventTypes.BACKSPACE_PRESSED)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
export default EmojiPicker;
|
||||||
customEmojis: state.customEmojis,
|
|
||||||
baseUrl: state.share.server.server || state.server.server
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(EmojiPicker);
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { TIconsName } from '../CustomIcon';
|
||||||
|
import { IEmoji } from '../../definitions';
|
||||||
|
|
||||||
|
export enum EventTypes {
|
||||||
|
EMOJI_PRESSED = 'emojiPressed',
|
||||||
|
BACKSPACE_PRESSED = 'backspacePressed',
|
||||||
|
SEARCH_PRESSED = 'searchPressed'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEmojiPickerProps {
|
||||||
|
onItemClicked: (event: EventTypes, emoji?: IEmoji) => void;
|
||||||
|
isEmojiKeyboard?: boolean;
|
||||||
|
searching?: boolean;
|
||||||
|
searchedEmojis?: IEmoji[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFooterProps {
|
||||||
|
onBackspacePressed: () => void;
|
||||||
|
onSearchPressed: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITabBarProps {
|
||||||
|
goToPage?: (page: number) => void;
|
||||||
|
activeTab?: number;
|
||||||
|
tabs?: TIconsName[];
|
||||||
|
}
|
|
@ -2,20 +2,23 @@ import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
|
export const EMOJI_BUTTON_SIZE = 44;
|
||||||
|
export const EMOJI_SIZE = EMOJI_BUTTON_SIZE - 16;
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
tabsContainer: {
|
tabsContainer: {
|
||||||
height: 45,
|
height: EMOJI_BUTTON_SIZE,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row'
|
||||||
paddingTop: 5
|
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingBottom: 10
|
paddingVertical: 10,
|
||||||
|
width: EMOJI_BUTTON_SIZE
|
||||||
},
|
},
|
||||||
tabEmoji: {
|
tabEmoji: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
@ -33,7 +36,6 @@ export default StyleSheet.create({
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
height: 2,
|
height: 2,
|
||||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
|
||||||
bottom: 0
|
bottom: 0
|
||||||
},
|
},
|
||||||
categoryContainer: {
|
categoryContainer: {
|
||||||
|
@ -49,10 +51,34 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
categoryEmoji: {
|
categoryEmoji: {
|
||||||
...sharedStyles.textAlignCenter,
|
...sharedStyles.textAlignCenter,
|
||||||
|
textAlignVertical: 'center',
|
||||||
|
fontSize: EMOJI_SIZE,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
color: '#ffffff'
|
color: '#ffffff'
|
||||||
},
|
},
|
||||||
customCategoryEmoji: {
|
customCategoryEmoji: {
|
||||||
margin: 8
|
height: EMOJI_SIZE,
|
||||||
}
|
width: EMOJI_SIZE
|
||||||
|
},
|
||||||
|
emojiButton: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: EMOJI_BUTTON_SIZE,
|
||||||
|
width: EMOJI_BUTTON_SIZE
|
||||||
|
},
|
||||||
|
footerContainer: {
|
||||||
|
height: EMOJI_BUTTON_SIZE,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderTopWidth: 1
|
||||||
|
},
|
||||||
|
footerButtonsContainer: {
|
||||||
|
height: EMOJI_BUTTON_SIZE,
|
||||||
|
width: EMOJI_BUTTON_SIZE,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
emojiPickerContainer: { flex: 1 }
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
|
import { STATUS_COLORS } from '../../lib/constants';
|
||||||
import UnreadBadge from '../UnreadBadge';
|
import UnreadBadge from '../UnreadBadge';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -15,6 +16,8 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Badge = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />;
|
export const BadgeUnread = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />;
|
||||||
|
|
||||||
export default Badge;
|
export const BadgeWarn = (): React.ReactElement => (
|
||||||
|
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: STATUS_COLORS.disabled }]} />
|
||||||
|
);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
import { Header, HeaderBackground } from '@react-navigation/elements';
|
import { Header, HeaderBackground } from '@react-navigation/elements';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
|
@ -103,9 +104,10 @@ export const Badge = () => (
|
||||||
<HeaderExample
|
<HeaderExample
|
||||||
left={() => (
|
left={() => (
|
||||||
<HeaderButton.Container left>
|
<HeaderButton.Container left>
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} />
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} tunreadUser={[1]} />} />
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadUser={[1]} />} />
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} tunreadGroup={[1]} />} />
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadGroup={[1]} />} />
|
||||||
|
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
||||||
</HeaderButton.Container>
|
</HeaderButton.Container>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -114,20 +116,23 @@ export const Badge = () => (
|
||||||
|
|
||||||
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
||||||
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
|
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
|
||||||
|
<View style={{ flexDirection: 'column' }}>
|
||||||
<HeaderExample
|
<HeaderExample
|
||||||
left={() => (
|
left={() => (
|
||||||
<HeaderButton.Container left>
|
<HeaderButton.Container left>
|
||||||
|
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
||||||
<HeaderButton.Item iconName='threads' />
|
<HeaderButton.Item iconName='threads' />
|
||||||
</HeaderButton.Container>
|
</HeaderButton.Container>
|
||||||
)}
|
)}
|
||||||
right={() => (
|
right={() => (
|
||||||
<HeaderButton.Container>
|
<HeaderButton.Container>
|
||||||
<HeaderButton.Item title='Threads' />
|
<HeaderButton.Item title='Threads' />
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} />
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
|
||||||
</HeaderButton.Container>
|
</HeaderButton.Container>
|
||||||
)}
|
)}
|
||||||
colors={colors[theme]}
|
colors={colors[theme]}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
</ThemeContext.Provider>
|
</ThemeContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export { default as Container } from './HeaderButtonContainer';
|
export { default as Container } from './HeaderButtonContainer';
|
||||||
export { default as Item } from './HeaderButtonItem';
|
export { default as Item } from './HeaderButtonItem';
|
||||||
export { default as Badge } from './HeaderButtonItemBadge';
|
export * from './HeaderButtonItemBadge';
|
||||||
export * from './Common';
|
export * from './Common';
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { themes } from '../../lib/constants';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { ROW_HEIGHT } from '../RoomItem';
|
import { ROW_HEIGHT } from '../RoomItem';
|
||||||
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
|
||||||
import { useOrientation } from '../../dimensions';
|
import { useOrientation } from '../../dimensions';
|
||||||
import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions';
|
import { IApplicationState, ISubscription, SubscriptionType } from '../../definitions';
|
||||||
|
|
||||||
|
@ -98,12 +97,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie
|
||||||
prid
|
prid
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isMasterDetail) {
|
goRoom({ item, isMasterDetail, jumpToMessageId: _id, popToRoot: true });
|
||||||
Navigation.navigate('DrawerNavigator');
|
|
||||||
} else {
|
|
||||||
Navigation.navigate('RoomsListView');
|
|
||||||
}
|
|
||||||
goRoom({ item, isMasterDetail, jumpToMessageId: _id });
|
|
||||||
hideNotification();
|
hideNotification();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,6 +118,7 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifie
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
background={Touchable.SelectableBackgroundBorderless()}
|
background={Touchable.SelectableBackgroundBorderless()}
|
||||||
|
testID={`in-app-notification-${text}`}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<Avatar text={avatar} size={AVATAR_SIZE} type={type} rid={rid} style={styles.avatar} />
|
<Avatar text={avatar} size={AVATAR_SIZE} type={type} rid={rid} style={styles.avatar} />
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import React, { memo, useEffect } from 'react';
|
import React, { memo, useEffect } from 'react';
|
||||||
import { Easing, Notifier, NotifierRoot } from 'react-native-notifier';
|
import { Easing, Notifier, NotifierRoot } from 'react-native-notifier';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { dequal } from 'dequal';
|
|
||||||
|
|
||||||
import NotifierComponent, { INotifierComponent } from './NotifierComponent';
|
import NotifierComponent, { INotifierComponent } from './NotifierComponent';
|
||||||
import EventEmitter from '../../lib/methods/helpers/events';
|
import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { getActiveRoute } from '../../lib/methods/helpers/navigation';
|
import { getActiveRoute } from '../../lib/methods/helpers/navigation';
|
||||||
import { IApplicationState } from '../../definitions';
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
import { IRoom } from '../../reducers/room';
|
|
||||||
|
|
||||||
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
|
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
|
||||||
|
|
||||||
const InAppNotification = memo(
|
const InAppNotification = memo(() => {
|
||||||
({ rooms, appState }: { rooms: IRoom['rooms']; appState: string }) => {
|
const { appState, subscribedRoom } = useAppSelector(state => ({
|
||||||
|
subscribedRoom: state.room.subscribedRoom,
|
||||||
|
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
|
||||||
|
}));
|
||||||
|
|
||||||
const show = (notification: INotifierComponent['notification']) => {
|
const show = (notification: INotifierComponent['notification']) => {
|
||||||
if (appState !== 'foreground') {
|
if (appState !== 'foreground') {
|
||||||
return;
|
return;
|
||||||
|
@ -23,7 +24,7 @@ const InAppNotification = memo(
|
||||||
const state = Navigation.navigationRef.current?.getRootState();
|
const state = Navigation.navigationRef.current?.getRootState();
|
||||||
const route = getActiveRoute(state);
|
const route = getActiveRoute(state);
|
||||||
if (payload.rid) {
|
if (payload.rid) {
|
||||||
if (rooms.includes(payload.rid) || route?.name === 'JitsiMeetView') {
|
if (payload.rid === subscribedRoom || route?.name === 'JitsiMeetView') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Notifier.showNotification({
|
Notifier.showNotification({
|
||||||
|
@ -41,16 +42,9 @@ const InAppNotification = memo(
|
||||||
return () => {
|
return () => {
|
||||||
EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener);
|
EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener);
|
||||||
};
|
};
|
||||||
}, [rooms]);
|
}, [subscribedRoom, appState]);
|
||||||
|
|
||||||
return <NotifierRoot />;
|
return <NotifierRoot />;
|
||||||
},
|
|
||||||
(prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms)
|
|
||||||
);
|
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
|
||||||
rooms: state.room.rooms,
|
|
||||||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(InAppNotification);
|
export default InAppNotification;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { PADDING_HORIZONTAL } from './constants';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
paddingBottom: 12,
|
paddingVertical: 8,
|
||||||
paddingHorizontal: PADDING_HORIZONTAL
|
paddingHorizontal: PADDING_HORIZONTAL
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Header } from '.';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
marginVertical: 16
|
marginBottom: 16
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,6 @@ import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
export const styles = StyleSheet.create({
|
export const styles = StyleSheet.create({
|
||||||
contentContainerStyleFlatList: {
|
contentContainerStyleFlatList: {
|
||||||
paddingVertical: 32
|
paddingVertical: 16
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
export const BUTTON_HEIGHT = 48;
|
export const BUTTON_HEIGHT = 48;
|
||||||
export const SERVICE_HEIGHT = 58;
|
export const SERVICE_HEIGHT = 58;
|
||||||
export const BORDER_RADIUS = 2;
|
export const BORDER_RADIUS = 4;
|
||||||
export const SERVICES_COLLAPSED_HEIGHT = 174;
|
export const SERVICES_COLLAPSED_HEIGHT = 174;
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
|
|
|
@ -1,32 +1,29 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
import { TSupportedThemes, useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../CustomIcon';
|
import { CustomIcon } from '../CustomIcon';
|
||||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||||
|
import { addFrequentlyUsed } from '../../lib/methods';
|
||||||
|
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
import database from '../../lib/database';
|
|
||||||
import { useDimensions } from '../../dimensions';
|
import { useDimensions } from '../../dimensions';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions';
|
import { IEmoji, TAnyMessageModel } from '../../definitions';
|
||||||
import Touch from '../Touch';
|
import Touch from '../Touch';
|
||||||
|
|
||||||
type TItem = TFrequentlyUsedEmojiModel | string;
|
|
||||||
|
|
||||||
export interface IHeader {
|
export interface IHeader {
|
||||||
handleReaction: (emoji: TItem, message: TAnyMessageModel) => void;
|
handleReaction: (emoji: IEmoji | null, message: TAnyMessageModel) => void;
|
||||||
server: string;
|
|
||||||
message: TAnyMessageModel;
|
message: TAnyMessageModel;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TOnReaction = ({ emoji }: { emoji: TItem }) => void;
|
type TOnReaction = ({ emoji }: { emoji?: IEmoji }) => void;
|
||||||
|
|
||||||
interface THeaderItem {
|
interface THeaderItem {
|
||||||
item: TItem;
|
item: IEmoji;
|
||||||
onReaction: TOnReaction;
|
onReaction: TOnReaction;
|
||||||
server: string;
|
|
||||||
theme: TSupportedThemes;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,30 +61,19 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const keyExtractor = (item: TItem) => {
|
const HeaderItem = ({ item, onReaction, theme }: THeaderItem) => (
|
||||||
const emojiModel = item as TFrequentlyUsedEmojiModel;
|
|
||||||
return (emojiModel.id ? emojiModel.content : item) as string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
|
|
||||||
|
|
||||||
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
|
|
||||||
const emojiModel = item as TFrequentlyUsedEmojiModel;
|
|
||||||
const emoji = (emojiModel.id ? emojiModel.content : item) as string;
|
|
||||||
return (
|
|
||||||
<Touch
|
<Touch
|
||||||
testID={`message-actions-emoji-${emoji}`}
|
testID={`message-actions-emoji-${item}`}
|
||||||
onPress={() => onReaction({ emoji: `:${emoji}:` })}
|
onPress={() => onReaction({ emoji: item })}
|
||||||
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||||
>
|
>
|
||||||
{emojiModel?.isCustom ? (
|
{typeof item === 'string' ? (
|
||||||
<CustomEmoji style={styles.customEmoji} emoji={emojiModel} baseUrl={server} />
|
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${item}:`)}</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text>
|
<CustomEmoji style={styles.customEmoji} emoji={item} />
|
||||||
)}
|
)}
|
||||||
</Touch>
|
</Touch>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
|
const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
|
||||||
<Touch
|
<Touch
|
||||||
|
@ -99,49 +85,37 @@ const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
|
||||||
</Touch>
|
</Touch>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => {
|
const Header = React.memo(({ handleReaction, message, isMasterDetail }: IHeader) => {
|
||||||
const [items, setItems] = useState<TItem[]>([]);
|
|
||||||
const { width, height } = useDimensions();
|
const { width, height } = useDimensions();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(true);
|
||||||
// TODO: create custom hook to re-render based on screen size
|
|
||||||
const setEmojis = async () => {
|
|
||||||
try {
|
|
||||||
const db = database.active;
|
|
||||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
|
||||||
let freqEmojis: TItem[] = await freqEmojiCollection.query().fetch();
|
|
||||||
|
|
||||||
const isLandscape = width > height;
|
const isLandscape = width > height;
|
||||||
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
||||||
const quantity = size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1;
|
const quantity = Math.trunc(size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1);
|
||||||
|
|
||||||
freqEmojis = freqEmojis.concat(DEFAULT_EMOJIS).slice(0, quantity);
|
const onReaction: TOnReaction = ({ emoji }) => {
|
||||||
setItems(freqEmojis);
|
handleReaction(emoji || null, message);
|
||||||
} catch {
|
if (emoji) {
|
||||||
// Do nothing
|
addFrequentlyUsed(emoji);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const renderItem = ({ item }: { item: IEmoji }) => <HeaderItem item={item} onReaction={onReaction} theme={theme} />;
|
||||||
setEmojis();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message);
|
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: TItem }) => (
|
|
||||||
<HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
|
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
|
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={items}
|
data={frequentlyUsed.slice(0, quantity)}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
ListFooterComponent={renderFooter}
|
ListFooterComponent={renderFooter}
|
||||||
style={{ backgroundColor: themes[theme].focusedBackground }}
|
style={{ backgroundColor: themes[theme].focusedBackground }}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
scrollEnabled={false}
|
scrollEnabled={false}
|
||||||
horizontal
|
horizontal
|
||||||
|
|
|
@ -12,12 +12,12 @@ import { getMessageTranslation } from '../message/utils';
|
||||||
import { LISTENER } from '../Toast';
|
import { LISTENER } from '../Toast';
|
||||||
import EventEmitter from '../../lib/methods/helpers/events';
|
import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
||||||
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
|
import { TActionSheetOptionsItem, useActionSheet, ACTION_SHEET_ANIMATION_DURATION } from '../ActionSheet';
|
||||||
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
||||||
import events from '../../lib/methods/helpers/log/events';
|
import events from '../../lib/methods/helpers/log/events';
|
||||||
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||||
import { getPermalinkMessage } from '../../lib/methods';
|
import { getPermalinkMessage } from '../../lib/methods';
|
||||||
import { hasPermission } from '../../lib/methods/helpers';
|
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
||||||
import { Services } from '../../lib/services';
|
import { Services } from '../../lib/services';
|
||||||
|
|
||||||
export interface IMessageActionsProps {
|
export interface IMessageActionsProps {
|
||||||
|
@ -26,7 +26,7 @@ export interface IMessageActionsProps {
|
||||||
user: Pick<ILoggedUser, 'id'>;
|
user: Pick<ILoggedUser, 'id'>;
|
||||||
editInit: (message: TAnyMessageModel) => void;
|
editInit: (message: TAnyMessageModel) => void;
|
||||||
reactionInit: (message: TAnyMessageModel) => void;
|
reactionInit: (message: TAnyMessageModel) => void;
|
||||||
onReactionPress: (shortname: string, messageId: string) => void;
|
onReactionPress: (shortname: IEmoji, messageId: string) => void;
|
||||||
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
|
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
|
@ -37,11 +37,12 @@ export interface IMessageActionsProps {
|
||||||
Message_AllowPinning?: boolean;
|
Message_AllowPinning?: boolean;
|
||||||
Message_AllowStarring?: boolean;
|
Message_AllowStarring?: boolean;
|
||||||
Message_Read_Receipt_Store_Users?: boolean;
|
Message_Read_Receipt_Store_Users?: boolean;
|
||||||
server: string;
|
|
||||||
editMessagePermission?: string[];
|
editMessagePermission?: string[];
|
||||||
deleteMessagePermission?: string[];
|
deleteMessagePermission?: string[];
|
||||||
forceDeleteMessagePermission?: string[];
|
forceDeleteMessagePermission?: string[];
|
||||||
|
deleteOwnMessagePermission?: string[];
|
||||||
pinMessagePermission?: string[];
|
pinMessagePermission?: string[];
|
||||||
|
createDirectMessagePermission?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageActions {
|
export interface IMessageActions {
|
||||||
|
@ -60,7 +61,6 @@ const MessageActions = React.memo(
|
||||||
onReactionPress,
|
onReactionPress,
|
||||||
replyInit,
|
replyInit,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
server,
|
|
||||||
Message_AllowDeleting,
|
Message_AllowDeleting,
|
||||||
Message_AllowDeleting_BlockDeleteInMinutes,
|
Message_AllowDeleting_BlockDeleteInMinutes,
|
||||||
Message_AllowEditing,
|
Message_AllowEditing,
|
||||||
|
@ -72,7 +72,9 @@ const MessageActions = React.memo(
|
||||||
editMessagePermission,
|
editMessagePermission,
|
||||||
deleteMessagePermission,
|
deleteMessagePermission,
|
||||||
forceDeleteMessagePermission,
|
forceDeleteMessagePermission,
|
||||||
pinMessagePermission
|
deleteOwnMessagePermission,
|
||||||
|
pinMessagePermission,
|
||||||
|
createDirectMessagePermission
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
|
@ -80,19 +82,27 @@ const MessageActions = React.memo(
|
||||||
hasEditPermission: false,
|
hasEditPermission: false,
|
||||||
hasDeletePermission: false,
|
hasDeletePermission: false,
|
||||||
hasForceDeletePermission: false,
|
hasForceDeletePermission: false,
|
||||||
hasPinPermission: false
|
hasPinPermission: false,
|
||||||
|
hasDeleteOwnPermission: false
|
||||||
};
|
};
|
||||||
const { showActionSheet, hideActionSheet } = useActionSheet();
|
const { showActionSheet, hideActionSheet } = useActionSheet();
|
||||||
|
|
||||||
const getPermissions = async () => {
|
const getPermissions = async () => {
|
||||||
try {
|
try {
|
||||||
const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission];
|
const permission = [
|
||||||
|
editMessagePermission,
|
||||||
|
deleteMessagePermission,
|
||||||
|
forceDeleteMessagePermission,
|
||||||
|
pinMessagePermission,
|
||||||
|
deleteOwnMessagePermission
|
||||||
|
];
|
||||||
const result = await hasPermission(permission, room.rid);
|
const result = await hasPermission(permission, room.rid);
|
||||||
permissions = {
|
permissions = {
|
||||||
hasEditPermission: result[0],
|
hasEditPermission: result[0],
|
||||||
hasDeletePermission: result[1],
|
hasDeletePermission: result[1],
|
||||||
hasForceDeletePermission: result[2],
|
hasForceDeletePermission: result[2],
|
||||||
hasPinPermission: result[3]
|
hasPinPermission: result[3],
|
||||||
|
hasDeleteOwnPermission: result[4]
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
@ -134,7 +144,7 @@ const MessageActions = React.memo(
|
||||||
if (tmid === message.id) {
|
if (tmid === message.id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const deleteOwn = isOwn(message);
|
const deleteOwn = isOwn(message) && permissions.hasDeleteOwnPermission;
|
||||||
if (!(permissions.hasDeletePermission || (Message_AllowDeleting && deleteOwn) || permissions.hasForceDeletePermission)) {
|
if (!(permissions.hasDeletePermission || (Message_AllowDeleting && deleteOwn) || permissions.hasForceDeletePermission)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -237,6 +247,23 @@ const MessageActions = React.memo(
|
||||||
replyInit(message, false);
|
replyInit(message, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleReplyInDM = async (message: TAnyMessageModel) => {
|
||||||
|
if (message?.u?.username) {
|
||||||
|
const result = await Services.createDirectMessage(message.u.username);
|
||||||
|
if (result.success) {
|
||||||
|
const { room } = result;
|
||||||
|
const params = {
|
||||||
|
rid: room.rid,
|
||||||
|
name: getRoomTitle(room),
|
||||||
|
t: room.t,
|
||||||
|
roomUserId: getUidDirectMessage(room),
|
||||||
|
replyInDM: message
|
||||||
|
};
|
||||||
|
Navigation.replace('RoomView', params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleStar = async (message: TAnyMessageModel) => {
|
const handleStar = async (message: TAnyMessageModel) => {
|
||||||
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
||||||
try {
|
try {
|
||||||
|
@ -258,15 +285,13 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReaction: IHeader['handleReaction'] = (shortname, message) => {
|
const handleReaction: IHeader['handleReaction'] = (emoji, message) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_REACTION);
|
logEvent(events.ROOM_MSG_ACTION_REACTION);
|
||||||
if (shortname) {
|
if (emoji) {
|
||||||
// TODO: evaluate unification with IEmoji
|
onReactionPress(emoji, message.id);
|
||||||
onReactionPress(shortname as any, message.id);
|
|
||||||
} else {
|
} else {
|
||||||
reactionInit(message);
|
setTimeout(() => reactionInit(message), ACTION_SHEET_ANIMATION_DURATION);
|
||||||
}
|
}
|
||||||
// close actionSheet when click at header
|
|
||||||
hideActionSheet();
|
hideActionSheet();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -327,21 +352,11 @@ const MessageActions = React.memo(
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOptions = (message: TAnyMessageModel) => {
|
const getOptions = (message: TAnyMessageModel) => {
|
||||||
let options: TActionSheetOptionsItem[] = [];
|
const options: TActionSheetOptionsItem[] = [];
|
||||||
|
const videoConfBlock = message.t === 'videoconf';
|
||||||
// Reply
|
|
||||||
if (!isReadOnly) {
|
|
||||||
options = [
|
|
||||||
{
|
|
||||||
title: I18n.t('Reply_in_Thread'),
|
|
||||||
icon: 'threads',
|
|
||||||
onPress: () => handleReply(message)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quote
|
// Quote
|
||||||
if (!isReadOnly) {
|
if (!isReadOnly && !videoConfBlock) {
|
||||||
options.push({
|
options.push({
|
||||||
title: I18n.t('Quote'),
|
title: I18n.t('Quote'),
|
||||||
icon: 'quote',
|
icon: 'quote',
|
||||||
|
@ -349,21 +364,23 @@ const MessageActions = React.memo(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit
|
// Reply
|
||||||
if (allowEdit(message)) {
|
if (!isReadOnly && !tmid) {
|
||||||
options.push({
|
options.push({
|
||||||
title: I18n.t('Edit'),
|
title: I18n.t('Reply_in_Thread'),
|
||||||
icon: 'edit',
|
icon: 'threads',
|
||||||
onPress: () => handleEdit(message)
|
onPress: () => handleReply(message)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permalink
|
// Reply in DM
|
||||||
|
if (room.t !== 'd' && room.t !== 'l' && createDirectMessagePermission && !videoConfBlock) {
|
||||||
options.push({
|
options.push({
|
||||||
title: I18n.t('Permalink'),
|
title: I18n.t('Reply_in_direct_message'),
|
||||||
icon: 'link',
|
icon: 'arrow-back',
|
||||||
onPress: () => handlePermalink(message)
|
onPress: () => handleReplyInDM(message)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create Discussion
|
// Create Discussion
|
||||||
options.push({
|
options.push({
|
||||||
|
@ -372,21 +389,21 @@ const MessageActions = React.memo(
|
||||||
onPress: () => handleCreateDiscussion(message)
|
onPress: () => handleCreateDiscussion(message)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark as unread
|
// Permalink
|
||||||
if (message.u && message.u._id !== user.id) {
|
|
||||||
options.push({
|
options.push({
|
||||||
title: I18n.t('Mark_unread'),
|
title: I18n.t('Get_link'),
|
||||||
icon: 'flag',
|
icon: 'link',
|
||||||
onPress: () => handleUnread(message)
|
onPress: () => handlePermalink(message)
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Copy
|
// Copy
|
||||||
|
if (!videoConfBlock) {
|
||||||
options.push({
|
options.push({
|
||||||
title: I18n.t('Copy'),
|
title: I18n.t('Copy'),
|
||||||
icon: 'copy',
|
icon: 'copy',
|
||||||
onPress: () => handleCopy(message)
|
onPress: () => handleCopy(message)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Share
|
// Share
|
||||||
options.push({
|
options.push({
|
||||||
|
@ -395,8 +412,26 @@ const MessageActions = React.memo(
|
||||||
onPress: () => handleShare(message)
|
onPress: () => handleShare(message)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Edit
|
||||||
|
if (allowEdit(message) && !videoConfBlock) {
|
||||||
|
options.push({
|
||||||
|
title: I18n.t('Edit'),
|
||||||
|
icon: 'edit',
|
||||||
|
onPress: () => handleEdit(message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pin
|
||||||
|
if (Message_AllowPinning && permissions?.hasPinPermission && !videoConfBlock) {
|
||||||
|
options.push({
|
||||||
|
title: I18n.t(message.pinned ? 'Unpin' : 'Pin'),
|
||||||
|
icon: 'pin',
|
||||||
|
onPress: () => handlePin(message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Star
|
// Star
|
||||||
if (Message_AllowStarring) {
|
if (Message_AllowStarring && !videoConfBlock) {
|
||||||
options.push({
|
options.push({
|
||||||
title: I18n.t(message.starred ? 'Unstar' : 'Star'),
|
title: I18n.t(message.starred ? 'Unstar' : 'Star'),
|
||||||
icon: message.starred ? 'star-filled' : 'star',
|
icon: message.starred ? 'star-filled' : 'star',
|
||||||
|
@ -404,12 +439,12 @@ const MessageActions = React.memo(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pin
|
// Mark as unread
|
||||||
if (Message_AllowPinning && permissions?.hasPinPermission) {
|
if (message.u && message.u._id !== user.id) {
|
||||||
options.push({
|
options.push({
|
||||||
title: I18n.t(message.pinned ? 'Unpin' : 'Pin'),
|
title: I18n.t('Mark_unread'),
|
||||||
icon: 'pin',
|
icon: 'flag',
|
||||||
onPress: () => handlePin(message)
|
onPress: () => handleUnread(message)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,7 +495,7 @@ const MessageActions = React.memo(
|
||||||
headerHeight: HEADER_HEIGHT,
|
headerHeight: HEADER_HEIGHT,
|
||||||
customHeader:
|
customHeader:
|
||||||
!isReadOnly || room.reactWhenReadOnly ? (
|
!isReadOnly || room.reactWhenReadOnly ? (
|
||||||
<Header server={server} handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />
|
<Header handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />
|
||||||
) : null
|
) : null
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -483,8 +518,10 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
editMessagePermission: state.permissions['edit-message'],
|
editMessagePermission: state.permissions['edit-message'],
|
||||||
deleteMessagePermission: state.permissions['delete-message'],
|
deleteMessagePermission: state.permissions['delete-message'],
|
||||||
|
deleteOwnMessagePermission: state.permissions['delete-own-message'],
|
||||||
forceDeleteMessagePermission: state.permissions['force-delete-message'],
|
forceDeleteMessagePermission: state.permissions['force-delete-message'],
|
||||||
pinMessagePermission: state.permissions['pin-message']
|
pinMessagePermission: state.permissions['pin-message'],
|
||||||
|
createDirectMessagePermission: state.permissions['create-d']
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);
|
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);
|
||||||
|
|
|
@ -6,22 +6,31 @@ import { Provider } from 'react-redux';
|
||||||
import store from '../../lib/store';
|
import store from '../../lib/store';
|
||||||
import EmojiPicker from '../EmojiPicker';
|
import EmojiPicker from '../EmojiPicker';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { ThemeContext, TSupportedThemes } from '../../theme';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { EventTypes } from '../EmojiPicker/interfaces';
|
||||||
|
import { IEmoji } from '../../definitions';
|
||||||
|
import { colors } from '../../lib/constants';
|
||||||
|
|
||||||
const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => {
|
const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => {
|
||||||
const onEmojiSelected = (emoji: string) => {
|
const onItemClicked = (eventType: EventTypes, emoji?: IEmoji) => {
|
||||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
KeyboardRegistry.onItemSelected('EmojiKeyboard', { eventType, emoji });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
<ThemeContext.Provider
|
||||||
|
value={{
|
||||||
|
theme,
|
||||||
|
colors: colors[theme]
|
||||||
|
}}
|
||||||
|
>
|
||||||
<View
|
<View
|
||||||
style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]}
|
style={[styles.emojiKeyboardContainer, { borderTopColor: colors[theme].borderColor }]}
|
||||||
testID='messagebox-keyboard-emoji'
|
testID='messagebox-keyboard-emoji'
|
||||||
>
|
>
|
||||||
<EmojiPicker onEmojiSelected={onEmojiSelected} theme={theme} />
|
<EmojiPicker onItemClicked={onItemClicked} isEmojiKeyboard={true} />
|
||||||
</View>
|
</View>
|
||||||
|
</ThemeContext.Provider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, Text, Pressable, FlatList, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import { CustomIcon } from '../CustomIcon';
|
||||||
|
import { IEmoji } from '../../definitions';
|
||||||
|
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||||
|
import { addFrequentlyUsed, searchEmojis } from '../../lib/methods';
|
||||||
|
import { useDebounce } from '../../lib/methods/helpers';
|
||||||
|
import sharedStyles from '../../views/Styles';
|
||||||
|
import { PressableEmoji } from '../EmojiPicker/PressableEmoji';
|
||||||
|
import { EmojiSearch } from '../EmojiPicker/EmojiSearch';
|
||||||
|
import { EMOJI_BUTTON_SIZE } from '../EmojiPicker/styles';
|
||||||
|
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
|
|
||||||
|
const BUTTON_HIT_SLOP = { top: 4, right: 4, bottom: 4, left: 4 };
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
listContainer: {
|
||||||
|
height: EMOJI_BUTTON_SIZE,
|
||||||
|
margin: 8,
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
borderTopWidth: 1
|
||||||
|
},
|
||||||
|
searchContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
marginBottom: 12
|
||||||
|
},
|
||||||
|
backButton: {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
emptyContainer: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 16
|
||||||
|
},
|
||||||
|
inputContainer: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IEmojiSearchBarProps {
|
||||||
|
openEmoji: () => void;
|
||||||
|
closeEmoji: () => void;
|
||||||
|
onEmojiSelected: (emoji: IEmoji) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmojiSearchBar = ({ openEmoji, closeEmoji, onEmojiSelected }: IEmojiSearchBarProps): React.ReactElement => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
|
const { frequentlyUsed } = useFrequentlyUsedEmoji(true);
|
||||||
|
const [emojis, setEmojis] = useState<IEmoji[]>([]);
|
||||||
|
|
||||||
|
const handleTextChange = useDebounce(async (text: string) => {
|
||||||
|
logEvent(events.MB_SB_EMOJI_SEARCH);
|
||||||
|
setSearchText(text);
|
||||||
|
const result = await searchEmojis(text);
|
||||||
|
setEmojis(result);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
const handleEmojiSelected = (emoji: IEmoji) => {
|
||||||
|
logEvent(events.MB_SB_EMOJI_SELECTED);
|
||||||
|
onEmojiSelected(emoji);
|
||||||
|
addFrequentlyUsed(emoji);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={handleEmojiSelected} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { borderTopColor: colors.borderColor, backgroundColor: colors.messageboxBackground }]}>
|
||||||
|
<FlatList
|
||||||
|
horizontal
|
||||||
|
data={searchText ? emojis : frequentlyUsed}
|
||||||
|
renderItem={renderItem}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
ListEmptyComponent={() => (
|
||||||
|
<View style={styles.emptyContainer} testID='no-results-found'>
|
||||||
|
<Text style={[styles.emptyText, { color: colors.auxiliaryText }]}>{I18n.t('No_results_found')}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||||
|
contentContainerStyle={styles.listContainer}
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
|
/>
|
||||||
|
<View style={styles.searchContainer}>
|
||||||
|
<Pressable
|
||||||
|
style={({ pressed }: { pressed: boolean }) => [styles.backButton, { opacity: pressed ? 0.7 : 1 }]}
|
||||||
|
onPress={openEmoji}
|
||||||
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
testID='openback-emoji-keyboard'
|
||||||
|
>
|
||||||
|
<CustomIcon name='chevron-left' size={24} color={colors.auxiliaryTintColor} />
|
||||||
|
</Pressable>
|
||||||
|
<View style={styles.inputContainer}>
|
||||||
|
<EmojiSearch onBlur={closeEmoji} onChangeText={handleTextChange} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmojiSearchBar;
|
|
@ -1,10 +1,9 @@
|
||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
import { IEmoji } from '../../../definitions/IEmoji';
|
import { IEmoji } from '../../../definitions/IEmoji';
|
||||||
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
|
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
|
||||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||||
import MessageboxContext from '../Context';
|
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
|
|
||||||
interface IMessageBoxMentionEmoji {
|
interface IMessageBoxMentionEmoji {
|
||||||
|
@ -12,13 +11,10 @@ interface IMessageBoxMentionEmoji {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
|
const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
|
||||||
const context = useContext(MessageboxContext);
|
if (typeof item === 'string') {
|
||||||
const { baseUrl } = context;
|
|
||||||
|
|
||||||
if (item.name) {
|
|
||||||
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} baseUrl={baseUrl} />;
|
|
||||||
}
|
|
||||||
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
|
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
|
||||||
|
}
|
||||||
|
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MentionEmoji;
|
export default MentionEmoji;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Text, TouchableOpacity } from 'react-native';
|
import { Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import { IEmoji } from '../../../definitions/IEmoji';
|
import { IEmoji } from '../../../definitions/IEmoji';
|
||||||
|
@ -37,7 +37,9 @@ const MentionItemContent = React.memo(({ trackingType, item }: IMessageBoxMentio
|
||||||
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
|
<View style={[styles.slash, { backgroundColor: themes[theme].borderColor }]}>
|
||||||
|
<Text style={{ color: themes[theme].tintColor }}>/</Text>
|
||||||
|
</View>
|
||||||
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
|
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,7 +29,8 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
username: {
|
username: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
...sharedStyles.textMedium
|
...sharedStyles.textMedium,
|
||||||
|
flexShrink: 1
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
@ -67,7 +68,7 @@ const ReplyPreview = React.memo(
|
||||||
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
|
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
|
||||||
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
|
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={[styles.username, { color: themes[theme].tintColor }]}>
|
<Text numberOfLines={1} style={[styles.username, { color: themes[theme].tintColor }]}>
|
||||||
{useRealName ? message.u?.name : message.u?.username}
|
{useRealName ? message.u?.name : message.u?.username}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||||
|
|
|
@ -4,5 +4,6 @@ export const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
|
||||||
export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
|
export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
|
||||||
export const MENTIONS_TRACKING_TYPE_CANNED = '!';
|
export const MENTIONS_TRACKING_TYPE_CANNED = '!';
|
||||||
export const MENTIONS_COUNT_TO_DISPLAY = 4;
|
export const MENTIONS_COUNT_TO_DISPLAY = 4;
|
||||||
|
export const MAX_EMOJIS_TO_DISPLAY = 20;
|
||||||
|
|
||||||
export const TIMEOUT_CLOSE_EMOJI = 300;
|
export const TIMEOUT_CLOSE_EMOJI = 300;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
|
import { Alert, Keyboard, NativeModules, Text, View, BackHandler } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
|
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
|
||||||
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
|
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
|
||||||
|
@ -13,12 +13,11 @@ import { TextInput, IThemedTextInput } from '../TextInput';
|
||||||
import { userTyping as userTypingAction } from '../../actions/room';
|
import { userTyping as userTypingAction } from '../../actions/room';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { emojis } from '../EmojiPicker/emojis';
|
|
||||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||||
import RecordAudio from './RecordAudio';
|
import RecordAudio from './RecordAudio';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import ReplyPreview from './ReplyPreview';
|
import ReplyPreview from './ReplyPreview';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes, emojis } from '../../lib/constants';
|
||||||
import LeftButtons from './LeftButtons';
|
import LeftButtons from './LeftButtons';
|
||||||
import RightButtons from './RightButtons';
|
import RightButtons from './RightButtons';
|
||||||
import { canUploadFile } from '../../lib/methods/helpers/media';
|
import { canUploadFile } from '../../lib/methods/helpers/media';
|
||||||
|
@ -51,14 +50,18 @@ import {
|
||||||
TGetCustomEmoji,
|
TGetCustomEmoji,
|
||||||
TSubscriptionModel,
|
TSubscriptionModel,
|
||||||
TThreadModel,
|
TThreadModel,
|
||||||
IMessage
|
IMessage,
|
||||||
|
IEmoji
|
||||||
} from '../../definitions';
|
} from '../../definitions';
|
||||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
|
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
|
||||||
import { hasPermission, debounce, isAndroid, isIOS, isTablet } from '../../lib/methods/helpers';
|
import { hasPermission, debounce, isAndroid, isIOS, isTablet, compareServerVersion } from '../../lib/methods/helpers';
|
||||||
import { Services } from '../../lib/services';
|
import { Services } from '../../lib/services';
|
||||||
import { TSupportedThemes } from '../../theme';
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { ChatsStackParamList } from '../../stacks/types';
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
|
import { EventTypes } from '../EmojiPicker/interfaces';
|
||||||
|
import EmojiSearchbar from './EmojiSearchbar';
|
||||||
|
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||||
|
|
||||||
require('./EmojiKeyboard');
|
require('./EmojiKeyboard');
|
||||||
|
|
||||||
|
@ -111,8 +114,8 @@ export interface IMessageBoxProps extends IBaseScreen<ChatsStackParamList & Mast
|
||||||
isActionsEnabled: boolean;
|
isActionsEnabled: boolean;
|
||||||
usedCannedResponse: string;
|
usedCannedResponse: string;
|
||||||
uploadFilePermission: string[];
|
uploadFilePermission: string[];
|
||||||
serverVersion: string;
|
|
||||||
goToCannedResponses: () => void | null;
|
goToCannedResponses: () => void | null;
|
||||||
|
serverVersion: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageBoxState {
|
interface IMessageBoxState {
|
||||||
|
@ -129,6 +132,7 @@ interface IMessageBoxState {
|
||||||
tshow: boolean;
|
tshow: boolean;
|
||||||
mentionLoading: boolean;
|
mentionLoading: boolean;
|
||||||
permissionToUpload: boolean;
|
permissionToUpload: boolean;
|
||||||
|
showEmojiSearchbar: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
|
@ -181,9 +185,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
commandPreview: [],
|
commandPreview: [],
|
||||||
showCommandPreview: false,
|
showCommandPreview: false,
|
||||||
command: {},
|
command: {},
|
||||||
tshow: false,
|
tshow: this.sendThreadToChannel,
|
||||||
mentionLoading: false,
|
mentionLoading: false,
|
||||||
permissionToUpload: true
|
permissionToUpload: true,
|
||||||
|
showEmojiSearchbar: false
|
||||||
};
|
};
|
||||||
this.text = '';
|
this.text = '';
|
||||||
this.selection = { start: 0, end: 0 };
|
this.selection = { start: 0, end: 0 };
|
||||||
|
@ -209,11 +214,30 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
...videoPickerConfig,
|
...videoPickerConfig,
|
||||||
...libPickerLabels
|
...libPickerLabels
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||||
|
}
|
||||||
|
|
||||||
|
get sendThreadToChannel() {
|
||||||
|
const { user, serverVersion, tmid } = this.props;
|
||||||
|
if (tmid && compareServerVersion(serverVersion, 'lowerThan', '5.0.0')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tmid && user.alsoSendThreadToChannel === 'default') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (user.alsoSendThreadToChannel === 'always') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (user.alsoSendThreadToChannel === 'never') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props;
|
const { rid, tmid, navigation, sharing, usedCannedResponse } = this.props;
|
||||||
let msg;
|
let msg;
|
||||||
try {
|
try {
|
||||||
const threadsCollection = db.get('threads');
|
const threadsCollection = db.get('threads');
|
||||||
|
@ -248,7 +272,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
|
EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMasterDetail && usedCannedResponse) {
|
if (usedCannedResponse) {
|
||||||
this.onChangeText(usedCannedResponse);
|
this.onChangeText(usedCannedResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +302,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (usedCannedResponse !== nextProps.usedCannedResponse) {
|
if (usedCannedResponse !== nextProps.usedCannedResponse) {
|
||||||
this.onChangeText(nextProps.usedCannedResponse ?? '');
|
this.onChangeText(nextProps.usedCannedResponse ?? '');
|
||||||
}
|
}
|
||||||
if (sharing) {
|
if (sharing && !replying) {
|
||||||
this.setInput(nextProps.message.msg ?? '');
|
this.setInput(nextProps.message.msg ?? '');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -309,7 +333,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
tshow,
|
tshow,
|
||||||
mentionLoading,
|
mentionLoading,
|
||||||
trackingType,
|
trackingType,
|
||||||
permissionToUpload
|
permissionToUpload,
|
||||||
|
showEmojiSearchbar
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -329,6 +354,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
|
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (nextState.showEmojiSearchbar !== showEmojiSearchbar) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!isFocused()) {
|
if (!isFocused()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -381,7 +409,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: IMessageBoxProps) {
|
componentDidUpdate(prevProps: IMessageBoxProps) {
|
||||||
const { uploadFilePermission, goToCannedResponses } = this.props;
|
const { uploadFilePermission, goToCannedResponses, replyWithMention, threadsEnabled } = this.props;
|
||||||
|
if (prevProps.replyWithMention !== replyWithMention) {
|
||||||
|
if (threadsEnabled && replyWithMention) {
|
||||||
|
this.setState({ tshow: this.sendThreadToChannel });
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) {
|
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) {
|
||||||
this.setOptions();
|
this.setOptions();
|
||||||
}
|
}
|
||||||
|
@ -416,6 +449,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (isTablet) {
|
if (isTablet) {
|
||||||
EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
|
EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
|
||||||
}
|
}
|
||||||
|
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptions = async () => {
|
setOptions = async () => {
|
||||||
|
@ -497,7 +531,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
onKeyboardResigned = () => {
|
onKeyboardResigned = () => {
|
||||||
|
const { showEmojiSearchbar } = this.state;
|
||||||
|
if (!showEmojiSearchbar) {
|
||||||
this.closeEmoji();
|
this.closeEmoji();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onPressMention = (item: any) => {
|
onPressMention = (item: any) => {
|
||||||
|
@ -555,18 +592,50 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onEmojiSelected = (keyboardId: string, params: { emoji: string }) => {
|
onKeyboardItemSelected = (keyboardId: string, params: { eventType: EventTypes; emoji: IEmoji }) => {
|
||||||
|
const { eventType, emoji } = params;
|
||||||
const { text } = this;
|
const { text } = this;
|
||||||
const { emoji } = params;
|
|
||||||
let newText = '';
|
let newText = '';
|
||||||
|
|
||||||
// if messagebox has an active cursor
|
// if messagebox has an active cursor
|
||||||
const { start, end } = this.selection;
|
const { start, end } = this.selection;
|
||||||
const cursor = Math.max(start, end);
|
const cursor = Math.max(start, end);
|
||||||
newText = `${text.substr(0, cursor)}${emoji}${text.substr(cursor)}`;
|
let newCursor;
|
||||||
const newCursor = cursor + emoji.length;
|
|
||||||
|
switch (eventType) {
|
||||||
|
case EventTypes.BACKSPACE_PRESSED:
|
||||||
|
logEvent(events.MB_BACKSPACE);
|
||||||
|
const emojiRegex = /\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]/;
|
||||||
|
let charsToRemove = 1;
|
||||||
|
const lastEmoji = text.substr(cursor > 0 ? cursor - 2 : text.length - 2, cursor > 0 ? cursor : text.length);
|
||||||
|
// Check if last character is an emoji
|
||||||
|
if (emojiRegex.test(lastEmoji)) charsToRemove = 2;
|
||||||
|
newText =
|
||||||
|
text.substr(0, (cursor > 0 ? cursor : text.length) - charsToRemove) + text.substr(cursor > 0 ? cursor : text.length);
|
||||||
|
newCursor = cursor - charsToRemove;
|
||||||
|
this.setInput(newText, { start: newCursor, end: newCursor });
|
||||||
|
this.setShowSend(newText !== '');
|
||||||
|
break;
|
||||||
|
case EventTypes.EMOJI_PRESSED:
|
||||||
|
logEvent(events.MB_EMOJI_SELECTED);
|
||||||
|
let emojiText = '';
|
||||||
|
if (typeof emoji === 'string') {
|
||||||
|
const shortname = `:${emoji}:`;
|
||||||
|
emojiText = shortnameToUnicode(shortname);
|
||||||
|
} else {
|
||||||
|
emojiText = `:${emoji.name}:`;
|
||||||
|
}
|
||||||
|
newText = `${text.substr(0, cursor)}${emojiText}${text.substr(cursor)}`;
|
||||||
|
newCursor = cursor + emojiText.length;
|
||||||
this.setInput(newText, { start: newCursor, end: newCursor });
|
this.setInput(newText, { start: newCursor, end: newCursor });
|
||||||
this.setShowSend(true);
|
this.setShowSend(true);
|
||||||
|
break;
|
||||||
|
case EventTypes.SEARCH_PRESSED:
|
||||||
|
logEvent(events.MB_EMOJI_SEARCH_PRESSED);
|
||||||
|
this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: true });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getPermalink = async (message: any) => {
|
getPermalink = async (message: any) => {
|
||||||
|
@ -589,7 +658,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
getUsers = debounce(async (keyword: any) => {
|
getUsers = debounce(async (keyword: any) => {
|
||||||
let res = await search({ text: keyword, filterRooms: false, filterUsers: true });
|
const { rid } = this.props;
|
||||||
|
let res = await search({ text: keyword, filterRooms: false, filterUsers: true, rid });
|
||||||
res = [...this.getFixedMentions(keyword), ...res];
|
res = [...this.getFixedMentions(keyword), ...res];
|
||||||
this.setState({ mentions: res, mentionLoading: false });
|
this.setState({ mentions: res, mentionLoading: false });
|
||||||
}, 300);
|
}, 300);
|
||||||
|
@ -599,16 +669,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
this.setState({ mentions: res, mentionLoading: false });
|
this.setState({ mentions: res, mentionLoading: false });
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
getEmojis = debounce(async (keyword: any) => {
|
getCustomEmojis = async (keyword: any, count: number) => {
|
||||||
const db = database.active;
|
|
||||||
const customEmojisCollection = db.get('custom_emojis');
|
|
||||||
const likeString = sanitizeLikeString(keyword);
|
const likeString = sanitizeLikeString(keyword);
|
||||||
const whereClause = [];
|
const whereClause = [];
|
||||||
if (likeString) {
|
if (likeString) {
|
||||||
whereClause.push(Q.where('name', Q.like(`${likeString}%`)));
|
whereClause.push(Q.where('name', Q.like(`${likeString}%`)));
|
||||||
}
|
}
|
||||||
let customEmojis = await customEmojisCollection.query(...whereClause).fetch();
|
const db = database.active;
|
||||||
customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
const customEmojisCollection = db.get('custom_emojis');
|
||||||
|
const customEmojis = await (await customEmojisCollection.query(...whereClause).fetch()).slice(0, count);
|
||||||
|
return customEmojis;
|
||||||
|
};
|
||||||
|
|
||||||
|
getEmojis = debounce(async (keyword: any) => {
|
||||||
|
const customEmojis = await this.getCustomEmojis(keyword, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
this.setState({ mentions: mergedEmojis || [], mentionLoading: false });
|
this.setState({ mentions: mergedEmojis || [], mentionLoading: false });
|
||||||
|
@ -687,9 +761,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
clearInput = () => {
|
clearInput = () => {
|
||||||
|
const { tshow } = this.state;
|
||||||
|
const { user, serverVersion } = this.props;
|
||||||
this.setInput('');
|
this.setInput('');
|
||||||
this.setShowSend(false);
|
this.setShowSend(false);
|
||||||
|
if (compareServerVersion(serverVersion, 'lowerThan', '5.0.0') || (tshow && user.alsoSendThreadToChannel === 'default')) {
|
||||||
this.setState({ tshow: false });
|
this.setState({ tshow: false });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
canUploadFile = (file: any) => {
|
canUploadFile = (file: any) => {
|
||||||
|
@ -779,14 +857,21 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
openShareView = (attachments: any) => {
|
openShareView = (attachments: any) => {
|
||||||
const { message, replyCancel, replyWithMention } = this.props;
|
const { message, replyCancel, replyWithMention, replying } = this.props;
|
||||||
// Start a thread with an attachment
|
// Start a thread with an attachment
|
||||||
let value: TThreadModel | IMessage = this.thread;
|
let value: TThreadModel | IMessage = this.thread;
|
||||||
if (replyWithMention) {
|
if (replyWithMention) {
|
||||||
value = message;
|
value = message;
|
||||||
replyCancel();
|
replyCancel();
|
||||||
}
|
}
|
||||||
Navigation.navigate('ShareView', { room: this.room, thread: value, attachments });
|
Navigation.navigate('ShareView', {
|
||||||
|
room: this.room,
|
||||||
|
thread: value,
|
||||||
|
attachments,
|
||||||
|
replying,
|
||||||
|
replyingMessage: message,
|
||||||
|
closeReply: replyCancel
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
createDiscussion = () => {
|
createDiscussion = () => {
|
||||||
|
@ -855,7 +940,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
|
|
||||||
openEmoji = () => {
|
openEmoji = () => {
|
||||||
logEvent(events.ROOM_OPEN_EMOJI);
|
logEvent(events.ROOM_OPEN_EMOJI);
|
||||||
this.setState({ showEmojiKeyboard: true });
|
this.setState({ showEmojiKeyboard: true, showEmojiSearchbar: false });
|
||||||
|
this.stopTrackingMention();
|
||||||
};
|
};
|
||||||
|
|
||||||
recordingCallback = (recording: any) => {
|
recordingCallback = (recording: any) => {
|
||||||
|
@ -877,7 +963,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
closeEmoji = () => {
|
closeEmoji = () => {
|
||||||
this.setState({ showEmojiKeyboard: false });
|
this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
closeEmojiKeyboardAndFocus = () => {
|
||||||
|
logEvent(events.ROOM_CLOSE_EMOJI);
|
||||||
|
this.closeEmoji();
|
||||||
|
this.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
closeEmojiAndAction = (action?: Function, params?: any) => {
|
closeEmojiAndAction = (action?: Function, params?: any) => {
|
||||||
|
@ -900,7 +992,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
|
|
||||||
this.clearInput();
|
this.clearInput();
|
||||||
this.debouncedOnChangeText.stop();
|
this.debouncedOnChangeText.stop();
|
||||||
this.closeEmoji();
|
this.closeEmojiKeyboardAndFocus();
|
||||||
this.stopTrackingMention();
|
this.stopTrackingMention();
|
||||||
this.handleTyping(false);
|
this.handleTyping(false);
|
||||||
if (message.trim() === '' && !showSend) {
|
if (message.trim() === '' && !showSend) {
|
||||||
|
@ -957,7 +1049,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
|
|
||||||
// Legacy reply or quote (quote is a reply without mention)
|
// Legacy reply or quote (quote is a reply without mention)
|
||||||
} else {
|
} else {
|
||||||
const { user, roomType } = this.props;
|
const msg = await this.formatReplyMessage(replyingMessage, message);
|
||||||
|
onSubmit(msg);
|
||||||
|
}
|
||||||
|
replyCancel();
|
||||||
|
|
||||||
|
// Normal message
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
onSubmit(message, undefined, tmid ? tshow : false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
formatReplyMessage = async (replyingMessage: IMessage, message = '') => {
|
||||||
|
const { user, roomType, replyWithMention, serverVersion } = this.props;
|
||||||
const permalink = await this.getPermalink(replyingMessage);
|
const permalink = await this.getPermalink(replyingMessage);
|
||||||
let msg = `[ ](${permalink}) `;
|
let msg = `[ ](${permalink}) `;
|
||||||
|
|
||||||
|
@ -966,16 +1071,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
msg += `@${replyingMessage?.u?.username} `;
|
msg += `@${replyingMessage?.u?.username} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = `${msg} ${message}`;
|
const connectionString = compareServerVersion(serverVersion, 'lowerThan', '5.0.0') ? ' ' : '\n';
|
||||||
onSubmit(msg);
|
return `${msg}${connectionString}${message}`;
|
||||||
}
|
|
||||||
replyCancel();
|
|
||||||
|
|
||||||
// Normal message
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
onSubmit(message, undefined, tshow);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMentions = (keyword: any, type: string) => {
|
updateMentions = (keyword: any, type: string) => {
|
||||||
|
@ -1044,7 +1141,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
onPress={this.onPressSendToChannel}
|
onPress={this.onPressSendToChannel}
|
||||||
testID='messagebox-send-to-channel'
|
testID='messagebox-send-to-channel'
|
||||||
>
|
>
|
||||||
<CustomIcon name={tshow ? 'checkbox-checked' : 'checkbox-unchecked'} size={24} color={themes[theme].auxiliaryText} />
|
<CustomIcon
|
||||||
|
testID={tshow ? 'send-to-channel-checked' : 'send-to-channel-unchecked'}
|
||||||
|
name={tshow ? 'checkbox-checked' : 'checkbox-unchecked'}
|
||||||
|
size={24}
|
||||||
|
color={themes[theme].auxiliaryText}
|
||||||
|
/>
|
||||||
<Text style={[styles.sendToChannelText, { color: themes[theme].auxiliaryText }]}>
|
<Text style={[styles.sendToChannelText, { color: themes[theme].auxiliaryText }]}>
|
||||||
{I18n.t('Messagebox_Send_to_channel')}
|
{I18n.t('Messagebox_Send_to_channel')}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -1052,10 +1154,34 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderEmojiSearchbar = () => {
|
||||||
|
const { showEmojiSearchbar } = this.state;
|
||||||
|
|
||||||
|
return showEmojiSearchbar ? (
|
||||||
|
<EmojiSearchbar
|
||||||
|
openEmoji={this.openEmoji}
|
||||||
|
closeEmoji={this.closeEmoji}
|
||||||
|
onEmojiSelected={(emoji: IEmoji) => {
|
||||||
|
this.onKeyboardItemSelected('EmojiKeyboard', { eventType: EventTypes.EMOJI_PRESSED, emoji });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleBackPress = () => {
|
||||||
|
const { showEmojiSearchbar } = this.state;
|
||||||
|
if (showEmojiSearchbar) {
|
||||||
|
this.setState({ showEmojiSearchbar: false });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
renderContent = () => {
|
renderContent = () => {
|
||||||
const {
|
const {
|
||||||
recording,
|
recording,
|
||||||
showEmojiKeyboard,
|
showEmojiKeyboard,
|
||||||
|
showEmojiSearchbar,
|
||||||
showSend,
|
showSend,
|
||||||
mentions,
|
mentions,
|
||||||
trackingType,
|
trackingType,
|
||||||
|
@ -1118,11 +1244,11 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
const textInputAndButtons = !recording ? (
|
const textInputAndButtons = !recording ? (
|
||||||
<>
|
<>
|
||||||
<LeftButtons
|
<LeftButtons
|
||||||
showEmojiKeyboard={showEmojiKeyboard}
|
showEmojiKeyboard={showEmojiKeyboard || showEmojiSearchbar}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
editCancel={this.editCancel}
|
editCancel={this.editCancel}
|
||||||
openEmoji={this.openEmoji}
|
openEmoji={this.openEmoji}
|
||||||
closeEmoji={this.closeEmoji}
|
closeEmoji={this.closeEmojiKeyboardAndFocus}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={component => (this.component = component)}
|
ref={component => (this.component = component)}
|
||||||
|
@ -1166,6 +1292,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
{recordAudio}
|
{recordAudio}
|
||||||
</View>
|
</View>
|
||||||
{this.renderSendToChannel()}
|
{this.renderSendToChannel()}
|
||||||
|
{this.renderEmojiSearchbar()}
|
||||||
</View>
|
</View>
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
|
@ -1193,7 +1320,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
||||||
kbInitialProps={{ theme }}
|
kbInitialProps={{ theme }}
|
||||||
onKeyboardResigned={this.onKeyboardResigned}
|
onKeyboardResigned={this.onKeyboardResigned}
|
||||||
onItemSelected={this.onEmojiSelected}
|
onItemSelected={this.onKeyboardItemSelected}
|
||||||
trackInteractive
|
trackInteractive
|
||||||
requiresSameParentToManageScrollView
|
requiresSameParentToManageScrollView
|
||||||
addBottomView
|
addBottomView
|
||||||
|
@ -1213,7 +1340,8 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
|
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
|
||||||
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
|
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
|
||||||
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
|
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
|
||||||
uploadFilePermission: state.permissions['mobile-upload-file']
|
uploadFilePermission: state.permissions['mobile-upload-file'],
|
||||||
|
serverVersion: state.server.version
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatchToProps = {
|
const dispatchToProps = {
|
||||||
|
|
|
@ -105,7 +105,7 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
emojiKeyboardContainer: {
|
emojiKeyboardContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
borderTopWidth: StyleSheet.hairlineWidth
|
borderTopWidth: 1
|
||||||
},
|
},
|
||||||
slash: {
|
slash: {
|
||||||
height: 30,
|
height: 30,
|
||||||
|
@ -113,7 +113,7 @@ export default StyleSheet.create({
|
||||||
padding: 5,
|
padding: 5,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
marginHorizontal: 10,
|
marginHorizontal: 10,
|
||||||
borderRadius: 2
|
borderRadius: 4
|
||||||
},
|
},
|
||||||
commandPreviewImage: {
|
commandPreviewImage: {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text, StyleProp, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
|
@ -12,16 +12,17 @@ interface IPasscodeButton {
|
||||||
icon?: TIconsName;
|
icon?: TIconsName;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onPress?: Function;
|
onPress?: Function;
|
||||||
|
style?: StyleProp<ViewStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.memo(({ text, disabled, onPress, icon }: IPasscodeButton) => {
|
const Button = React.memo(({ style, text, disabled, onPress, icon }: IPasscodeButton) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const press = () => onPress && onPress(text);
|
const press = () => onPress && onPress(text);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Touch
|
<Touch
|
||||||
style={[styles.buttonView, { backgroundColor: 'transparent' }]}
|
style={[styles.buttonView, { backgroundColor: 'transparent' }, style]}
|
||||||
underlayColor={themes[theme].passcodeButtonActive}
|
underlayColor={themes[theme].passcodeButtonActive}
|
||||||
rippleColor={themes[theme].passcodeButtonActive}
|
rippleColor={themes[theme].passcodeButtonActive}
|
||||||
enabled={!disabled}
|
enabled={!disabled}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
import React, { forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { Col, Grid, Row } from 'react-native-easy-grid';
|
import { Col, Grid, Row } from 'react-native-easy-grid';
|
||||||
import range from 'lodash/range';
|
import range from 'lodash/range';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import * as Animatable from 'react-native-animatable';
|
import * as Animatable from 'react-native-animatable';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
|
import Orientation from 'react-native-orientation-locker';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
@ -14,6 +15,8 @@ import { useTheme } from '../../../theme';
|
||||||
import LockIcon from './LockIcon';
|
import LockIcon from './LockIcon';
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import Subtitle from './Subtitle';
|
import Subtitle from './Subtitle';
|
||||||
|
import { useDimensions } from '../../../dimensions';
|
||||||
|
import { isTablet } from '../../../lib/methods/helpers';
|
||||||
|
|
||||||
interface IPasscodeBase {
|
interface IPasscodeBase {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -34,7 +37,25 @@ export interface IBase {
|
||||||
|
|
||||||
const Base = forwardRef<IBase, IPasscodeBase>(
|
const Base = forwardRef<IBase, IPasscodeBase>(
|
||||||
({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => {
|
({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => {
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!isTablet) {
|
||||||
|
Orientation.lockToPortrait();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (!isTablet) {
|
||||||
|
Orientation.unlockAllOrientations();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { height } = useDimensions();
|
||||||
|
|
||||||
|
// 206 is the height of the header calculating the margins, icon size height, title font size and subtitle height.
|
||||||
|
// 56 is a fixed number to decrease the height of button numbers.
|
||||||
|
const dinamicHeight = (height - 206 - 56) / 4;
|
||||||
|
const heightButtonRow = { height: dinamicHeight > 102 ? 102 : dinamicHeight };
|
||||||
|
|
||||||
const rootRef = useRef<Animatable.View & View>(null);
|
const rootRef = useRef<Animatable.View & View>(null);
|
||||||
const dotsRef = useRef<Animatable.View & View>(null);
|
const dotsRef = useRef<Animatable.View & View>(null);
|
||||||
|
@ -103,40 +124,40 @@ const Base = forwardRef<IBase, IPasscodeBase>(
|
||||||
<Dots passcode={passcode} length={PASSCODE_LENGTH} />
|
<Dots passcode={passcode} length={PASSCODE_LENGTH} />
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={[styles.row, styles.buttonRow]}>
|
<Row style={[styles.row, heightButtonRow]}>
|
||||||
{range(1, 4).map(i => (
|
{range(1, 4).map(i => (
|
||||||
<Col key={i} style={styles.colButton}>
|
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||||
<Button text={i.toString()} onPress={onPressNumber} />
|
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={[styles.row, styles.buttonRow]}>
|
<Row style={[styles.row, heightButtonRow]}>
|
||||||
{range(4, 7).map(i => (
|
{range(4, 7).map(i => (
|
||||||
<Col key={i} style={styles.colButton}>
|
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||||
<Button text={i.toString()} onPress={onPressNumber} />
|
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={[styles.row, styles.buttonRow]}>
|
<Row style={[styles.row, heightButtonRow]}>
|
||||||
{range(7, 10).map(i => (
|
{range(7, 10).map(i => (
|
||||||
<Col key={i} style={styles.colButton}>
|
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||||
<Button text={i.toString()} onPress={onPressNumber} />
|
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={[styles.row, styles.buttonRow]}>
|
<Row style={[styles.row, heightButtonRow]}>
|
||||||
{showBiometry ? (
|
{showBiometry ? (
|
||||||
<Col style={styles.colButton}>
|
<Col style={[styles.colButton, heightButtonRow]}>
|
||||||
<Button icon='fingerprint' onPress={onBiometryPress} />
|
<Button style={heightButtonRow} icon='fingerprint' onPress={onBiometryPress} />
|
||||||
</Col>
|
</Col>
|
||||||
) : (
|
) : (
|
||||||
<Col style={styles.colButton} />
|
<Col style={[styles.colButton, heightButtonRow]} />
|
||||||
)}
|
)}
|
||||||
<Col style={styles.colButton}>
|
<Col style={[styles.colButton, heightButtonRow]}>
|
||||||
<Button text='0' onPress={onPressNumber} />
|
<Button style={heightButtonRow} text='0' onPress={onPressNumber} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={styles.colButton}>
|
<Col style={[styles.colButton, heightButtonRow]}>
|
||||||
<Button icon='backspace' onPress={onPressDelete} />
|
<Button style={heightButtonRow} icon='backspace' onPress={onPressDelete} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -18,16 +18,12 @@ export default StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
buttonRow: {
|
|
||||||
height: 102
|
|
||||||
},
|
|
||||||
colButton: {
|
colButton: {
|
||||||
flex: 0,
|
flex: 0,
|
||||||
marginLeft: 12,
|
marginLeft: 12,
|
||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: 78,
|
width: 78
|
||||||
height: 78
|
|
||||||
},
|
},
|
||||||
buttonText: {
|
buttonText: {
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
|
@ -37,7 +33,6 @@ export default StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
width: 78,
|
width: 78,
|
||||||
height: 78,
|
|
||||||
borderRadius: 4
|
borderRadius: 4
|
||||||
},
|
},
|
||||||
textTitle: {
|
textTitle: {
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { RadioButton as RadioButtonUiLib } from 'react-native-ui-lib';
|
||||||
|
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
|
export const RadioButton = ({ check, testID, size }: { check: boolean; testID?: string; size?: number }): React.ReactElement => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
return (
|
||||||
|
<RadioButtonUiLib
|
||||||
|
testID={testID}
|
||||||
|
selected={check}
|
||||||
|
size={size || 20}
|
||||||
|
color={check ? colors.tintActive : colors.auxiliaryTintColor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,145 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { StyleSheet, Text, Pressable, View, ScrollView } from 'react-native';
|
|
||||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
|
||||||
import { FlatList } from 'react-native-gesture-handler';
|
|
||||||
|
|
||||||
import Emoji from './message/Emoji';
|
|
||||||
import { useTheme } from '../theme';
|
|
||||||
import { TGetCustomEmoji } from '../definitions/IEmoji';
|
|
||||||
import { IReaction } from '../definitions';
|
|
||||||
import Avatar from './Avatar';
|
|
||||||
import sharedStyles from '../views/Styles';
|
|
||||||
|
|
||||||
const MIN_TAB_WIDTH = 70;
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
reactionsListContainer: { height: '100%', width: '100%' },
|
|
||||||
tabBarItem: {
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
paddingBottom: 10,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
reactionCount: { marginLeft: 5 },
|
|
||||||
emojiName: { margin: 10 },
|
|
||||||
userItemContainer: { marginHorizontal: 10, marginVertical: 5, flexDirection: 'row' },
|
|
||||||
usernameContainer: { marginHorizontal: 10, justifyContent: 'center' },
|
|
||||||
usernameText: { fontSize: 17, ...sharedStyles.textMedium },
|
|
||||||
standardEmojiStyle: { fontSize: 20, color: '#fff' },
|
|
||||||
customEmojiStyle: { width: 25, height: 25 }
|
|
||||||
});
|
|
||||||
|
|
||||||
interface IReactionsListBase {
|
|
||||||
baseUrl: string;
|
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IReactionsListProps extends IReactionsListBase {
|
|
||||||
reactions?: IReaction[];
|
|
||||||
width: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ITabBarItem extends IReactionsListBase {
|
|
||||||
tab: IReaction;
|
|
||||||
index: number;
|
|
||||||
goToPage?: (index: number) => void;
|
|
||||||
}
|
|
||||||
interface IReactionsTabBar extends IReactionsListBase {
|
|
||||||
activeTab?: number;
|
|
||||||
tabs?: IReaction[];
|
|
||||||
goToPage?: (index: number) => void;
|
|
||||||
width: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TabBarItem = ({ tab, index, goToPage, baseUrl, getCustomEmoji }: ITabBarItem) => {
|
|
||||||
const { colors } = useTheme();
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
key={tab.emoji}
|
|
||||||
onPress={() => {
|
|
||||||
goToPage?.(index);
|
|
||||||
}}
|
|
||||||
style={({ pressed }: { pressed: boolean }) => ({
|
|
||||||
opacity: pressed ? 0.7 : 1
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<View style={styles.tabBarItem}>
|
|
||||||
<Emoji
|
|
||||||
content={tab.emoji}
|
|
||||||
standardEmojiStyle={styles.standardEmojiStyle}
|
|
||||||
customEmojiStyle={styles.customEmojiStyle}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
getCustomEmoji={getCustomEmoji}
|
|
||||||
/>
|
|
||||||
<Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text>
|
|
||||||
</View>
|
|
||||||
</Pressable>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ReactionsTabBar = ({ tabs, activeTab, goToPage, baseUrl, getCustomEmoji, width }: IReactionsTabBar) => {
|
|
||||||
const tabWidth = tabs && Math.max(width / tabs.length, MIN_TAB_WIDTH);
|
|
||||||
const { colors } = useTheme();
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
|
|
||||||
{tabs?.map((tab, index) => {
|
|
||||||
const isActiveTab = activeTab === index;
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: tabWidth,
|
|
||||||
borderBottomWidth: isActiveTab ? 2 : 1,
|
|
||||||
borderColor: isActiveTab ? colors.tintActive : colors.separatorColor
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TabBarItem tab={tab} index={index} goToPage={goToPage} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const UsersList = ({ tabLabel }: { tabLabel: IReaction }) => {
|
|
||||||
const { colors } = useTheme();
|
|
||||||
const { emoji, usernames } = tabLabel;
|
|
||||||
return (
|
|
||||||
<FlatList
|
|
||||||
data={usernames}
|
|
||||||
ListHeaderComponent={() => (
|
|
||||||
<View style={styles.emojiName}>
|
|
||||||
<Text style={{ color: colors.auxiliaryTintColor }}>{emoji}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
renderItem={({ item }) => (
|
|
||||||
<View style={styles.userItemContainer}>
|
|
||||||
<Avatar text={item} size={36} />
|
|
||||||
<View style={styles.usernameContainer}>
|
|
||||||
<Text style={[styles.usernameText, { color: colors.titleText }]}>{item}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
keyExtractor={item => item}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ReactionsList = ({ reactions, baseUrl, getCustomEmoji, width }: IReactionsListProps): React.ReactElement => {
|
|
||||||
// sorting reactions in descending order on the basic of number of users reacted
|
|
||||||
const sortedReactions = reactions?.sort((reaction1, reaction2) => reaction2.usernames.length - reaction1.usernames.length);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.reactionsListContainer}>
|
|
||||||
<ScrollableTabView renderTabBar={() => <ReactionsTabBar baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} width={width} />}>
|
|
||||||
{sortedReactions?.map(reaction => (
|
|
||||||
<UsersList tabLabel={reaction} key={reaction.emoji} />
|
|
||||||
))}
|
|
||||||
</ScrollableTabView>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ReactionsList;
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View, FlatList } from 'react-native';
|
||||||
|
|
||||||
|
import Emoji from '../message/Emoji';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import { IReaction } from '../../definitions';
|
||||||
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import styles from './styles';
|
||||||
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
|
||||||
|
interface IAllReactionsListItemProps {
|
||||||
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
|
item: IReaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAllTabProps {
|
||||||
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
|
tabLabel: IReaction;
|
||||||
|
reactions?: IReaction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemProps) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
|
||||||
|
const username = useAppSelector(state => state.login.user.username);
|
||||||
|
const count = item.usernames.length;
|
||||||
|
|
||||||
|
let displayNames;
|
||||||
|
if (useRealName && item.names) {
|
||||||
|
displayNames = item.names
|
||||||
|
.slice(0, 3)
|
||||||
|
.map((name, index) => (item.usernames[index] === username ? I18n.t('you') : name))
|
||||||
|
.join(', ');
|
||||||
|
} else {
|
||||||
|
displayNames = item.usernames
|
||||||
|
.slice(0, 3)
|
||||||
|
.map((otherUsername: string) => (username === otherUsername ? I18n.t('you') : otherUsername))
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
if (count > 3) {
|
||||||
|
displayNames = `${displayNames} ${I18n.t('and_more')} ${count - 3}`;
|
||||||
|
} else {
|
||||||
|
displayNames = displayNames.replace(/,(?=[^,]*$)/, ` ${I18n.t('and')}`);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.listItemContainer}>
|
||||||
|
<Emoji
|
||||||
|
content={item.emoji}
|
||||||
|
standardEmojiStyle={styles.allTabStandardEmojiStyle}
|
||||||
|
customEmojiStyle={styles.allTabCustomEmojiStyle}
|
||||||
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
/>
|
||||||
|
<View style={styles.textContainer}>
|
||||||
|
<Text style={[styles.allListNPeopleReacted, { color: colors.bodyText }]}>
|
||||||
|
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.allListWhoReacted, { color: colors.auxiliaryText }]}>{displayNames}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AllTab = ({ reactions, getCustomEmoji }: IAllTabProps): React.ReactElement => (
|
||||||
|
<View style={styles.allTabContainer} testID='reactionsListAllTab'>
|
||||||
|
<FlatList
|
||||||
|
data={reactions}
|
||||||
|
contentContainerStyle={styles.listContainer}
|
||||||
|
renderItem={({ item }) => <AllReactionsListItem item={item} getCustomEmoji={getCustomEmoji} />}
|
||||||
|
keyExtractor={item => item.emoji}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AllTab;
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
|
import { TGetCustomEmoji, ICustomEmoji } from '../../definitions';
|
||||||
|
import ReactionsList from '.';
|
||||||
|
import { mockedStore as store } from '../../reducers/mockedStore';
|
||||||
|
import { updateSettings } from '../../actions/settings';
|
||||||
|
|
||||||
|
const getCustomEmoji: TGetCustomEmoji = content => {
|
||||||
|
const customEmoji = {
|
||||||
|
marioparty: { name: content, extension: 'gif' },
|
||||||
|
react_rocket: { name: content, extension: 'png' },
|
||||||
|
nyan_rocket: { name: content, extension: 'png' }
|
||||||
|
}[content] as ICustomEmoji;
|
||||||
|
return customEmoji;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reactions = [
|
||||||
|
{
|
||||||
|
emoji: ':marioparty:',
|
||||||
|
_id: 'marioparty',
|
||||||
|
usernames: ['rocket.cat', 'diego.mello'],
|
||||||
|
names: ['Rocket Cat', 'Diego Mello']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: ':react_rocket:',
|
||||||
|
_id: 'react_rocket',
|
||||||
|
usernames: ['rocket.cat', 'diego.mello'],
|
||||||
|
names: ['Rocket Cat', 'Diego Mello']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: ':nyan_rocket:',
|
||||||
|
_id: 'nyan_rocket',
|
||||||
|
usernames: ['rocket.cat'],
|
||||||
|
names: ['Rocket Cat']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: ':grinning:',
|
||||||
|
_id: 'grinning',
|
||||||
|
usernames: ['diego.mello'],
|
||||||
|
names: ['Diego Mello']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: ':tada:',
|
||||||
|
_id: 'tada',
|
||||||
|
usernames: ['diego.mello'],
|
||||||
|
names: ['Diego Mello']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ReactionsListStory = () => {
|
||||||
|
store.dispatch(updateSettings('UI_Use_Real_Name', false));
|
||||||
|
return (
|
||||||
|
<View style={{ paddingVertical: 10, flex: 1 }}>
|
||||||
|
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReactionsListFullName = () => {
|
||||||
|
store.dispatch(updateSettings('UI_Use_Real_Name', true));
|
||||||
|
return (
|
||||||
|
<View style={{ paddingVertical: 10, flex: 1 }}>
|
||||||
|
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'ReactionsList'
|
||||||
|
};
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { fireEvent, render, within } from '@testing-library/react-native';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import ReactionsList from '.';
|
||||||
|
import { mockedStore } from '../../reducers/mockedStore';
|
||||||
|
|
||||||
|
const getCustomEmoji = jest.fn();
|
||||||
|
const reactions = [
|
||||||
|
{
|
||||||
|
emoji: 'marioparty',
|
||||||
|
_id: 'marioparty',
|
||||||
|
usernames: ['rocket.cat', 'diego.mello'],
|
||||||
|
names: ['Rocket Cat', 'Diego Mello']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: 'react_rocket',
|
||||||
|
_id: 'react_rocket',
|
||||||
|
usernames: ['rocket.cat', 'diego.mello'],
|
||||||
|
names: ['Rocket Cat', 'Diego Mello']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: 'nyan_rocket',
|
||||||
|
_id: 'nyan_rocket',
|
||||||
|
usernames: ['rocket.cat'],
|
||||||
|
names: ['Rocket Cat']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: 'grinning',
|
||||||
|
_id: 'grinning',
|
||||||
|
usernames: ['diego.mello'],
|
||||||
|
names: ['Diego Mello']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const Render = () => (
|
||||||
|
<Provider store={mockedStore}>
|
||||||
|
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ReactionsList', () => {
|
||||||
|
test('should render Reactions List', async () => {
|
||||||
|
const { findByTestId } = render(<Render />);
|
||||||
|
const ReactionsListView = await findByTestId('reactionsList');
|
||||||
|
expect(ReactionsListView).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render tab bar', async () => {
|
||||||
|
const { findByTestId } = render(<Render />);
|
||||||
|
const AllTab = await findByTestId('reactionsTabBar');
|
||||||
|
expect(AllTab).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render All tab', async () => {
|
||||||
|
const { findByTestId } = render(<Render />);
|
||||||
|
const AllTab = await findByTestId('reactionsListAllTab');
|
||||||
|
expect(AllTab).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('correct tab on clicking tab item', async () => {
|
||||||
|
const { findByTestId } = render(<Render />);
|
||||||
|
const tab = await findByTestId(`tabBarItem-${reactions[0].emoji}`);
|
||||||
|
fireEvent.press(tab);
|
||||||
|
const usersList = await findByTestId(`usersList-${reactions[0].emoji}`);
|
||||||
|
expect(usersList).toBeTruthy();
|
||||||
|
const emojiName = await within(usersList).getByTestId(`usersListEmojiName`);
|
||||||
|
expect(emojiName.props.children).toEqual(reactions[0].emoji);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render correct number of reactions', async () => {
|
||||||
|
const { findByTestId } = render(<Render />);
|
||||||
|
const tab = await findByTestId(`tabBarItem-${reactions[0].emoji}`);
|
||||||
|
fireEvent.press(tab);
|
||||||
|
const usersList = await findByTestId(`usersList-${reactions[0].emoji}`);
|
||||||
|
const allReactions = await within(usersList).getAllByTestId('userItem');
|
||||||
|
expect(allReactions).toHaveLength(reactions[0].usernames.length);
|
||||||
|
});
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue